summaryrefslogtreecommitdiffstats
path: root/tools/lint
diff options
context:
space:
mode:
Diffstat (limited to 'tools/lint')
-rw-r--r--tools/lint/docs/Makefile192
-rw-r--r--tools/lint/docs/conf.py112
-rw-r--r--tools/lint/docs/create.rst153
-rw-r--r--tools/lint/docs/index.rst37
-rw-r--r--tools/lint/docs/linters/eslint-plugin-mozilla.rst174
-rw-r--r--tools/lint/docs/linters/eslint.rst45
-rw-r--r--tools/lint/docs/linters/flake8.rst50
-rw-r--r--tools/lint/docs/make.bat263
-rw-r--r--tools/lint/docs/usage.rst41
-rw-r--r--tools/lint/eslint.lint368
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/LICENSE363
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js188
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js524
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/index.js45
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js363
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc.js51
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js113
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js83
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js15
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js49
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js37
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js55
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js112
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js39
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js37
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js48
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js34
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/package.json29
-rw-r--r--tools/lint/eslint/manifest.tt9
-rw-r--r--tools/lint/eslint/modules.json247
-rw-r--r--tools/lint/eslint/npm-shrinkwrap.json718
-rw-r--r--tools/lint/eslint/package.json16
-rwxr-xr-xtools/lint/eslint/update70
-rw-r--r--tools/lint/flake8.lint195
-rw-r--r--tools/lint/flake8/flake8_requirements.txt4
-rw-r--r--tools/lint/mach_commands.py62
-rw-r--r--tools/lint/wpt.lint55
-rw-r--r--tools/lint/wpt_manifest.lint34
38 files changed, 5030 insertions, 0 deletions
diff --git a/tools/lint/docs/Makefile b/tools/lint/docs/Makefile
new file mode 100644
index 000000000..1d97fa065
--- /dev/null
+++ b/tools/lint/docs/Makefile
@@ -0,0 +1,192 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " applehelp to make an Apple Help Book"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " xml to make Docutils-native XML files"
+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+ @echo " coverage to run coverage check of the documentation (if enabled)"
+
+clean:
+ rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/mozlint.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/mozlint.qhc"
+
+applehelp:
+ $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+ @echo
+ @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+ @echo "N.B. You won't be able to view it unless you put it in" \
+ "~/Library/Documentation/Help or install it in your application" \
+ "bundle."
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/mozlint"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/mozlint"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through platex and dvipdfmx..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
+
+coverage:
+ $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+ @echo "Testing of coverage in the sources finished, look at the " \
+ "results in $(BUILDDIR)/coverage/python.txt."
+
+xml:
+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+ @echo
+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+ @echo
+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/tools/lint/docs/conf.py b/tools/lint/docs/conf.py
new file mode 100644
index 000000000..31ebb6dcc
--- /dev/null
+++ b/tools/lint/docs/conf.py
@@ -0,0 +1,112 @@
+# -*- coding: utf-8 -*-
+#
+# mozlint documentation build configuration file, created by
+# sphinx-quickstart on Fri Nov 27 17:38:49 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+
+import os
+
+# -- General configuration ------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.intersphinx',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'mozlint'
+copyright = u'2015, Andrew Halberstadt'
+author = u'Andrew Halberstadt'
+
+# The short X.Y version.
+version = '0.1.0'
+# The full version, including alpha/beta/rc tags.
+release = '0.1.0'
+
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+if os.environ.get('READTHEDOCS', None) != 'True':
+ try:
+ import sphinx_rtd_theme
+ html_theme = 'sphinx_rtd_theme'
+ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+ except ImportError:
+ pass
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'mozlintdoc'
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'mozlint.tex', u'mozlint Documentation',
+ u'Andrew Halberstadt', 'manual'),
+]
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'mozlint', u'mozlint Documentation',
+ [author], 1)
+]
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'mozlint', u'mozlint Documentation',
+ author, 'mozlint', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/': None}
diff --git a/tools/lint/docs/create.rst b/tools/lint/docs/create.rst
new file mode 100644
index 000000000..a132417a8
--- /dev/null
+++ b/tools/lint/docs/create.rst
@@ -0,0 +1,153 @@
+Adding a New Linter to the Tree
+===============================
+
+A linter is a python file with a ``.lint`` extension and a global dict called LINTER. Depending on how
+complex it is, there may or may not be any actual python code alongside the LINTER definition.
+
+Here's a trivial example:
+
+no-eval.lint
+
+.. code-block:: python
+
+ LINTER = {
+ 'name': 'EvalLinter',
+ 'description': "Ensures the string 'eval' doesn't show up."
+ 'include': "**/*.js",
+ 'type': 'string',
+ 'payload': 'eval',
+ }
+
+Now ``no-eval.lint`` gets passed into :func:`LintRoller.read`.
+
+
+Linter Types
+------------
+
+There are three types of linters, though more may be added in the future.
+
+1. string - fails if substring is found
+2. regex - fails if regex matches
+3. external - fails if a python function returns a non-empty result list
+4. structured_log - fails if a mozlog logger emits any lint_error or lint_warning log messages
+
+As seen from the example above, string and regex linters are very easy to create, but they
+should be avoided if possible. It is much better to use a context aware linter for the language you
+are trying to lint. For example, use eslint to lint JavaScript files, use flake8 to lint python
+files, etc.
+
+Which brings us to the third and most interesting type of linter,
+external. External linters call an arbitrary python function which is
+responsible for not only running the linter, but ensuring the results
+are structured properly. For example, an external type could shell out
+to a 3rd party linter, collect the output and format it into a list of
+:class:`ResultContainer` objects. The signature for this python
+function is ``lint(files, **kwargs)``, where ``files`` is a list of
+files to lint.
+
+Structured log linters are much like external linters, but suitable
+for cases where the linter code is using mozlog and emits
+``lint_error`` or ``lint_warning`` logging messages when the lint
+fails. This is recommended for writing novel gecko-specific lints. In
+this case the signature for lint functions is ``lint(files, logger,
+**kwargs)``.
+
+LINTER Definition
+-----------------
+
+Each ``.lint`` file must have a variable called LINTER which is a dict containing metadata about the
+linter. Here are the supported keys:
+
+* name - The name of the linter (required)
+* description - A brief description of the linter's purpose (required)
+* type - One of 'string', 'regex' or 'external' (required)
+* payload - The actual linting logic, depends on the type (required)
+* include - A list of glob patterns that must be matched (optional)
+* exclude - A list of glob patterns that must not be matched (optional)
+* extensions - A list of file extensions to be considered (optional)
+* setup - A function that sets up external dependencies (optional)
+
+In addition to the above, some ``.lint`` files correspond to a single lint rule. For these, the
+following additional keys may be specified:
+
+* message - A string to print on infraction (optional)
+* hint - A string with a clue on how to fix the infraction (optional)
+* rule - An id string for the lint rule (optional)
+* level - The severity of the infraction, either 'error' or 'warning' (optional)
+
+For structured_log lints the following additional keys apply:
+
+* logger - A StructuredLog object to use for logging. If not supplied
+ one will be created (optional)
+
+Example
+-------
+
+Here is an example of an external linter that shells out to the python flake8 linter:
+
+.. code-block:: python
+
+ import json
+ import os
+ import subprocess
+ from collections import defaultdict
+
+ from mozlint import result
+
+
+ FLAKE8_NOT_FOUND = """
+ Could not find flake8! Install flake8 and try again.
+ """.strip()
+
+
+ def lint(files, **lintargs):
+ import which
+
+ binary = os.environ.get('FLAKE8')
+ if not binary:
+ try:
+ binary = which.which('flake8')
+ except which.WhichError:
+ print(FLAKE8_NOT_FOUND)
+ return 1
+
+ # Flake8 allows passing in a custom format string. We use
+ # this to help mold the default flake8 format into what
+ # mozlint's ResultContainer object expects.
+ cmdargs = [
+ binary,
+ '--format',
+ '{"path":"%(path)s","lineno":%(row)s,"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}',
+ ] + files
+
+ proc = subprocess.Popen(cmdargs, stdout=subprocess.PIPE, env=os.environ)
+ output = proc.communicate()[0]
+
+ # all passed
+ if not output:
+ return []
+
+ results = []
+ for line in output.splitlines():
+ # res is a dict of the form specified by --format above
+ res = json.loads(line)
+
+ # parse level out of the id string
+ if 'code' in res and res['code'].startswith('W'):
+ res['level'] = 'warning'
+
+ # result.from_linter is a convenience method that
+ # creates a ResultContainer using a LINTER definition
+ # to populate some defaults.
+ results.append(result.from_linter(LINTER, **res))
+
+ return results
+
+
+ LINTER = {
+ 'name': "flake8",
+ 'description': "Python linter",
+ 'include': ['**/*.py'],
+ 'type': 'external',
+ 'payload': lint,
+ }
diff --git a/tools/lint/docs/index.rst b/tools/lint/docs/index.rst
new file mode 100644
index 000000000..54bc404a3
--- /dev/null
+++ b/tools/lint/docs/index.rst
@@ -0,0 +1,37 @@
+Linting
+=======
+
+Linters are used in mozilla-central to help enforce coding style and avoid bad practices. Due to the
+wide variety of languages in use and the varying style preferences per team, this is not an easy
+task. In addition, linters should be runnable from editors, from the command line, from review tools
+and from continuous integration. It's easy to see how the complexity of running all of these
+different kinds of linters in all of these different places could quickly balloon out of control.
+
+``Mozlint`` is a library that accomplishes two goals:
+
+1. It provides a standard method for adding new linters to the tree, which can be as easy as
+ defining a json object in a ``.lint`` file. This helps keep lint related code localized, and
+ prevents different teams from coming up with their own unique lint implementations.
+2. It provides a streamlined interface for running all linters at once. Instead of running N
+ different lint commands to test your patch, a single ``mach lint`` command will automatically run
+ all applicable linters. This means there is a single API surface that other tools can use to
+ invoke linters.
+
+``Mozlint`` isn't designed to be used directly by end users. Instead, it can be consumed by things
+like mach, mozreview and taskcluster.
+
+.. toctree::
+ :caption: Linting User Guide
+ :maxdepth: 2
+
+ usage
+ create
+ linters/eslint
+ linters/flake8
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/tools/lint/docs/linters/eslint-plugin-mozilla.rst b/tools/lint/docs/linters/eslint-plugin-mozilla.rst
new file mode 100644
index 000000000..e75864238
--- /dev/null
+++ b/tools/lint/docs/linters/eslint-plugin-mozilla.rst
@@ -0,0 +1,174 @@
+=====================
+Mozilla ESLint Plugin
+=====================
+
+
+balanced-listeners
+------------------
+
+Checks that for every occurence of 'addEventListener' or 'on' there is an
+occurence of 'removeEventListener' or 'off' with the same event name.
+
+
+components-imports
+------------------
+
+Checks the filename of imported files e.g. ``Cu.import("some/path/Blah.jsm")``
+adds Blah to the global scope.
+
+
+import-browserjs-globals
+------------------------
+
+When included files from the main browser UI scripts will be loaded and any
+declared globals will be defined for the current file. This is mostly useful for
+browser-chrome mochitests that call browser functions.
+
+
+import-globals-from
+-------------------
+
+Parses a file for globals defined in various unique Mozilla ways.
+
+When a "import-globals-from <path>" comment is found in a file, then all globals
+from the file at <path> will be imported in the current scope. This will also
+operate recursively.
+
+This is useful for scripts that are loaded as <script> tag in a window and rely
+on each other's globals.
+
+If <path> is a relative path, then it must be relative to the file being
+checked by the rule.
+
+
+import-headjs-globals
+---------------------
+
+Import globals from head.js and from any files that were imported by
+head.js (as far as we can correctly resolve the path).
+
+The following file import patterns are supported:
+
+- ``Services.scriptloader.loadSubScript(path)``
+- ``loader.loadSubScript(path)``
+- ``loadSubScript(path)``
+- ``loadHelperScript(path)``
+- ``import-globals-from path``
+
+If path does not exist because it is generated e.g.
+``testdir + "/somefile.js"`` we do our best to resolve it.
+
+The following patterns are supported:
+
+- ``Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");``
+- ``loader.lazyImporter(this, "name1");``
+- ``loader.lazyRequireGetter(this, "name2"``
+- ``loader.lazyServiceGetter(this, "name3"``
+- ``XPCOMUtils.defineLazyModuleGetter(this, "setNamedTimeout", ...)``
+- ``loader.lazyGetter(this, "toolboxStrings"``
+- ``XPCOMUtils.defineLazyGetter(this, "clipboardHelper"``
+
+
+mark-test-function-used
+-----------------------
+
+Simply marks `test` (the test method) or `run_test` as used when in mochitests
+or xpcshell tests respectively. This avoids ESLint telling us that the function
+is never called.
+
+
+no-aArgs
+--------
+
+Checks that function argument names don't start with lowercase 'a' followed by
+a capital letter. This is to prevent the use of Hungarian notation whereby the
+first letter is a prefix that indicates the type or intended use of a variable.
+
+
+no-cpows-in-tests
+-----------------
+
+This rule checks if the file is a browser mochitest and, if so, checks for
+possible CPOW usage by checking for the following strings:
+
+- "gBrowser.contentWindow"
+- "gBrowser.contentDocument"
+- "gBrowser.selectedBrowser.contentWindow"
+- "browser.contentDocument"
+- "window.content"
+- "content"
+- "content."
+
+Note: These are string matches so we will miss situations where the parent
+object is assigned to another variable e.g.::
+
+ var b = gBrowser;
+ b.content // Would not be detected as a CPOW.
+
+
+no-single-arg-cu-import
+-----------------------
+
+Rejects calls to "Cu.import" that do not supply a second argument (meaning they
+add the exported properties into global scope).
+
+
+reject-importGlobalProperties
+-----------------------------
+
+Rejects calls to ``Cu.importGlobalProperties``. Use of this function is
+undesirable in some parts of the tree.
+
+
+reject-some-requires
+--------------------
+
+This takes an option, a regular expression. Invocations of
+``require`` with a string literal argument are matched against this
+regexp; and if it matches, the ``require`` use is flagged.
+
+
+this-top-level-scope
+--------------------
+
+Treats top-level assignments like ``this.mumble = value`` as declaring a global.
+
+Note: These are string matches so we will miss situations where the parent
+object is assigned to another variable e.g.::
+
+ var b = gBrowser;
+ b.content // Would not be detected as a CPOW.
+
+
+var-only-at-top-level
+---------------------
+
+Marks all var declarations that are not at the top level invalid.
+
+
+Example
+=======
+
++-------+-----------------------+
+| Possible values for all rules |
++-------+-----------------------+
+| Value | Meaning |
++-------+-----------------------+
+| 0 | Deactivated |
++-------+-----------------------+
+| 1 | Warning |
++-------+-----------------------+
+| 2 | Error |
++-------+-----------------------+
+
+Example configuration::
+
+ "rules": {
+ "mozilla/balanced-listeners": 2,
+ "mozilla/components-imports": 1,
+ "mozilla/import-globals-from": 1,
+ "mozilla/import-headjs-globals": 1,
+ "mozilla/mark-test-function-used": 1,
+ "mozilla/var-only-at-top-level": 1,
+ "mozilla/no-cpows-in-tests": 1,
+ }
diff --git a/tools/lint/docs/linters/eslint.rst b/tools/lint/docs/linters/eslint.rst
new file mode 100644
index 000000000..31f7f8121
--- /dev/null
+++ b/tools/lint/docs/linters/eslint.rst
@@ -0,0 +1,45 @@
+ESLint
+======
+
+`ESLint`_ is a popular linter for JavaScript.
+
+Run Locally
+-----------
+
+The mozlint integration of `ESLint`_ can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter eslint <file paths>
+
+Alternatively, omit the ``--linter eslint`` and run all configured linters, which will include
+ESLint.
+
+
+Configuration
+-------------
+
+The `ESLint`_ mozilla-central integration uses a blacklist to exclude certain directories from being
+linted. This lives in ``topsrcdir/.eslintignore``. If you don't wish your directory to be linted, it
+must be added here.
+
+The global configuration file lives in ``topsrcdir/.eslintrc``. This global configuration can be
+overridden by including an ``.eslintrc`` in the appropriate subdirectory. For an overview of the
+supported configuration, see `ESLint's documentation`_.
+
+
+ESLint Plugin Mozilla
+---------------------
+
+In addition to default ESLint rules, there are several Mozilla-specific rules that are defined in
+the :doc:`Mozilla ESLint Plugin <eslint-plugin-mozilla>`.
+
+
+.. _ESLint: http://eslint.org/
+.. _ESLint's documentation: http://eslint.org/docs/user-guide/configuring
+
+
+.. toctree::
+ :hidden:
+
+ eslint-plugin-mozilla
diff --git a/tools/lint/docs/linters/flake8.rst b/tools/lint/docs/linters/flake8.rst
new file mode 100644
index 000000000..5ef17d41d
--- /dev/null
+++ b/tools/lint/docs/linters/flake8.rst
@@ -0,0 +1,50 @@
+Flake8
+======
+
+`Flake8`_ is a popular lint wrapper for python. Under the hood, it runs three other tools and
+combines their results:
+
+* `pep8`_ for checking style
+* `pyflakes`_ for checking syntax
+* `mccabe`_ for checking complexity
+
+
+Run Locally
+-----------
+
+The mozlint integration of flake8 can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter flake8 <file paths>
+
+Alternatively, omit the ``--linter flake8`` and run all configured linters, which will include
+flake8.
+
+
+Configuration
+-------------
+
+Only directories explicitly whitelisted will have flake8 run against them. To enable flake8 linting
+in a source directory, it must be added to the ``include`` directive in ```tools/lint/flake8.lint``.
+If you wish to exclude a subdirectory of an included one, you can add it to the ``exclude``
+directive.
+
+The default configuration file lives in ``topsrcdir/.flake8``. The default configuration can be
+overriden for a given subdirectory by creating a new ``.flake8`` file in the subdirectory. Be warned
+that ``.flake8`` files cannot inherit from one another, so all configuration you wish to keep must
+be re-defined.
+
+.. warning::
+
+ Only ``.flake8`` files that live in a directory that is explicitly included in the ``include``
+ directive will be considered. See `bug 1277851`_ for more details.
+
+For an overview of the supported configuration, see `flake8's documentation`_.
+
+.. _Flake8: https://flake8.readthedocs.io/en/latest/
+.. _pep8: http://pep8.readthedocs.io/en/latest/
+.. _pyflakes: https://github.com/pyflakes/pyflakes
+.. _mccabe: https://github.com/pycqa/mccabe
+.. _bug 1277851: https://bugzilla.mozilla.org/show_bug.cgi?id=1277851
+.. _flake8's documentation: https://flake8.readthedocs.io/en/latest/config.html
diff --git a/tools/lint/docs/make.bat b/tools/lint/docs/make.bat
new file mode 100644
index 000000000..be5489633
--- /dev/null
+++ b/tools/lint/docs/make.bat
@@ -0,0 +1,263 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^<target^>` where ^<target^> is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. xml to make Docutils-native XML files
+ echo. pseudoxml to make pseudoxml-XML files for display purposes
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ echo. coverage to run coverage check of the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+
+REM Check if sphinx-build is available and fallback to Python version if any
+%SPHINXBUILD% 2> nul
+if errorlevel 9009 goto sphinx_python
+goto sphinx_ok
+
+:sphinx_python
+
+set SPHINXBUILD=python -m sphinx.__init__
+%SPHINXBUILD% 2> nul
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+:sphinx_ok
+
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\mozlint.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\mozlint.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "latexpdf" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ cd %BUILDDIR%/latex
+ make all-pdf
+ cd %~dp0
+ echo.
+ echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "latexpdfja" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ cd %BUILDDIR%/latex
+ make all-pdf-ja
+ cd %~dp0
+ echo.
+ echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+if "%1" == "coverage" (
+ %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of coverage in the sources finished, look at the ^
+results in %BUILDDIR%/coverage/python.txt.
+ goto end
+)
+
+if "%1" == "xml" (
+ %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The XML files are in %BUILDDIR%/xml.
+ goto end
+)
+
+if "%1" == "pseudoxml" (
+ %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
+ goto end
+)
+
+:end
diff --git a/tools/lint/docs/usage.rst b/tools/lint/docs/usage.rst
new file mode 100644
index 000000000..0eb5f3d22
--- /dev/null
+++ b/tools/lint/docs/usage.rst
@@ -0,0 +1,41 @@
+Running Linters Locally
+=======================
+
+You can run all the various linters in the tree using the ``mach lint`` command. Simply pass in the
+directory or file you wish to lint (defaults to current working directory):
+
+.. parsed-literal::
+
+ ./mach lint path/to/files
+
+Multiple paths are allowed:
+
+.. parsed-literal::
+
+ ./mach lint path/to/foo.js path/to/bar.py path/to/dir
+
+``Mozlint`` will automatically determine which types of files exist, and which linters need to be run
+against them. For example, if the directory contains both JavaScript and Python files then mozlint
+will automatically run both ESLint and Flake8 against those files respectively.
+
+To restrict which linters are invoked manually, pass in ``-l/--linter``:
+
+.. parsed-literal::
+
+ ./mach lint -l eslint path/to/files
+
+Finally, ``mozlint`` can lint the files touched by a set of revisions or the working directory using
+the ``-r/--rev`` and ``-w/--workdir`` arguments respectively. These work both with mercurial and
+git. In the case of ``--rev`` the value is passed directly to the underlying vcs, so normal revision
+specifiers will work. For example, say we want to lint all files touched by the last three commits.
+In mercurial, this would be:
+
+.. parsed-literal::
+
+ ./mach lint -r ".~2::."
+
+In git, this would be:
+
+.. parsed-literal::
+
+ ./mach lint -r "HEAD~2 HEAD"
diff --git a/tools/lint/eslint.lint b/tools/lint/eslint.lint
new file mode 100644
index 000000000..61eb1daaa
--- /dev/null
+++ b/tools/lint/eslint.lint
@@ -0,0 +1,368 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import json
+import os
+import platform
+import re
+import signal
+import subprocess
+import sys
+from distutils.version import LooseVersion
+
+import which
+from mozprocess import ProcessHandler
+
+from mozlint import result
+
+ESLINT_ERROR_MESSAGE = """
+An error occurred running eslint. Please check the following error messages:
+
+{}
+""".strip()
+
+ESLINT_NOT_FOUND_MESSAGE = """
+Could not find eslint! We looked at the --binary option, at the ESLINT
+environment variable, and then at your local node_modules path. Please Install
+eslint and needed plugins with:
+
+mach eslint --setup
+
+and try again.
+""".strip()
+
+NODE_NOT_FOUND_MESSAGE = """
+nodejs v4.2.3 is either not installed or is installed to a non-standard path.
+Please install nodejs from https://nodejs.org and try again.
+
+Valid installation paths:
+""".strip()
+
+NPM_NOT_FOUND_MESSAGE = """
+Node Package Manager (npm) is either not installed or installed to a
+non-standard path. Please install npm from https://nodejs.org (it comes as an
+option in the node installation) and try again.
+
+Valid installation paths:
+""".strip()
+
+
+VERSION_RE = re.compile(r"^\d+\.\d+\.\d+$")
+CARET_VERSION_RANGE_RE = re.compile(r"^\^((\d+)\.\d+\.\d+)$")
+
+EXTENSIONS = ['.js', '.jsm', '.jsx', '.xml', '.html', '.xhtml']
+
+project_root = None
+
+
+def eslint_setup():
+ """Ensure eslint is optimally configured.
+
+ This command will inspect your eslint configuration and
+ guide you through an interactive wizard helping you configure
+ eslint for optimal use on Mozilla projects.
+ """
+ orig_cwd = os.getcwd()
+ sys.path.append(os.path.dirname(__file__))
+
+ module_path = get_eslint_module_path()
+
+ # npm sometimes fails to respect cwd when it is run using check_call so
+ # we manually switch folders here instead.
+ os.chdir(module_path)
+
+ npm_path = get_node_or_npm_path("npm")
+ if not npm_path:
+ return 1
+
+ # Install ESLint and external plugins
+ cmd = [npm_path, "install"]
+ print("Installing eslint for mach using \"%s\"..." % (" ".join(cmd)))
+ if not call_process("eslint", cmd):
+ return 1
+
+ # Install in-tree ESLint plugin
+ cmd = [npm_path, "install",
+ os.path.join(module_path, "eslint-plugin-mozilla")]
+ print("Installing eslint-plugin-mozilla using \"%s\"..." % (" ".join(cmd)))
+ if not call_process("eslint-plugin-mozilla", cmd):
+ return 1
+
+ eslint_path = os.path.join(module_path, "node_modules", ".bin", "eslint")
+
+ print("\nESLint and approved plugins installed successfully!")
+ print("\nNOTE: Your local eslint binary is at %s\n" % eslint_path)
+
+ os.chdir(orig_cwd)
+
+
+def call_process(name, cmd, cwd=None):
+ try:
+ with open(os.devnull, "w") as fnull:
+ subprocess.check_call(cmd, cwd=cwd, stdout=fnull)
+ except subprocess.CalledProcessError:
+ if cwd:
+ print("\nError installing %s in the %s folder, aborting." % (name, cwd))
+ else:
+ print("\nError installing %s, aborting." % name)
+
+ return False
+
+ return True
+
+
+def expected_eslint_modules():
+ # Read the expected version of ESLint and external modules
+ expected_modules_path = os.path.join(get_eslint_module_path(), "package.json")
+ with open(expected_modules_path, "r") as f:
+ expected_modules = json.load(f)["dependencies"]
+
+ # Also read the in-tree ESLint plugin version
+ mozilla_json_path = os.path.join(get_eslint_module_path(),
+ "eslint-plugin-mozilla", "package.json")
+ with open(mozilla_json_path, "r") as f:
+ expected_modules["eslint-plugin-mozilla"] = json.load(f)["version"]
+
+ return expected_modules
+
+
+def eslint_module_has_issues():
+ has_issues = False
+ node_modules_path = os.path.join(get_eslint_module_path(), "node_modules")
+
+ for name, version_range in expected_eslint_modules().iteritems():
+ path = os.path.join(node_modules_path, name, "package.json")
+
+ if not os.path.exists(path):
+ print("%s v%s needs to be installed locally." % (name, version_range))
+ has_issues = True
+ continue
+
+ data = json.load(open(path))
+
+ if not version_in_range(data["version"], version_range):
+ print("%s v%s should be v%s." % (name, data["version"], version_range))
+ has_issues = True
+
+ return has_issues
+
+
+def version_in_range(version, version_range):
+ """
+ Check if a module version is inside a version range. Only supports explicit versions and
+ caret ranges for the moment, since that's all we've used so far.
+ """
+ if version == version_range:
+ return True
+
+ version_match = VERSION_RE.match(version)
+ if not version_match:
+ raise RuntimeError("mach eslint doesn't understand module version %s" % version)
+ version = LooseVersion(version)
+
+ # Caret ranges as specified by npm allow changes that do not modify the left-most non-zero
+ # digit in the [major, minor, patch] tuple. The code below assumes the major digit is
+ # non-zero.
+ range_match = CARET_VERSION_RANGE_RE.match(version_range)
+ if range_match:
+ range_version = range_match.group(1)
+ range_major = int(range_match.group(2))
+
+ range_min = LooseVersion(range_version)
+ range_max = LooseVersion("%d.0.0" % (range_major + 1))
+
+ return range_min <= version < range_max
+
+ return False
+
+
+def get_possible_node_paths_win():
+ """
+ Return possible nodejs paths on Windows.
+ """
+ if platform.system() != "Windows":
+ return []
+
+ return list({
+ "%s\\nodejs" % os.environ.get("SystemDrive"),
+ os.path.join(os.environ.get("ProgramFiles"), "nodejs"),
+ os.path.join(os.environ.get("PROGRAMW6432"), "nodejs"),
+ os.path.join(os.environ.get("PROGRAMFILES"), "nodejs")
+ })
+
+
+def get_node_or_npm_path(filename, minversion=None):
+ """
+ Return the nodejs or npm path.
+ """
+ if platform.system() == "Windows":
+ for ext in [".cmd", ".exe", ""]:
+ try:
+ node_or_npm_path = which.which(filename + ext,
+ path=get_possible_node_paths_win())
+ if is_valid(node_or_npm_path, minversion):
+ return node_or_npm_path
+ except which.WhichError:
+ pass
+ else:
+ try:
+ node_or_npm_path = which.which(filename)
+ if is_valid(node_or_npm_path, minversion):
+ return node_or_npm_path
+ except which.WhichError:
+ pass
+
+ if filename == "node":
+ print(NODE_NOT_FOUND_MESSAGE)
+ elif filename == "npm":
+ print(NPM_NOT_FOUND_MESSAGE)
+
+ if platform.system() == "Windows":
+ app_paths = get_possible_node_paths_win()
+
+ for p in app_paths:
+ print(" - %s" % p)
+ elif platform.system() == "Darwin":
+ print(" - /usr/local/bin/node")
+ elif platform.system() == "Linux":
+ print(" - /usr/bin/nodejs")
+
+ return None
+
+
+def is_valid(path, minversion=None):
+ try:
+ version_str = subprocess.check_output([path, "--version"],
+ stderr=subprocess.STDOUT)
+ if minversion:
+ # nodejs prefixes its version strings with "v"
+ version = LooseVersion(version_str.lstrip('v'))
+ return version >= minversion
+ return True
+ except (subprocess.CalledProcessError, OSError):
+ return False
+
+
+def get_project_root():
+ global project_root
+ return project_root
+
+
+def get_eslint_module_path():
+ return os.path.join(get_project_root(), "tools", "lint", "eslint")
+
+
+def lint(paths, binary=None, fix=None, setup=None, **lintargs):
+ """Run eslint."""
+ global project_root
+ project_root = lintargs['root']
+
+ module_path = get_eslint_module_path()
+
+ # eslint requires at least node 4.2.3
+ node_path = get_node_or_npm_path("node", LooseVersion("4.2.3"))
+ if not node_path:
+ return 1
+
+ if setup:
+ return eslint_setup()
+
+ npm_path = get_node_or_npm_path("npm")
+ if not npm_path:
+ return 1
+
+ if eslint_module_has_issues():
+ eslint_setup()
+
+ # Valid binaries are:
+ # - Any provided by the binary argument.
+ # - Any pointed at by the ESLINT environmental variable.
+ # - Those provided by mach eslint --setup.
+ #
+ # eslint --setup installs some mozilla specific plugins and installs
+ # all node modules locally. This is the preferred method of
+ # installation.
+
+ if not binary:
+ binary = os.environ.get('ESLINT', None)
+
+ if not binary:
+ binary = os.path.join(module_path, "node_modules", ".bin", "eslint")
+ if not os.path.isfile(binary):
+ binary = None
+
+ if not binary:
+ print(ESLINT_NOT_FOUND_MESSAGE)
+ return 1
+
+ extra_args = lintargs.get('extra_args') or []
+ cmd_args = [binary,
+ # Enable the HTML plugin.
+ # We can't currently enable this in the global config file
+ # because it has bad interactions with the SublimeText
+ # ESLint plugin (bug 1229874).
+ '--plugin', 'html',
+ # This keeps ext as a single argument.
+ '--ext', '[{}]'.format(','.join(EXTENSIONS)),
+ '--format', 'json',
+ ] + extra_args + paths
+
+ # eslint requires that --fix be set before the --ext argument.
+ if fix:
+ cmd_args.insert(1, '--fix')
+
+ shell = False
+ if os.environ.get('MSYSTEM') in ('MINGW32', 'MINGW64'):
+ # The eslint binary needs to be run from a shell with msys
+ shell = True
+
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ proc = ProcessHandler(cmd_args, env=os.environ, stream=None, shell=shell)
+ proc.run()
+ signal.signal(signal.SIGINT, orig)
+
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+ return []
+
+ if not proc.output:
+ return [] # no output means success
+
+ try:
+ jsonresult = json.loads(proc.output[0])
+ except ValueError:
+ print(ESLINT_ERROR_MESSAGE.format("\n".join(proc.output)))
+ return 1
+
+ results = []
+ for obj in jsonresult:
+ errors = obj['messages']
+
+ for err in errors:
+ err.update({
+ 'hint': err.get('fix'),
+ 'level': 'error' if err['severity'] == 2 else 'warning',
+ 'lineno': err.get('line'),
+ 'path': obj['filePath'],
+ 'rule': err.get('ruleId'),
+ })
+ results.append(result.from_linter(LINTER, **err))
+
+ return results
+
+
+LINTER = {
+ 'name': "eslint",
+ 'description': "JavaScript linter",
+ # ESLint infra handles its own path filtering, so just include cwd
+ 'include': ['.'],
+ 'exclude': [],
+ 'extensions': EXTENSIONS,
+ 'type': 'external',
+ 'payload': lint,
+}
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/LICENSE b/tools/lint/eslint/eslint-plugin-mozilla/LICENSE
new file mode 100644
index 000000000..e87a115e4
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/LICENSE
@@ -0,0 +1,363 @@
+Mozilla Public License, version 2.0
+
+1. Definitions
+
+1.1. "Contributor"
+
+ means each individual or legal entity that creates, contributes to the
+ creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+
+ means the combination of the Contributions of others (if any) used by a
+ Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+
+ means Source Code Form to which the initial Contributor has attached the
+ notice in Exhibit A, the Executable Form of such Source Code Form, and
+ Modifications of such Source Code Form, in each case including portions
+ thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ a. that the initial Contributor has attached the notice described in
+ Exhibit B to the Covered Software; or
+
+ b. that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the terms of
+ a Secondary License.
+
+1.6. "Executable Form"
+
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+
+ means a work that combines Covered Software with other material, in a
+ separate file or files, that is not Covered Software.
+
+1.8. "License"
+
+ means this document.
+
+1.9. "Licensable"
+
+ means having the right to grant, to the maximum extent possible, whether
+ at the time of the initial grant or subsequently, any and all of the
+ rights conveyed by this License.
+
+1.10. "Modifications"
+
+ means any of the following:
+
+ a. any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered Software; or
+
+ b. any new file in Source Code Form that contains any Covered Software.
+
+1.11. "Patent Claims" of a Contributor
+
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the License,
+ by the making, using, selling, offering for sale, having made, import,
+ or transfer of either its Contributions or its Contributor Version.
+
+1.12. "Secondary License"
+
+ means either the GNU General Public License, Version 2.0, the GNU Lesser
+ General Public License, Version 2.1, the GNU Affero General Public
+ License, Version 3.0, or any later versions of those licenses.
+
+1.13. "Source Code Form"
+
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that controls, is
+ controlled by, or is under common control with You. For purposes of this
+ definition, "control" means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by contract or
+ otherwise, or (b) ownership of more than fifty percent (50%) of the
+ outstanding shares or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+2.1. Grants
+
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ a. under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+ b. under Patent Claims of such Contributor to make, use, sell, offer for
+ sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+ The licenses granted in Section 2.1 with respect to any Contribution
+ become effective for each Contribution on the date the Contributor first
+ distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+ The licenses granted in this Section 2 are the only rights granted under
+ this License. No additional rights or licenses will be implied from the
+ distribution or licensing of Covered Software under this License.
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
+ Contributor:
+
+ a. for any code that a Contributor has removed from Covered Software; or
+
+ b. for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+ c. under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+ This License does not grant any rights in the trademarks, service marks,
+ or logos of any Contributor (except as may be necessary to comply with
+ the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this
+ License (see Section 10.2) or under the terms of a Secondary License (if
+ permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+ Each Contributor represents that the Contributor believes its
+ Contributions are its original creation(s) or it has sufficient rights to
+ grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+ This License is not intended to limit any rights You have under
+ applicable copyright doctrines of fair use, fair dealing, or other
+ equivalents.
+
+2.7. Conditions
+
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
+ Section 2.1.
+
+
+3. Responsibilities
+
+3.1. Distribution of Source Form
+
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under
+ the terms of this License. You must inform recipients that the Source
+ Code Form of the Covered Software is governed by the terms of this
+ License, and how they can obtain a copy of this License. You may not
+ attempt to alter or restrict the recipients' rights in the Source Code
+ Form.
+
+3.2. Distribution of Executable Form
+
+ If You distribute Covered Software in Executable Form then:
+
+ a. such Covered Software must also be made available in Source Code Form,
+ as described in Section 3.1, and You must inform recipients of the
+ Executable Form how they can obtain a copy of such Source Code Form by
+ reasonable means in a timely manner, at a charge no more than the cost
+ of distribution to the recipient; and
+
+ b. You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter the
+ recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for
+ the Covered Software. If the Larger Work is a combination of Covered
+ Software with a work governed by one or more Secondary Licenses, and the
+ Covered Software is not Incompatible With Secondary Licenses, this
+ License permits You to additionally distribute such Covered Software
+ under the terms of such Secondary License(s), so that the recipient of
+ the Larger Work may, at their option, further distribute the Covered
+ Software under the terms of either this License or such Secondary
+ License(s).
+
+3.4. Notices
+
+ You may not remove or alter the substance of any license notices
+ (including copyright notices, patent notices, disclaimers of warranty, or
+ limitations of liability) contained within the Source Code Form of the
+ Covered Software, except that You may alter any license notices to the
+ extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on
+ behalf of any Contributor. You must make it absolutely clear that any
+ such warranty, support, indemnity, or liability obligation is offered by
+ You alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+
+ If it is impossible for You to comply with any of the terms of this License
+ with respect to some or all of the Covered Software due to statute,
+ judicial order, or regulation then You must: (a) comply with the terms of
+ this License to the maximum extent possible; and (b) describe the
+ limitations and the code they affect. Such description must be placed in a
+ text file included with all distributions of the Covered Software under
+ this License. Except to the extent prohibited by statute or regulation,
+ such description must be sufficiently detailed for a recipient of ordinary
+ skill to be able to understand it.
+
+5. Termination
+
+5.1. The rights granted under this License will terminate automatically if You
+ fail to comply with any of its terms. However, if You become compliant,
+ then the rights granted under this License from a particular Contributor
+ are reinstated (a) provisionally, unless and until such Contributor
+ explicitly and finally terminates Your grants, and (b) on an ongoing
+ basis, if such Contributor fails to notify You of the non-compliance by
+ some reasonable means prior to 60 days after You have come back into
+ compliance. Moreover, Your grants from a particular Contributor are
+ reinstated on an ongoing basis if such Contributor notifies You of the
+ non-compliance by some reasonable means, this is the first time You have
+ received notice of non-compliance with this License from such
+ Contributor, and You become compliant prior to 30 days after Your receipt
+ of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions,
+ counter-claims, and cross-claims) alleging that a Contributor Version
+ directly or indirectly infringes any patent, then the rights granted to
+ You by any and all Contributors for the Covered Software under Section
+ 2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
+ license agreements (excluding distributors and resellers) which have been
+ validly granted by You or Your distributors under this License prior to
+ termination shall survive termination.
+
+6. Disclaimer of Warranty
+
+ Covered Software is provided under this License on an "as is" basis,
+ without warranty of any kind, either expressed, implied, or statutory,
+ including, without limitation, warranties that the Covered Software is free
+ of defects, merchantable, fit for a particular purpose or non-infringing.
+ The entire risk as to the quality and performance of the Covered Software
+ is with You. Should any Covered Software prove defective in any respect,
+ You (not any Contributor) assume the cost of any necessary servicing,
+ repair, or correction. This disclaimer of warranty constitutes an essential
+ part of this License. No use of any Covered Software is authorized under
+ this License except under this disclaimer.
+
+7. Limitation of Liability
+
+ Under no circumstances and under no legal theory, whether tort (including
+ negligence), contract, or otherwise, shall any Contributor, or anyone who
+ distributes Covered Software as permitted above, be liable to You for any
+ direct, indirect, special, incidental, or consequential damages of any
+ character including, without limitation, damages for lost profits, loss of
+ goodwill, work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses, even if such party shall have been
+ informed of the possibility of such damages. This limitation of liability
+ shall not apply to liability for death or personal injury resulting from
+ such party's negligence to the extent applicable law prohibits such
+ limitation. Some jurisdictions do not allow the exclusion or limitation of
+ incidental or consequential damages, so this exclusion and limitation may
+ not apply to You.
+
+8. Litigation
+
+ Any litigation relating to this License may be brought only in the courts
+ of a jurisdiction where the defendant maintains its principal place of
+ business and such litigation shall be governed by laws of that
+ jurisdiction, without reference to its conflict-of-law provisions. Nothing
+ in this Section shall prevent a party's ability to bring cross-claims or
+ counter-claims.
+
+9. Miscellaneous
+
+ This License represents the complete agreement concerning the subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. Any law or regulation which provides that
+ the language of a contract shall be construed against the drafter shall not
+ be used to construe this License against a Contributor.
+
+
+10. Versions of the License
+
+10.1. New Versions
+
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+
+10.2. Effect of New Versions
+
+ You may distribute the Covered Software under the terms of the version
+ of the License under which You originally received the Covered Software,
+ or under the terms of any subsequent version published by the license
+ steward.
+
+10.3. Modified Versions
+
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a
+ modified version of this License if you rename the license and remove
+ any references to the name of the license steward (except to note that
+ such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+ Licenses If You choose to distribute Source Code Form that is
+ Incompatible With Secondary Licenses under the terms of this version of
+ the License, the notice described in Exhibit B of this License must be
+ attached.
+
+Exhibit A - Source Code Form License Notice
+
+ This Source Code Form is subject to the
+ terms of the Mozilla Public License, v.
+ 2.0. If a copy of the MPL was not
+ distributed with this file, You can
+ obtain one at
+ http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular file,
+then You may include the notice in a location (such as a LICENSE file in a
+relevant directory) where a recipient would be likely to look for such a
+notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+
+ This Source Code Form is "Incompatible
+ With Secondary Licenses", as defined by
+ the Mozilla Public License, v. 2.0.
+
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
new file mode 100644
index 000000000..acbfa0684
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
@@ -0,0 +1,188 @@
+/**
+ * @fileoverview functions for scanning an AST for globals including
+ * traversing referenced scripts.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const path = require("path");
+const fs = require("fs");
+const helpers = require("./helpers");
+const escope = require("escope");
+const estraverse = require("estraverse");
+
+/**
+ * Parses a list of "name:boolean_value" or/and "name" options divided by comma or
+ * whitespace.
+ *
+ * This function was copied from eslint.js
+ *
+ * @param {string} string The string to parse.
+ * @param {Comment} comment The comment node which has the string.
+ * @returns {Object} Result map object of names and boolean values
+ */
+function parseBooleanConfig(string, comment) {
+ let items = {};
+
+ // Collapse whitespace around : to make parsing easier
+ string = string.replace(/\s*:\s*/g, ":");
+ // Collapse whitespace around ,
+ string = string.replace(/\s*,\s*/g, ",");
+
+ string.split(/\s|,+/).forEach(function(name) {
+ if (!name) {
+ return;
+ }
+
+ let pos = name.indexOf(":");
+ let value = undefined;
+ if (pos !== -1) {
+ value = name.substring(pos + 1, name.length);
+ name = name.substring(0, pos);
+ }
+
+ items[name] = {
+ value: (value === "true"),
+ comment: comment
+ };
+ });
+
+ return items;
+}
+
+/**
+ * Global discovery can require parsing many files. This map of
+ * {String} => {Object} caches what globals were discovered for a file path.
+ */
+const globalCache = new Map();
+
+/**
+ * An object that returns found globals for given AST node types. Each prototype
+ * property should be named for a node type and accepts a node parameter and a
+ * parents parameter which is a list of the parent nodes of the current node.
+ * Each returns an array of globals found.
+ *
+ * @param {String} path
+ * The absolute path of the file being parsed.
+ */
+function GlobalsForNode(path) {
+ this.path = path;
+ this.root = helpers.getRootDir(path);
+}
+
+GlobalsForNode.prototype = {
+ BlockComment(node, parents) {
+ let value = node.value.trim();
+ let match = /^import-globals-from\s+(.+)$/.exec(value);
+ if (!match) {
+ return [];
+ }
+
+ let filePath = match[1].trim();
+
+ if (!path.isAbsolute(filePath)) {
+ let dirName = path.dirname(this.path);
+ filePath = path.resolve(dirName, filePath);
+ }
+
+ return module.exports.getGlobalsForFile(filePath);
+ },
+
+ ExpressionStatement(node, parents) {
+ let isGlobal = helpers.getIsGlobalScope(parents);
+ let names = helpers.convertExpressionToGlobals(node, isGlobal, this.root);
+ return names.map(name => { return { name, writable: true }});
+ },
+};
+
+module.exports = {
+ /**
+ * Returns all globals for a given file. Recursively searches through
+ * import-globals-from directives and also includes globals defined by
+ * standard eslint directives.
+ *
+ * @param {String} path
+ * The absolute path of the file to be parsed.
+ */
+ getGlobalsForFile(path) {
+ if (globalCache.has(path)) {
+ return globalCache.get(path);
+ }
+
+ let content = fs.readFileSync(path, "utf8");
+
+ // Parse the content into an AST
+ let ast = helpers.getAST(content);
+
+ // Discover global declarations
+ let scopeManager = escope.analyze(ast);
+ let globalScope = scopeManager.acquire(ast);
+
+ let globals = Object.keys(globalScope.variables).map(v => ({
+ name: globalScope.variables[v].name,
+ writable: true,
+ }));
+
+ // Walk over the AST to find any of our custom globals
+ let handler = new GlobalsForNode(path);
+
+ helpers.walkAST(ast, (type, node, parents) => {
+ // We have to discover any globals that ESLint would have defined through
+ // comment directives
+ if (type == "BlockComment") {
+ let value = node.value.trim();
+ let match = /^globals?\s+(.+)$/.exec(value);
+ if (match) {
+ let values = parseBooleanConfig(match[1].trim(), node);
+ for (let name of Object.keys(values)) {
+ globals.push({
+ name,
+ writable: values[name].value
+ })
+ }
+ }
+ }
+
+ if (type in handler) {
+ let newGlobals = handler[type](node, parents);
+ globals.push.apply(globals, newGlobals);
+ }
+ });
+
+ globalCache.set(path, globals);
+
+ return globals;
+ },
+
+ /**
+ * Intended to be used as-is for an ESLint rule that parses for globals in
+ * the current file and recurses through import-globals-from directives.
+ *
+ * @param {Object} context
+ * The ESLint parsing context.
+ */
+ getESLintGlobalParser(context) {
+ let globalScope;
+
+ let parser = {
+ Program(node) {
+ globalScope = context.getScope();
+ }
+ };
+
+ // Install thin wrappers around GlobalsForNode
+ let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context));
+
+ for (let type of Object.keys(GlobalsForNode.prototype)) {
+ parser[type] = function(node) {
+ let globals = handler[type](node, context.getAncestors());
+ helpers.addGlobals(globals, globalScope);
+ }
+ }
+
+ return parser;
+ }
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
new file mode 100644
index 000000000..50e00ab97
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
@@ -0,0 +1,524 @@
+/**
+ * @fileoverview A collection of helper functions.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+"use strict";
+
+var escope = require("escope");
+var espree = require("espree");
+var estraverse = require("estraverse");
+var path = require("path");
+var fs = require("fs");
+var ini = require("ini-parser");
+
+var modules = null;
+var directoryManifests = new Map();
+
+var definitions = [
+ /^loader\.lazyGetter\(this, "(\w+)"/,
+ /^loader\.lazyImporter\(this, "(\w+)"/,
+ /^loader\.lazyServiceGetter\(this, "(\w+)"/,
+ /^loader\.lazyRequireGetter\(this, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyGetter\(this, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyServiceGetter\(this, "(\w+)"/,
+ /^XPCOMUtils\.defineConstant\(this, "(\w+)"/,
+ /^DevToolsUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
+ /^DevToolsUtils\.defineLazyGetter\(this, "(\w+)"/,
+ /^Object\.defineProperty\(this, "(\w+)"/,
+ /^Reflect\.defineProperty\(this, "(\w+)"/,
+ /^this\.__defineGetter__\("(\w+)"/,
+ /^this\.(\w+) =/
+];
+
+var imports = [
+ /^(?:Cu|Components\.utils)\.import\(".*\/((.*?)\.jsm?)"(?:, this)?\)/,
+];
+
+module.exports = {
+ /**
+ * Gets the abstract syntax tree (AST) of the JavaScript source code contained
+ * in sourceText.
+ *
+ * @param {String} sourceText
+ * Text containing valid JavaScript.
+ *
+ * @return {Object}
+ * The resulting AST.
+ */
+ getAST: function(sourceText) {
+ // Use a permissive config file to allow parsing of anything that Espree
+ // can parse.
+ var config = this.getPermissiveConfig();
+
+ return espree.parse(sourceText, config);
+ },
+
+ /**
+ * A simplistic conversion of some AST nodes to a standard string form.
+ *
+ * @param {Object} node
+ * The AST node to convert.
+ *
+ * @return {String}
+ * The JS source for the node.
+ */
+ getASTSource: function(node) {
+ switch (node.type) {
+ case "MemberExpression":
+ if (node.computed)
+ throw new Error("getASTSource unsupported computed MemberExpression");
+ return this.getASTSource(node.object) + "." + this.getASTSource(node.property);
+ case "ThisExpression":
+ return "this";
+ case "Identifier":
+ return node.name;
+ case "Literal":
+ return JSON.stringify(node.value);
+ case "CallExpression":
+ var args = node.arguments.map(a => this.getASTSource(a)).join(", ");
+ return this.getASTSource(node.callee) + "(" + args + ")";
+ case "ObjectExpression":
+ return "{}";
+ case "ExpressionStatement":
+ return this.getASTSource(node.expression) + ";";
+ case "FunctionExpression":
+ return "function() {}";
+ case "ArrowFunctionExpression":
+ return "() => {}";
+ case "AssignmentExpression":
+ return this.getASTSource(node.left) + " = " + this.getASTSource(node.right);
+ default:
+ throw new Error("getASTSource unsupported node type: " + node.type);
+ }
+ },
+
+ /**
+ * This walks an AST in a manner similar to ESLint passing node and comment
+ * events to the listener. The listener is expected to be a simple function
+ * which accepts node type, node and parents arguments.
+ *
+ * @param {Object} ast
+ * The AST to walk.
+ * @param {Function} listener
+ * A callback function to call for the nodes. Passed three arguments,
+ * event type, node and an array of parent nodes for the current node.
+ */
+ walkAST(ast, listener) {
+ let parents = [];
+
+ let seenComments = new Set();
+ function sendCommentEvents(comments) {
+ if (!comments) {
+ return;
+ }
+
+ for (let comment of comments) {
+ if (seenComments.has(comment)) {
+ return;
+ }
+ seenComments.add(comment);
+
+ listener(comment.type + "Comment", comment, parents);
+ }
+ }
+
+ estraverse.traverse(ast, {
+ enter(node, parent) {
+ // Comments are held in node.comments for empty programs
+ let leadingComments = node.leadingComments;
+ if (node.type === "Program" && node.body.length == 0) {
+ leadingComments = node.comments;
+ }
+
+ sendCommentEvents(leadingComments);
+ listener(node.type, node, parents);
+ sendCommentEvents(node.trailingComments);
+
+ parents.push(node);
+ },
+
+ leave(node, parent) {
+ // TODO send comment exit events
+ listener(node.type + ":exit", node, parents);
+
+ if (parents.length == 0) {
+ throw new Error("Left more nodes than entered.");
+ }
+ parents.pop();
+ }
+ });
+ if (parents.length) {
+ throw new Error("Entered more nodes than left.");
+ }
+ },
+
+ /**
+ * Attempts to convert an ExpressionStatement to likely global variable
+ * definitions.
+ *
+ * @param {Object} node
+ * The AST node to convert.
+ * @param {boolean} isGlobal
+ * True if the current node is in the global scope.
+ * @param {String} repository
+ * The root of the repository.
+ *
+ * @return {Array}
+ * An array of variable names defined.
+ */
+ convertExpressionToGlobals: function(node, isGlobal, repository) {
+ if (!modules) {
+ modules = require(path.join(repository, "tools", "lint", "eslint", "modules.json"));
+ }
+
+ try {
+ var source = this.getASTSource(node);
+ }
+ catch (e) {
+ return [];
+ }
+
+ for (var reg of definitions) {
+ var match = source.match(reg);
+ if (match) {
+ // Must be in the global scope
+ if (!isGlobal) {
+ return [];
+ }
+
+ return [match[1]];
+ }
+ }
+
+ for (reg of imports) {
+ var match = source.match(reg);
+ if (match) {
+ // The two argument form is only acceptable in the global scope
+ if (node.expression.arguments.length > 1 && !isGlobal) {
+ return [];
+ }
+
+ if (match[1] in modules) {
+ return modules[match[1]];
+ }
+
+ return [match[2]];
+ }
+ }
+
+ return [];
+ },
+
+ /**
+ * Add a variable to the current scope.
+ * HACK: This relies on eslint internals so it could break at any time.
+ *
+ * @param {String} name
+ * The variable name to add to the scope.
+ * @param {ASTScope} scope
+ * The scope to add to.
+ * @param {boolean} writable
+ * Whether the global can be overwritten.
+ */
+ addVarToScope: function(name, scope, writable) {
+ scope.__defineGeneric(name, scope.set, scope.variables, null, null);
+
+ let variable = scope.set.get(name);
+ variable.eslintExplicitGlobal = false;
+ variable.writeable = writable;
+
+ // Walk to the global scope which holds all undeclared variables.
+ while (scope.type != "global") {
+ scope = scope.upper;
+ }
+
+ // "through" contains all references with no found definition.
+ scope.through = scope.through.filter(function(reference) {
+ if (reference.identifier.name != name) {
+ return true;
+ }
+
+ // Links the variable and the reference.
+ // And this reference is removed from `Scope#through`.
+ reference.resolved = variable;
+ variable.references.push(reference);
+ return false;
+ });
+ },
+
+ /**
+ * Adds a set of globals to a scope.
+ *
+ * @param {Array} globalVars
+ * An array of global variable names.
+ * @param {ASTScope} scope
+ * The scope.
+ */
+ addGlobals: function(globalVars, scope) {
+ globalVars.forEach(v => this.addVarToScope(v.name, scope, v.writable));
+ },
+
+ /**
+ * To allow espree to parse almost any JavaScript we need as many features as
+ * possible turned on. This method returns that config.
+ *
+ * @return {Object}
+ * Espree compatible permissive config.
+ */
+ getPermissiveConfig: function() {
+ return {
+ range: true,
+ loc: true,
+ comment: true,
+ attachComment: true,
+ ecmaVersion: 8,
+ sourceType: "script",
+ ecmaFeatures: {
+ experimentalObjectRestSpread: true,
+ globalReturn: true,
+ }
+ };
+ },
+
+ /**
+ * Check whether the context is the global scope.
+ *
+ * @param {Array} ancestors
+ * The parents of the current node.
+ *
+ * @return {Boolean}
+ * True or false
+ */
+ getIsGlobalScope: function(ancestors) {
+ for (let parent of ancestors) {
+ if (parent.type == "FunctionExpression" ||
+ parent.type == "FunctionDeclaration") {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Check whether we might be in a test head file.
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsHeadFile(this)
+ *
+ * @return {Boolean}
+ * True or false
+ */
+ getIsHeadFile: function(scope) {
+ var pathAndFilename = this.cleanUpPath(scope.getFilename());
+
+ return /.*[\\/]head(_.+)?\.js$/.test(pathAndFilename);
+ },
+
+ /**
+ * Gets the head files for a potential test file
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsHeadFile(this)
+ *
+ * @return {String[]}
+ * Paths to head files to load for the test
+ */
+ getTestHeadFiles: function(scope) {
+ if (!this.getIsTest(scope)) {
+ return [];
+ }
+
+ let filepath = this.cleanUpPath(scope.getFilename());
+ let dir = path.dirname(filepath);
+
+ let names = fs.readdirSync(dir)
+ .filter(name => name.startsWith("head") && name.endsWith(".js"))
+ .map(name => path.join(dir, name));
+ return names;
+ },
+
+ /**
+ * Gets all the test manifest data for a directory
+ *
+ * @param {String} dir
+ * The directory
+ *
+ * @return {Array}
+ * An array of objects with file and manifest properties
+ */
+ getManifestsForDirectory: function(dir) {
+ if (directoryManifests.has(dir)) {
+ return directoryManifests.get(dir);
+ }
+
+ let manifests = [];
+
+ let names = fs.readdirSync(dir);
+ for (let name of names) {
+ if (!name.endsWith(".ini")) {
+ continue;
+ }
+
+ try {
+ let manifest = ini.parse(fs.readFileSync(path.join(dir, name), 'utf8'));
+
+ manifests.push({
+ file: path.join(dir, name),
+ manifest
+ })
+ } catch (e) {
+ }
+ }
+
+ directoryManifests.set(dir, manifests);
+ return manifests;
+ },
+
+ /**
+ * Gets the manifest file a test is listed in
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsHeadFile(this)
+ *
+ * @return {String}
+ * The path to the test manifest file
+ */
+ getTestManifest: function(scope) {
+ let filepath = this.cleanUpPath(scope.getFilename());
+
+ let dir = path.dirname(filepath);
+ let filename = path.basename(filepath);
+
+ for (let manifest of this.getManifestsForDirectory(dir)) {
+ if (filename in manifest.manifest) {
+ return manifest.file;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Check whether we are in a test of some kind.
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsTest(this)
+ *
+ * @return {Boolean}
+ * True or false
+ */
+ getIsTest: function(scope) {
+ // Regardless of the manifest name being in a manifest means we're a test.
+ let manifest = this.getTestManifest(scope);
+ if (manifest) {
+ return true;
+ }
+
+ return !!this.getTestType(scope);
+ },
+
+ /**
+ * Gets the type of test or null if this isn't a test.
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsHeadFile(this)
+ *
+ * @return {String or null}
+ * Test type: xpcshell, browser, chrome, mochitest
+ */
+ getTestType: function(scope) {
+ let manifest = this.getTestManifest(scope);
+ if (manifest) {
+ let name = path.basename(manifest);
+ for (let testType of ["browser", "xpcshell", "chrome", "mochitest"]) {
+ if (name.startsWith(testType)) {
+ return testType;
+ }
+ }
+ }
+
+ let filepath = this.cleanUpPath(scope.getFilename());
+ let filename = path.basename(filepath);
+
+ if (filename.startsWith("browser_")) {
+ return "browser";
+ }
+
+ if (filename.startsWith("test_")) {
+ return "xpcshell";
+ }
+
+ return null;
+ },
+
+ /**
+ * Gets the root directory of the repository by walking up directories until
+ * a .eslintignore file is found.
+ * @param {String} fileName
+ * The absolute path of a file in the repository
+ *
+ * @return {String} The absolute path of the repository directory
+ */
+ getRootDir: function(fileName) {
+ var dirName = path.dirname(fileName);
+
+ while (dirName && !fs.existsSync(path.join(dirName, ".eslintignore"))) {
+ dirName = path.dirname(dirName);
+ }
+
+ if (!dirName) {
+ throw new Error("Unable to find root of repository");
+ }
+
+ return dirName;
+ },
+
+ /**
+ * ESLint may be executed from various places: from mach, at the root of the
+ * repository, or from a directory in the repository when, for instance,
+ * executed by a text editor's plugin.
+ * The value returned by context.getFileName() varies because of this.
+ * This helper function makes sure to return an absolute file path for the
+ * current context, by looking at process.cwd().
+ * @param {Context} context
+ * @return {String} The absolute path
+ */
+ getAbsoluteFilePath: function(context) {
+ var fileName = this.cleanUpPath(context.getFilename());
+ var cwd = process.cwd();
+
+ if (path.isAbsolute(fileName)) {
+ // Case 2: executed from the repo's root with mach:
+ // fileName: /path/to/mozilla/repo/a/b/c/d.js
+ // cwd: /path/to/mozilla/repo
+ return fileName;
+ } else if (path.basename(fileName) == fileName) {
+ // Case 1b: executed from a nested directory, fileName is the base name
+ // without any path info (happens in Atom with linter-eslint)
+ return path.join(cwd, fileName);
+ } else {
+ // Case 1: executed form in a nested directory, e.g. from a text editor:
+ // fileName: a/b/c/d.js
+ // cwd: /path/to/mozilla/repo/a/b/c
+ var dirName = path.dirname(fileName);
+ return cwd.slice(0, cwd.length - dirName.length) + fileName;
+ }
+ },
+
+ /**
+ * When ESLint is run from SublimeText, paths retrieved from
+ * context.getFileName contain leading and trailing double-quote characters.
+ * These characters need to be removed.
+ */
+ cleanUpPath: function(path) {
+ return path.replace(/^"/, "").replace(/"$/, "");
+ }
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
new file mode 100644
index 000000000..e1f694c36
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview A collection of rules that help enforce JavaScript coding
+ * standard and avoid common errors in the Mozilla project.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Plugin Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ processors: {
+ ".xml": require("../lib/processors/xbl-bindings"),
+ },
+ rules: {
+ "balanced-listeners": require("../lib/rules/balanced-listeners"),
+ "import-globals": require("../lib/rules/import-globals"),
+ "import-headjs-globals": require("../lib/rules/import-headjs-globals"),
+ "import-browserjs-globals": require("../lib/rules/import-browserjs-globals"),
+ "mark-test-function-used": require("../lib/rules/mark-test-function-used"),
+ "no-aArgs": require("../lib/rules/no-aArgs"),
+ "no-cpows-in-tests": require("../lib/rules/no-cpows-in-tests"),
+ "no-single-arg-cu-import": require("../lib/rules/no-single-arg-cu-import"),
+ "reject-importGlobalProperties": require("../lib/rules/reject-importGlobalProperties"),
+ "reject-some-requires": require("../lib/rules/reject-some-requires"),
+ "var-only-at-top-level": require("../lib/rules/var-only-at-top-level")
+ },
+ rulesConfig: {
+ "balanced-listeners": 0,
+ "import-globals": 0,
+ "import-headjs-globals": 0,
+ "import-browserjs-globals": 0,
+ "mark-test-function-used": 0,
+ "no-aArgs": 0,
+ "no-cpows-in-tests": 0,
+ "no-single-arg-cu-import": 0,
+ "reject-importGlobalProperties": 0,
+ "reject-some-requires": 0,
+ "var-only-at-top-level": 0
+ }
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js
new file mode 100644
index 000000000..dc09550f2
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js
@@ -0,0 +1,363 @@
+/**
+ * @fileoverview Converts functions and handlers from XBL bindings into JS
+ * functions
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const NS_XBL = "http://www.mozilla.org/xbl";
+
+let sax = require("sax");
+
+// Converts sax's error message to something that eslint will understand
+let errorRegex = /(.*)\nLine: (\d+)\nColumn: (\d+)\nChar: (.*)/
+function parseError(err) {
+ let matches = err.message.match(errorRegex);
+ if (!matches)
+ return null;
+
+ return {
+ fatal: true,
+ message: matches[1],
+ line: parseInt(matches[2]) + 1,
+ column: parseInt(matches[3])
+ }
+}
+
+let entityRegex = /&[\w][\w-\.]*;/g;
+
+// A simple sax listener that generates a tree of element information
+function XMLParser(parser) {
+ this.parser = parser;
+ parser.onopentag = this.onOpenTag.bind(this);
+ parser.onclosetag = this.onCloseTag.bind(this);
+ parser.ontext = this.onText.bind(this);
+ parser.onopencdata = this.onOpenCDATA.bind(this);
+ parser.oncdata = this.onCDATA.bind(this);
+ parser.oncomment = this.onComment.bind(this);
+
+ this.document = {
+ local: "#document",
+ uri: null,
+ children: [],
+ comments: [],
+ }
+ this._currentNode = this.document;
+}
+
+XMLParser.prototype = {
+ parser: null,
+
+ onOpenTag: function(tag) {
+ let node = {
+ parentNode: this._currentNode,
+ local: tag.local,
+ namespace: tag.uri,
+ attributes: {},
+ children: [],
+ comments: [],
+ textContent: "",
+ textLine: this.parser.line,
+ textColumn: this.parser.column,
+ textEndLine: this.parser.line
+ }
+
+ for (let attr of Object.keys(tag.attributes)) {
+ if (tag.attributes[attr].uri == "") {
+ node.attributes[attr] = tag.attributes[attr].value;
+ }
+ }
+
+ this._currentNode.children.push(node);
+ this._currentNode = node;
+ },
+
+ onCloseTag: function(tagname) {
+ this._currentNode.textEndLine = this.parser.line;
+ this._currentNode = this._currentNode.parentNode;
+ },
+
+ addText: function(text) {
+ this._currentNode.textContent += text;
+ },
+
+ onText: function(text) {
+ // Replace entities with some valid JS token.
+ this.addText(text.replace(entityRegex, "null"));
+ },
+
+ onOpenCDATA: function() {
+ // Turn the CDATA opening tag into whitespace for indent alignment
+ this.addText(" ".repeat("<![CDATA[".length));
+ },
+
+ onCDATA: function(text) {
+ this.addText(text);
+ },
+
+ onComment: function(text) {
+ this._currentNode.comments.push(text);
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Processor Definition
+// -----------------------------------------------------------------------------
+
+const INDENT_LEVEL = 2;
+
+function indent(count) {
+ return " ".repeat(count * INDENT_LEVEL);
+}
+
+// Stores any XML parse error
+let xmlParseError = null;
+
+// Stores the lines of JS code generated from the XBL
+let scriptLines = [];
+// Stores a map from the synthetic line number to the real line number
+// and column offset.
+let lineMap = [];
+
+function addSyntheticLine(line, linePos, addDisableLine) {
+ lineMap[scriptLines.length] = { line: linePos, offset: null };
+ scriptLines.push(line + (addDisableLine ? "" : " // eslint-disable-line"));
+}
+
+/**
+ * Adds generated lines from an XBL node to the script to be passed back to eslint.
+ */
+function addNodeLines(node, reindent) {
+ let lines = node.textContent.split("\n");
+ let startLine = node.textLine;
+ let startColumn = node.textColumn;
+
+ // The case where there are no whitespace lines before the first text is
+ // treated differently for indentation
+ let indentFirst = false;
+
+ // Strip off any preceeding whitespace only lines. These are often used to
+ // format the XML and CDATA blocks.
+ while (lines.length && lines[0].trim() == "") {
+ indentFirst = true;
+ startLine++;
+ lines.shift();
+ }
+
+ // Strip off any whitespace lines at the end. These are often used to line
+ // up the closing tags
+ while (lines.length && lines[lines.length - 1].trim() == "") {
+ lines.pop();
+ }
+
+ if (!indentFirst) {
+ let firstLine = lines.shift();
+ firstLine = " ".repeat(reindent * INDENT_LEVEL) + firstLine;
+ // ESLint counts columns starting at 1 rather than 0
+ lineMap[scriptLines.length] = { line: startLine, offset: reindent * INDENT_LEVEL - (startColumn - 1) };
+ scriptLines.push(firstLine);
+ startLine++;
+ }
+
+ // Find the preceeding whitespace for all lines that aren't entirely whitespace
+ let indents = lines.filter(s => s.trim().length > 0)
+ .map(s => s.length - s.trimLeft().length);
+ // Find the smallest indent level in use
+ let minIndent = Math.min.apply(null, indents);
+
+ for (let line of lines) {
+ if (line.trim().length == 0) {
+ // Don't offset lines that are only whitespace, the only possible JS error
+ // is trailing whitespace and we want it to point at the right place
+ lineMap[scriptLines.length] = { line: startLine, offset: 0 };
+ } else {
+ line = " ".repeat(reindent * INDENT_LEVEL) + line.substring(minIndent);
+ lineMap[scriptLines.length] = { line: startLine, offset: reindent * INDENT_LEVEL - (minIndent - 1) };
+ }
+
+ scriptLines.push(line);
+ startLine++;
+ }
+}
+
+module.exports = {
+ preprocess: function(text, filename) {
+ xmlParseError = null;
+ scriptLines = [];
+ lineMap = [];
+
+ // Non-strict allows us to ignore many errors from entities and
+ // preprocessing at the expense of failing to report some XML errors.
+ // Unfortunately it also throws away the case of tagnames and attributes
+ let parser = sax.parser(false, {
+ lowercase: true,
+ xmlns: true,
+ });
+
+ parser.onerror = function(err) {
+ xmlParseError = parseError(err);
+ }
+
+ let xp = new XMLParser(parser);
+ parser.write(text);
+
+ // Sanity checks to make sure we're dealing with an XBL document
+ let document = xp.document;
+ if (document.children.length != 1) {
+ return [];
+ }
+
+ let bindings = document.children[0];
+ if (bindings.local != "bindings" || bindings.namespace != NS_XBL) {
+ return [];
+ }
+
+ for (let comment of document.comments) {
+ addSyntheticLine(`/*`, 0, true);
+ for (let line of comment.split("\n")) {
+ addSyntheticLine(`${line.trim()}`, 0, true);
+ }
+ addSyntheticLine(`*/`, 0, true);
+ }
+
+ addSyntheticLine(`this.bindings = {`, bindings.textLine);
+
+ for (let binding of bindings.children) {
+ if (binding.local != "binding" || binding.namespace != NS_XBL) {
+ continue;
+ }
+
+ addSyntheticLine(indent(1) + `"${binding.attributes.id}": {`, binding.textLine);
+
+ for (let part of binding.children) {
+ if (part.namespace != NS_XBL) {
+ continue;
+ }
+
+ if (part.local == "implementation") {
+ addSyntheticLine(indent(2) + `implementation: {`, part.textLine);
+ } else if (part.local == "handlers") {
+ addSyntheticLine(indent(2) + `handlers: [`, part.textLine);
+ } else {
+ continue;
+ }
+
+ for (let item of part.children) {
+ if (item.namespace != NS_XBL) {
+ continue;
+ }
+
+ switch (item.local) {
+ case "field": {
+ // Fields are something like lazy getter functions
+
+ // Ignore empty fields
+ if (item.textContent.trim().length == 0) {
+ continue;
+ }
+
+ addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, item.textLine);
+ addSyntheticLine(indent(4) + `return (`, item.textLine);
+
+ // Remove trailing semicolons, as we are adding our own
+ item.textContent = item.textContent.replace(/;(?=\s*$)/, "");
+ addNodeLines(item, 5);
+
+ addSyntheticLine(indent(4) + `);`, item.textLine);
+ addSyntheticLine(indent(3) + `},`, item.textEndLine);
+ break;
+ }
+ case "constructor":
+ case "destructor": {
+ // Constructors and destructors become function declarations
+ addSyntheticLine(indent(3) + `${item.local}() {`, item.textLine);
+ addNodeLines(item, 4);
+ addSyntheticLine(indent(3) + `},`, item.textEndLine);
+ break;
+ }
+ case "method": {
+ // Methods become function declarations with the appropriate params
+
+ let params = item.children.filter(n => n.local == "parameter" && n.namespace == NS_XBL)
+ .map(n => n.attributes.name)
+ .join(", ");
+ let body = item.children.filter(n => n.local == "body" && n.namespace == NS_XBL)[0];
+
+ addSyntheticLine(indent(3) + `${item.attributes.name}(${params}) {`, item.textLine);
+ addNodeLines(body, 4);
+ addSyntheticLine(indent(3) + `},`, item.textEndLine);
+ break;
+ }
+ case "property": {
+ // Properties become one or two function declarations
+ for (let propdef of item.children) {
+ if (propdef.namespace != NS_XBL) {
+ continue;
+ }
+
+ if (propdef.local == "setter") {
+ addSyntheticLine(indent(3) + `set ${item.attributes.name}(val) {`, propdef.textLine);
+ } else if (propdef.local == "getter") {
+ addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, propdef.textLine);
+ } else {
+ continue;
+ }
+ addNodeLines(propdef, 4);
+ addSyntheticLine(indent(3) + `},`, propdef.textEndLine);
+ }
+ break;
+ }
+ case "handler": {
+ // Handlers become a function declaration with an `event` parameter
+ addSyntheticLine(indent(3) + `function(event) {`, item.textLine);
+ addNodeLines(item, 4);
+ addSyntheticLine(indent(3) + `},`, item.textEndLine);
+ break;
+ }
+ default:
+ continue;
+ }
+ }
+
+ addSyntheticLine(indent(2) + (part.local == "implementation" ? `},` : `],`), part.textEndLine);
+ }
+ addSyntheticLine(indent(1) + `},`, binding.textEndLine);
+ }
+ addSyntheticLine(`};`, bindings.textEndLine);
+
+ let script = scriptLines.join("\n") + "\n";
+ return [script];
+ },
+
+ postprocess: function(messages, filename) {
+ // If there was an XML parse error then just return that
+ if (xmlParseError) {
+ return [xmlParseError];
+ }
+
+ // For every message from every script block update the line to point to the
+ // correct place.
+ let errors = [];
+ for (let i = 0; i < messages.length; i++) {
+ for (let message of messages[i]) {
+ // ESLint indexes lines starting at 1 but our arrays start at 0
+ let mapped = lineMap[message.line - 1];
+
+ message.line = mapped.line + 1;
+ if (mapped.offset) {
+ message.column -= mapped.offset;
+ } else {
+ message.column = NaN;
+ }
+
+ errors.push(message);
+ }
+ }
+
+ return errors;
+ }
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc.js
new file mode 100644
index 000000000..505a3ea82
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc.js
@@ -0,0 +1,51 @@
+"use strict";
+
+/**
+ * Based on npm coding standards at https://docs.npmjs.com/misc/coding-style.
+ *
+ * The places we differ from the npm coding style:
+ * - Commas should be at the end of a line.
+ * - Always use semicolons.
+ * - Functions should not have whitespace before params.
+ */
+
+module.exports = {
+ "env": {
+ "node": true
+ },
+
+ "rules": {
+ "brace-style": ["error", "1tbs"],
+ "camelcase": "error",
+ "comma-dangle": ["error", "never"],
+ "comma-spacing": "error",
+ "comma-style": ["error", "last"],
+ "curly": ["error", "multi-line"],
+ "handle-callback-err": ["error", "er"],
+ "indent": ["error", 2, {"SwitchCase": 1}],
+ "max-len": ["error", 80, "error"],
+ "no-multiple-empty-lines": ["error", {"max": 1}],
+ "no-undef": "error",
+ "no-undef-init": "error",
+ "no-unexpected-multiline": "error",
+ "object-curly-spacing": "off",
+ "one-var": ["error", "never"],
+ "operator-linebreak": ["error", "after"],
+ "semi": ["error", "always"],
+ "space-before-blocks": "error",
+ "space-before-function-paren": ["error", "never"],
+ "keyword-spacing": "error",
+ "strict": ["error", "global"],
+ },
+
+ // Globals accessible within node modules.
+ "globals": {
+ "DTRACE_HTTP_CLIENT_REQUEST": true,
+ "DTRACE_HTTP_CLIENT_RESPONSE": true,
+ "DTRACE_HTTP_SERVER_REQUEST": true,
+ "DTRACE_HTTP_SERVER_RESPONSE": true,
+ "DTRACE_NET_SERVER_CONNECTION": true,
+ "DTRACE_NET_STREAM_END": true,
+ "Intl": true,
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js
new file mode 100644
index 000000000..c658a6b44
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js
@@ -0,0 +1,113 @@
+/**
+ * @fileoverview Check that there's a removeEventListener for each
+ * addEventListener and an off for each on.
+ * Note that for now, this rule is rather simple in that it only checks that
+ * for each event name there is both an add and remove listener. It doesn't
+ * check that these are called on the right objects or with the same callback.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+module.exports = function(context) {
+ // ---------------------------------------------------------------------------
+ // Helpers
+ // ---------------------------------------------------------------------------
+
+ var DICTIONARY = {
+ "addEventListener": "removeEventListener",
+ "on": "off"
+ };
+ // Invert this dictionary to make it easy later.
+ var INVERTED_DICTIONARY = {};
+ for (var i in DICTIONARY) {
+ INVERTED_DICTIONARY[DICTIONARY[i]] = i;
+ }
+
+ // Collect the add/remove listeners in these 2 arrays.
+ var addedListeners = [];
+ var removedListeners = [];
+
+ function addAddedListener(node) {
+ addedListeners.push({
+ functionName: node.callee.property.name,
+ type: node.arguments[0].value,
+ node: node.callee.property,
+ useCapture: node.arguments[2] ? node.arguments[2].value : null
+ });
+ }
+
+ function addRemovedListener(node) {
+ removedListeners.push({
+ functionName: node.callee.property.name,
+ type: node.arguments[0].value,
+ useCapture: node.arguments[2] ? node.arguments[2].value : null
+ });
+ }
+
+ function getUnbalancedListeners() {
+ var unbalanced = [];
+
+ for (var j = 0; j < addedListeners.length; j++) {
+ if (!hasRemovedListener(addedListeners[j])) {
+ unbalanced.push(addedListeners[j]);
+ }
+ }
+ addedListeners = removedListeners = [];
+
+ return unbalanced;
+ }
+
+ function hasRemovedListener(addedListener) {
+ for (var k = 0; k < removedListeners.length; k++) {
+ var listener = removedListeners[k];
+ if (DICTIONARY[addedListener.functionName] === listener.functionName &&
+ addedListener.type === listener.type &&
+ addedListener.useCapture === listener.useCapture) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // ---------------------------------------------------------------------------
+ // Public
+ // ---------------------------------------------------------------------------
+
+ return {
+ CallExpression: function(node) {
+ if (node.arguments.length === 0) {
+ return;
+ }
+
+ if (node.callee.type === "MemberExpression") {
+ var listenerMethodName = node.callee.property.name;
+
+ if (DICTIONARY.hasOwnProperty(listenerMethodName)) {
+ addAddedListener(node);
+ } else if (INVERTED_DICTIONARY.hasOwnProperty(listenerMethodName)) {
+ addRemovedListener(node);
+ }
+ }
+ },
+
+ "Program:exit": function() {
+ getUnbalancedListeners().forEach(function(listener) {
+ context.report(listener.node,
+ "No corresponding '{{functionName}}({{type}})' was found.",
+ {
+ functionName: DICTIONARY[listener.functionName],
+ type: listener.type
+ });
+ });
+ }
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
new file mode 100644
index 000000000..313af2d71
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview Import globals from browser.js.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var fs = require("fs");
+var path = require("path");
+var helpers = require("../helpers");
+var globals = require("../globals");
+
+const SCRIPTS = [
+ //"browser/base/content/nsContextMenu.js",
+ "toolkit/content/contentAreaUtils.js",
+ "browser/components/places/content/editBookmarkOverlay.js",
+ "toolkit/components/printing/content/printUtils.js",
+ "toolkit/content/viewZoomOverlay.js",
+ "browser/components/places/content/browserPlacesViews.js",
+ "browser/base/content/browser.js",
+ "browser/components/downloads/content/downloads.js",
+ "browser/components/downloads/content/indicator.js",
+ "browser/components/customizableui/content/panelUI.js",
+ "toolkit/components/viewsource/content/viewSourceUtils.js",
+ "browser/base/content/browser-addons.js",
+ "browser/base/content/browser-ctrlTab.js",
+ "browser/base/content/browser-customization.js",
+ "browser/base/content/browser-devedition.js",
+ "browser/base/content/browser-feeds.js",
+ "browser/base/content/browser-fullScreenAndPointerLock.js",
+ "browser/base/content/browser-fullZoom.js",
+ "browser/base/content/browser-gestureSupport.js",
+ "browser/base/content/browser-media.js",
+ "browser/base/content/browser-places.js",
+ "browser/base/content/browser-plugins.js",
+ "browser/base/content/browser-refreshblocker.js",
+ "browser/base/content/browser-safebrowsing.js",
+ "browser/base/content/browser-sidebar.js",
+ "browser/base/content/browser-social.js",
+ "browser/base/content/browser-syncui.js",
+ "browser/base/content/browser-tabsintitlebar.js",
+ "browser/base/content/browser-thumbnails.js",
+ "browser/base/content/browser-trackingprotection.js",
+ "browser/base/content/browser-data-submission-info-bar.js",
+ "browser/base/content/browser-fxaccounts.js"
+];
+
+module.exports = function(context) {
+ return {
+ Program: function(node) {
+ if (helpers.getTestType(this) != "browser" &&
+ !helpers.getIsHeadFile(this)) {
+ return;
+ }
+
+ let filepath = helpers.getAbsoluteFilePath(context);
+ let root = helpers.getRootDir(filepath);
+ for (let script of SCRIPTS) {
+ let fileName = path.join(root, script);
+ try {
+ let newGlobals = globals.getGlobalsForFile(fileName);
+ helpers.addGlobals(newGlobals, context.getScope());
+ } catch (e) {
+ context.report(
+ node,
+ "Could not load globals from file {{filePath}}: {{error}}",
+ {
+ filePath: path.relative(root, fileName),
+ error: e
+ }
+ );
+ }
+ }
+ }
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js
new file mode 100644
index 000000000..053a9e702
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js
@@ -0,0 +1,15 @@
+/**
+ * @fileoverview Discovers all globals for the current file.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+module.exports = require("../globals").getESLintGlobalParser;
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
new file mode 100644
index 000000000..783642f58
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
@@ -0,0 +1,49 @@
+/**
+ * @fileoverview Import globals from head.js and from any files that were
+ * imported by head.js (as far as we can correctly resolve the path).
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var fs = require("fs");
+var path = require("path");
+var helpers = require("../helpers");
+var globals = require("../globals");
+
+module.exports = function(context) {
+
+ function importHead(path, node) {
+ try {
+ let stats = fs.statSync(path);
+ if (!stats.isFile()) {
+ return;
+ }
+ } catch (e) {
+ return;
+ }
+
+ let newGlobals = globals.getGlobalsForFile(path);
+ helpers.addGlobals(newGlobals, context.getScope());
+ }
+
+ // ---------------------------------------------------------------------------
+ // Public
+ // ---------------------------------------------------------------------------
+
+ return {
+ Program: function(node) {
+ let heads = helpers.getTestHeadFiles(this);
+ for (let head of heads) {
+ importHead(head, node);
+ }
+ }
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js
new file mode 100644
index 000000000..b2e8ec294
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Simply marks `test` (the test method) or `run_test` as used when
+ * in mochitests or xpcshell tests respectively. This avoids ESLint telling us
+ * that the function is never called.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var helpers = require("../helpers");
+
+module.exports = function(context) {
+ // ---------------------------------------------------------------------------
+ // Public
+ // ---------------------------------------------------------------------------
+
+ return {
+ Program: function() {
+ if (helpers.getTestType(this) == "browser") {
+ context.markVariableAsUsed("test");
+ return;
+ }
+
+ if (helpers.getTestType(this) == "xpcshell") {
+ context.markVariableAsUsed("run_test");
+ return;
+ }
+ }
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js
new file mode 100644
index 000000000..267f6836f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js
@@ -0,0 +1,55 @@
+/**
+ * @fileoverview warns against using hungarian notation in function arguments
+ * (i.e. aArg).
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+module.exports = function(context) {
+ // ---------------------------------------------------------------------------
+ // Helpers
+ // ---------------------------------------------------------------------------
+
+ function isPrefixed(name) {
+ return name.length >= 2 && /^a[A-Z]/.test(name);
+ }
+
+ function deHungarianize(name) {
+ return name.substring(1, 2).toLowerCase() +
+ name.substring(2, name.length);
+ }
+
+ function checkFunction(node) {
+ for (var i = 0; i < node.params.length; i++) {
+ var param = node.params[i];
+ if (param.name && isPrefixed(param.name)) {
+ var errorObj = {
+ name: param.name,
+ suggestion: deHungarianize(param.name)
+ };
+ context.report(param,
+ "Parameter '{{name}}' uses Hungarian Notation, " +
+ "consider using '{{suggestion}}' instead.",
+ errorObj);
+ }
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // Public
+ // ---------------------------------------------------------------------------
+
+ return {
+ "FunctionDeclaration": checkFunction,
+ "ArrowFunctionExpression": checkFunction,
+ "FunctionExpression": checkFunction
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js
new file mode 100644
index 000000000..415cb2fc9
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js
@@ -0,0 +1,112 @@
+/**
+ * @fileoverview Prevent access to CPOWs in browser mochitests.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var helpers = require("../helpers");
+
+var cpows = [
+ /^gBrowser\.contentWindow/,
+ /^gBrowser\.contentDocument/,
+ /^gBrowser\.selectedBrowser.contentWindow/,
+ /^browser\.contentDocument/,
+ /^window\.content/
+];
+
+var isInContentTask = false;
+
+module.exports = function(context) {
+ // ---------------------------------------------------------------------------
+ // Helpers
+ // ---------------------------------------------------------------------------
+
+ function showError(node, identifier) {
+ if (isInContentTask) {
+ return;
+ }
+
+ context.report({
+ node: node,
+ message: identifier +
+ " is a possible Cross Process Object Wrapper (CPOW)."
+ });
+ }
+
+ function isContentTask(node) {
+ return node &&
+ node.type === "MemberExpression" &&
+ node.property.type === "Identifier" &&
+ node.property.name === "spawn" &&
+ node.object.type === "Identifier" &&
+ node.object.name === "ContentTask";
+ }
+
+ // ---------------------------------------------------------------------------
+ // Public
+ // ---------------------------------------------------------------------------
+
+ return {
+ CallExpression: function(node) {
+ if (isContentTask(node.callee)) {
+ isInContentTask = true;
+ }
+ },
+
+ "CallExpression:exit": function(node) {
+ if (isContentTask(node.callee)) {
+ isInContentTask = false;
+ }
+ },
+
+ MemberExpression: function(node) {
+ if (helpers.getTestType(this) != "browser") {
+ return;
+ }
+
+ var expression = context.getSource(node);
+
+ // Only report a single CPOW error per node -- so if checking
+ // |cpows| reports one, don't report another below.
+ var someCpowFound = cpows.some(function(cpow) {
+ if (cpow.test(expression)) {
+ showError(node, expression);
+ return true;
+ }
+ return false;
+ });
+ if (!someCpowFound && helpers.getIsGlobalScope(context.getAncestors())) {
+ if (/^content\./.test(expression)) {
+ showError(node, expression);
+ return;
+ }
+ }
+ },
+
+ Identifier: function(node) {
+ if (helpers.getTestType(this) != "browser") {
+ return;
+ }
+
+ var expression = context.getSource(node);
+ if (expression == "content" || /^content\./.test(expression)) {
+ if (node.parent.type === "MemberExpression" &&
+ node.parent.object &&
+ node.parent.object.type === "Identifier" &&
+ node.parent.object.name != "content") {
+ return;
+ }
+ showError(node, expression);
+ return;
+ }
+ }
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js
new file mode 100644
index 000000000..b295f3555
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Reject use of single argument Cu.import
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var helpers = require("../helpers");
+
+module.exports = function(context) {
+
+ // ---------------------------------------------------------------------------
+ // Public
+ // --------------------------------------------------------------------------
+
+ return {
+ "CallExpression": function(node) {
+ if (node.callee.type === "MemberExpression") {
+ let memexp = node.callee;
+ if (memexp.object.type === "Identifier" &&
+ // Only Cu, not Components.utils; see bug 1230369.
+ memexp.object.name === "Cu" &&
+ memexp.property.type === "Identifier" &&
+ memexp.property.name === "import" &&
+ node.arguments.length === 1) {
+ context.report(node, "Single argument Cu.import exposes new " +
+ "globals to all modules");
+ }
+ }
+ }
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js
new file mode 100644
index 000000000..0661c91d4
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Reject use of Cu.importGlobalProperties
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var helpers = require("../helpers");
+
+module.exports = function(context) {
+
+ // ---------------------------------------------------------------------------
+ // Public
+ // --------------------------------------------------------------------------
+
+ return {
+ "CallExpression": function(node) {
+ if (node.callee.type === "MemberExpression") {
+ let memexp = node.callee;
+ if (memexp.object.type === "Identifier" &&
+ // Only Cu, not Components.utils; see bug 1230369.
+ memexp.object.name === "Cu" &&
+ memexp.property.type === "Identifier" &&
+ memexp.property.name === "importGlobalProperties") {
+ context.report(node, "Unexpected call to Cu.importGlobalProperties");
+ }
+ }
+ }
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js
new file mode 100644
index 000000000..746f98a1f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js
@@ -0,0 +1,48 @@
+/**
+ * @fileoverview Reject some uses of require.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ // ---------------------------------------------------------------------------
+ // Public
+ // --------------------------------------------------------------------------
+
+ if (typeof(context.options[0]) !== "string") {
+ throw new Error("reject-some-requires expects a regexp");
+ }
+ const RX = new RegExp(context.options[0]);
+
+ const checkPath = function(node, path) {
+ if (RX.test(path)) {
+ context.report(node, `require(${path}) is not allowed`);
+ }
+ };
+
+ return {
+ "CallExpression": function(node) {
+ if (node.callee.type == "Identifier" &&
+ node.callee.name == "require" &&
+ node.arguments.length == 1 &&
+ node.arguments[0].type == "Literal") {
+ checkPath(node, node.arguments[0].value);
+ } else if (node.callee.type == "MemberExpression" &&
+ node.callee.property.type == "Identifier" &&
+ node.callee.property.name == "lazyRequireGetter" &&
+ node.arguments.length >= 3 &&
+ node.arguments[2].type == "Literal") {
+ checkPath(node, node.arguments[2].value);
+ }
+ }
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
new file mode 100644
index 000000000..a1e14e166
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
@@ -0,0 +1,34 @@
+/**
+ * @fileoverview Marks all var declarations that are not at the top level
+ * invalid.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var helpers = require("../helpers");
+
+module.exports = function(context) {
+ // ---------------------------------------------------------------------------
+ // Public
+ // --------------------------------------------------------------------------
+
+ return {
+ "VariableDeclaration": function(node) {
+ if (node.kind === "var") {
+ if (helpers.getIsGlobalScope(context.getAncestors())) {
+ return;
+ }
+
+ context.report(node, "Unexpected var, use let or const instead.");
+ }
+ }
+ };
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/package.json b/tools/lint/eslint/eslint-plugin-mozilla/package.json
new file mode 100644
index 000000000..2f4a85172
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "eslint-plugin-mozilla",
+ "version": "0.2.5",
+ "description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.",
+ "keywords": [
+ "eslint",
+ "eslintplugin",
+ "eslint-plugin",
+ "mozilla",
+ "firefox"
+ ],
+ "bugs": {
+ "url": "https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=Developer%20Tools"
+ },
+ "homepage": "https://bugzilla.mozilla.org/show_bug.cgi?id=1203520",
+ "author": "Mike Ratcliffe",
+ "main": "lib/index.js",
+ "dependencies": {
+ "escope": "^3.6.0",
+ "espree": "^3.2.0",
+ "estraverse": "^4.2.0",
+ "ini-parser": "^0.0.2",
+ "sax": "^1.1.4"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "license": "MPL-2.0"
+}
diff --git a/tools/lint/eslint/manifest.tt b/tools/lint/eslint/manifest.tt
new file mode 100644
index 000000000..c98f6455f
--- /dev/null
+++ b/tools/lint/eslint/manifest.tt
@@ -0,0 +1,9 @@
+[
+{
+"size": 2768586,
+"visibility": "public",
+"digest": "1b9a7c5b1ca8d7d2aeb803055129d1cb0fa0d17b90dd3cf852ea77d86d1b1d9d09f34007a71908074afef9ce1ab87972a5cf16a36952e1a6159f7abfc6fa15b3",
+"algorithm": "sha512",
+"filename": "eslint.tar.gz"
+}
+]
diff --git a/tools/lint/eslint/modules.json b/tools/lint/eslint/modules.json
new file mode 100644
index 000000000..34ebfc6c1
--- /dev/null
+++ b/tools/lint/eslint/modules.json
@@ -0,0 +1,247 @@
+{
+ "AboutHome.jsm": ["AboutHomeUtils", "AboutHome"],
+ "AddonLogging.jsm": ["LogManager"],
+ "AddonManager.jsm": ["AddonManager", "AddonManagerPrivate"],
+ "addons.js": ["AddonsEngine", "AddonValidator"],
+ "addons.jsm": ["Addon", "STATE_ENABLED", "STATE_DISABLED"],
+ "addonsreconciler.js": ["AddonsReconciler", "CHANGE_INSTALLED", "CHANGE_UNINSTALLED", "CHANGE_ENABLED", "CHANGE_DISABLED"],
+ "AddonTestUtils.jsm": ["AddonTestUtils", "MockAsyncShutdown"],
+ "addonutils.js": ["AddonUtils"],
+ "ajv-4.1.1.js": ["Ajv"],
+ "AlertsHelper.jsm": [],
+ "AppData.jsm": ["makeFakeAppDir"],
+ "AppInfo.jsm": ["newAppInfo", "getAppInfo", "updateAppInfo"],
+ "AppsServiceChild.jsm": ["DOMApplicationRegistry", "WrappedManifestCache"],
+ "arrays.js": ["inArray", "getSet", "indexOf", "remove", "rindexOf", "compare"],
+ "assertions.js": ["Assert", "Expect"],
+ "async.js": ["Async"],
+ "AsyncSpellCheckTestHelper.jsm": ["onSpellCheck"],
+ "Battery.jsm": ["GetBattery", "Battery"],
+ "blocklist-clients.js": ["AddonBlocklistClient", "GfxBlocklistClient", "OneCRLBlocklistClient", "PluginBlocklistClient", "FILENAME_ADDONS_JSON", "FILENAME_GFX_JSON", "FILENAME_PLUGINS_JSON"],
+ "blocklist-updater.js": ["checkVersions", "addTestBlocklistClient"],
+ "bogus_element_type.jsm": [],
+ "bookmark_validator.js": ["BookmarkValidator", "BookmarkProblemData"],
+ "bookmarks.js": ["BookmarksEngine", "PlacesItem", "Bookmark", "BookmarkFolder", "BookmarkQuery", "Livemark", "BookmarkSeparator"],
+ "bookmarks.jsm": ["PlacesItem", "Bookmark", "Separator", "Livemark", "BookmarkFolder", "DumpBookmarks"],
+ "BootstrapMonitor.jsm": ["monitor"],
+ "browser-loader.js": ["BrowserLoader"],
+ "browserid_identity.js": ["BrowserIDManager", "AuthenticationError"],
+ "CertUtils.jsm": ["BadCertHandler", "checkCert", "readCertPrefs", "validateCert"],
+ "clients.js": ["ClientEngine", "ClientsRec"],
+ "CloudSyncAdapters.jsm": ["Adapters"],
+ "CloudSyncBookmarks.jsm": ["Bookmarks"],
+ "CloudSyncBookmarksFolderCache.jsm": ["FolderCache"],
+ "CloudSyncEventSource.jsm": ["EventSource"],
+ "CloudSyncLocal.jsm": ["Local"],
+ "CloudSyncPlacesWrapper.jsm": ["PlacesWrapper"],
+ "CloudSyncTabs.jsm": ["Tabs"],
+ "cluster.js": ["ClusterManager"],
+ "collection_validator.js": ["CollectionValidator", "CollectionProblemData"],
+ "Console.jsm": ["console", "ConsoleAPI"],
+ "Constants.jsm": ["Roles", "Events", "Relations", "Filters", "States", "Prefilters"],
+ "ContactDB.jsm": ["ContactDB", "DB_NAME", "STORE_NAME", "SAVED_GETALL_STORE_NAME", "REVISION_STORE", "DB_VERSION"],
+ "content-server.jsm": ["init"],
+ "content.jsm": ["registerContentFrame"],
+ "ContentCrashHandlers.jsm": ["TabCrashHandler", "PluginCrashReporter", "UnsubmittedCrashHandler"],
+ "ContentObservers.jsm": [],
+ "ContentPrefUtils.jsm": ["ContentPref", "cbHandleResult", "cbHandleError", "cbHandleCompletion", "safeCallback"],
+ "controller.js": ["MozMillController", "globalEventRegistry", "sleep", "windowMap"],
+ "cookies.js": ["Cookies"],
+ "CoverageUtils.jsm": ["CoverageCollector"],
+ "CrashManagerTest.jsm": ["configureLogging", "getManager", "sleep", "TestingCrashManager"],
+ "dbg-client.jsm": ["DebuggerTransport", "DebuggerClient", "RootClient", "LongStringClient", "EnvironmentClient", "ObjectClient"],
+ "dbg-server.jsm": ["DebuggerServer", "ActorPool", "OriginalLocation"],
+ "debug.js": ["NS_ASSERT"],
+ "declined.js": ["DeclinedEngines"],
+ "dispatcher.js": ["Dispatcher"],
+ "distribution.js": ["DistributionCustomizer"],
+ "DNSTypes.jsm": ["DNS_QUERY_RESPONSE_CODES", "DNS_AUTHORITATIVE_ANSWER_CODES", "DNS_CLASS_CODES", "DNS_RECORD_TYPES"],
+ "dom.js": ["getAttributes"],
+ "DOMRequestHelper.jsm": ["DOMRequestIpcHelper"],
+ "DownloadCore.jsm": ["Download", "DownloadSource", "DownloadTarget", "DownloadError", "DownloadSaver", "DownloadCopySaver", "DownloadLegacySaver", "DownloadPDFSaver"],
+ "DownloadList.jsm": ["DownloadList", "DownloadCombinedList", "DownloadSummary"],
+ "E10SAddonsRollout.jsm": ["isAddonPartOfE10SRollout"],
+ "elementslib.js": ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath", "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib"],
+ "engines.js": ["EngineManager", "Engine", "SyncEngine", "Tracker", "Store", "Changeset"],
+ "enginesync.js": ["EngineSynchronizer"],
+ "errors.js": ["BaseError", "ApplicationQuitError", "AssertionError", "TimeoutError"],
+ "evaluate.js": ["evaluate", "sandbox", "Sandboxes"],
+ "event-emitter.js": ["EventEmitter"],
+ "EventUtils.js": ["disableNonTestMouseEvents", "sendMouseEvent", "sendChar", "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch", "synthesizeMouseAtPoint", "synthesizeTouchAtPoint", "synthesizeMouseAtCenter", "synthesizeTouchAtCenter", "synthesizeWheel", "synthesizeKey", "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent", "synthesizeText", "synthesizeComposition", "synthesizeQuerySelectedText"],
+ "Extension.jsm": ["Extension", "ExtensionData"],
+ "ExtensionAPI.jsm": ["ExtensionAPI", "ExtensionAPIs"],
+ "ExtensionXPCShellUtils.jsm": ["ExtensionTestUtils"],
+ "fakeservices.js": ["FakeCryptoService", "FakeFilesystemService", "FakeGUIDService", "fakeSHA256HMAC"],
+ "file_expandosharing.jsm": ["checkFromJSM"],
+ "file_stringencoding.jsm": ["checkFromJSM"],
+ "file_url.jsm": ["checkFromJSM"],
+ "file_worker_url.jsm": ["checkFromJSM"],
+ "Finder.jsm": ["Finder", "GetClipboardSearchString"],
+ "FormAutofillContent.jsm": ["FormAutofillHandler"],
+ "forms.js": ["FormEngine", "FormRec", "FormValidator"],
+ "forms.jsm": ["FormData"],
+ "frame.js": ["Collector", "Runner", "events", "runTestFile", "log", "timers", "persisted", "shutdownApplication"],
+ "FrameScriptManager.jsm": ["getNewLoaderID"],
+ "fxa_utils.js": ["initializeIdentityWithTokenServerResponse"],
+ "fxaccounts.jsm": ["Authentication"],
+ "FxAccounts.jsm": ["fxAccounts", "FxAccounts"],
+ "FxAccountsOAuthGrantClient.jsm": ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"],
+ "FxAccountsProfileClient.jsm": ["FxAccountsProfileClient", "FxAccountsProfileClientError"],
+ "FxAccountsPush.js": ["FxAccountsPushService"],
+ "FxAccountsStorage.jsm": ["FxAccountsStorageManagerCanStoreField", "FxAccountsStorageManager"],
+ "FxAccountsWebChannel.jsm": ["EnsureFxAccountsWebChannel"],
+ "FxaMigrator.jsm": ["fxaMigrator"],
+ "gDevTools.jsm": ["gDevTools", "gDevToolsBrowser"],
+ "gDevTools.jsm": ["gDevTools", "gDevToolsBrowser"],
+ "Geometry.jsm": ["Point", "Rect"],
+ "Gestures.jsm": ["GestureSettings", "GestureTracker"],
+ "GMPInstallManager.jsm": ["GMPInstallManager", "GMPExtractor", "GMPDownloader", "GMPAddon"],
+ "GMPProvider.jsm": [],
+ "GMPUtils.jsm": ["EME_ADOBE_ID", "GMP_PLUGIN_IDS", "GMPPrefs", "GMPUtils", "OPEN_H264_ID", "WIDEVINE_ID"],
+ "hawkclient.js": ["HawkClient"],
+ "hawkrequest.js": ["HAWKAuthenticatedRESTRequest", "deriveHawkCredentials"],
+ "HelperApps.jsm": ["App", "HelperApps"],
+ "history.js": ["HistoryEngine", "HistoryRec"],
+ "history.jsm": ["HistoryEntry", "DumpHistory"],
+ "Http.jsm": ["httpRequest", "percentEncode"],
+ "httpd.js": ["HTTP_400", "HTTP_401", "HTTP_402", "HTTP_403", "HTTP_404", "HTTP_405", "HTTP_406", "HTTP_407", "HTTP_408", "HTTP_409", "HTTP_410", "HTTP_411", "HTTP_412", "HTTP_413", "HTTP_414", "HTTP_415", "HTTP_417", "HTTP_500", "HTTP_501", "HTTP_502", "HTTP_503", "HTTP_504", "HTTP_505", "HttpError", "HttpServer"],
+ "identity.js": ["IdentityManager"],
+ "Identity.jsm": ["IdentityService"],
+ "IdentityUtils.jsm": ["checkDeprecated", "checkRenamed", "getRandomId", "objectCopy", "makeMessageObject"],
+ "import_module.jsm": ["MODULE_IMPORTED", "MODULE_URI", "SUBMODULE_IMPORTED", "same_scope", "SUBMODULE_IMPORTED_TO_SCOPE"],
+ "import_sub_module.jsm": ["SUBMODULE_IMPORTED", "test_obj"],
+ "InlineSpellChecker.jsm": ["InlineSpellChecker", "SpellCheckHelper"],
+ "JNI.jsm": ["JNI", "android_log"],
+ "jpakeclient.js": ["JPAKEClient", "SendCredentialsController"],
+ "Jsbeautify.jsm": ["jsBeautify"],
+ "jsdebugger.jsm": ["addDebuggerToGlobal"],
+ "json2.js": ["JSON"],
+ "keys.js": ["BulkKeyBundle", "SyncKeyBundle"],
+ "KeyValueParser.jsm": ["parseKeyValuePairsFromLines", "parseKeyValuePairs", "parseKeyValuePairsFromFile"],
+ "kinto-http-client.js": ["KintoHttpClient"],
+ "kinto-offline-client.js": ["loadKinto"],
+ "loader-plugin-raw.jsm": ["requireRawId"],
+ "loader.js": ["WorkerDebuggerLoader", "worker"],
+ "Loader.jsm": ["DevToolsLoader", "devtools", "BuiltinProvider", "require", "loader"],
+ "logger.jsm": ["Logger"],
+ "logging.js": ["getTestLogger", "initTestLogging"],
+ "LoginManagerContent.jsm": ["LoginManagerContent", "LoginFormFactory", "UserAutoCompleteResult"],
+ "LoginRecipes.jsm": ["LoginRecipesContent", "LoginRecipesParent"],
+ "logmanager.js": ["LogManager"],
+ "LogUtils.jsm": ["Logger"],
+ "lz4.js": ["Lz4"],
+ "lz4_internal.js": ["Primitives"],
+ "main.js": ["Weave"],
+ "MatchPattern.jsm": ["MatchPattern", "MatchGlobs", "MatchURLFilters"],
+ "mcc_iso3166_table.jsm": ["MCC_ISO3166_TABLE"],
+ "message.js": ["Command", "Message", "MessageOrigin", "Response"],
+ "Messaging.jsm": ["sendMessageToJava", "Messaging"],
+ "microformat-shiv.js": ["Microformats"],
+ "MigrationUtils.jsm": ["MigrationUtils", "MigratorPrototype"],
+ "MinimalIdentity.jsm": ["IdentityService"],
+ "mozelement.js": ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup", "MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList", "MozMillTextBox", "subclasses"],
+ "mozmill.js": ["controller", "utils", "elementslib", "os", "getBrowserController", "newBrowserController", "getAddonsController", "getPreferencesController", "newMail3PaneController", "getMail3PaneController", "wm", "platform", "getAddrbkController", "getMsgComposeController", "getDownloadsController", "Application", "findElement", "getPlacesController", "isMac", "isLinux", "isWindows", "firePythonCallback", "getAddons"],
+ "msgbroker.js": ["addListener", "addObject", "removeListener", "sendMessage", "log", "pass", "fail"],
+ "MulticastDNSAndroid.jsm": ["MulticastDNS"],
+ "NativeMessaging.jsm": ["HostManifestManager", "NativeApp"],
+ "NetworkPrioritizer.jsm": ["trackBrowserWindow"],
+ "NotificationDB.jsm": [],
+ "nsFormAutoCompleteResult.jsm": ["FormAutoCompleteResult"],
+ "objects.js": ["getLength"],
+ "observers.js": ["Observers"],
+ "offlineAppCache.jsm": ["OfflineAppCacheHelper"],
+ "OrientationChangeHandler.jsm": [],
+ "os.js": ["listDirectory", "getFileForPath", "abspath", "getPlatform"],
+ "osfile.jsm": ["OS"],
+ "osfile_async_front.jsm": ["OS"],
+ "osfile_native.jsm": ["read"],
+ "osfile_shared_allthreads.jsm": ["LOG", "clone", "Config", "Constants", "Type", "HollowStructure", "OSError", "Library", "declareFFI", "declareLazy", "declareLazyFFI", "normalizeBufferArgs", "projectValue", "isArrayBuffer", "isTypedArray", "defineLazyGetter", "OS"],
+ "osfile_unix_allthreads.jsm": ["declareFFI", "libc", "Error", "AbstractInfo", "AbstractEntry", "Type", "POS_START", "POS_CURRENT", "POS_END"],
+ "osfile_win_allthreads.jsm": ["declareFFI", "libc", "Error", "AbstractInfo", "AbstractEntry", "Type", "POS_START", "POS_CURRENT", "POS_END"],
+ "ospath_unix.jsm": ["basename", "dirname", "join", "normalize", "split", "toFileURI", "fromFileURI"],
+ "ospath_win.jsm": ["basename", "dirname", "join", "normalize", "split", "winGetDrive", "winIsAbsolute", "toFileURI", "fromFileURI"],
+ "OutputGenerator.jsm": ["UtteranceGenerator", "BrailleGenerator"],
+ "PageMenu.jsm": ["PageMenuParent", "PageMenuChild"],
+ "PageThumbs.jsm": ["PageThumbs", "PageThumbsStorage"],
+ "Parser.jsm": ["Parser", "ParserHelpers", "SyntaxTreeVisitor"],
+ "parsingTestHelpers.jsm": ["generateURIsFromDirTree"],
+ "passwords.js": ["PasswordEngine", "LoginRec", "PasswordValidator"],
+ "passwords.jsm": ["Password", "DumpPasswords"],
+ "PdfJsNetwork.jsm": ["NetworkManager"],
+ "PermissionSettings.jsm": ["PermissionSettingsModule"],
+ "PermissionsTable.jsm": ["PermissionsTable", "PermissionsReverseTable", "expandPermissions", "appendAccessToPermName", "isExplicitInPermissionsTable", "AllPossiblePermissions"],
+ "PhoneNumberMetaData.jsm": ["PHONE_NUMBER_META_DATA"],
+ "PlacesUtils.jsm": ["PlacesUtils", "PlacesAggregatedTransaction", "PlacesCreateFolderTransaction", "PlacesCreateBookmarkTransaction", "PlacesCreateSeparatorTransaction", "PlacesCreateLivemarkTransaction", "PlacesMoveItemTransaction", "PlacesRemoveItemTransaction", "PlacesEditItemTitleTransaction", "PlacesEditBookmarkURITransaction", "PlacesSetItemAnnotationTransaction", "PlacesSetPageAnnotationTransaction", "PlacesEditBookmarkKeywordTransaction", "PlacesEditBookmarkPostDataTransaction", "PlacesEditItemDateAddedTransaction", "PlacesEditItemLastModifiedTransaction", "PlacesSortFolderByNameTransaction", "PlacesTagURITransaction", "PlacesUntagURITransaction"],
+ "PluginProvider.jsm": [],
+ "PointerAdapter.jsm": ["PointerRelay", "PointerAdapter"],
+ "policies.js": ["ErrorHandler", "SyncScheduler"],
+ "prefs.js": ["PrefsEngine", "PrefRec"],
+ "prefs.jsm": ["Preference"],
+ "PresentationDeviceInfoManager.jsm": ["PresentationDeviceInfoService"],
+ "PromiseWorker.jsm": ["BasePromiseWorker"],
+ "PushCrypto.jsm": ["PushCrypto", "concatArray"],
+ "quit.js": ["goQuitApplication"],
+ "record.js": ["WBORecord", "RecordManager", "CryptoWrapper", "CollectionKeyManager", "Collection"],
+ "recursive_importA.jsm": ["foo", "bar"],
+ "recursive_importB.jsm": ["baz", "qux"],
+ "reflect.jsm": ["Reflect"],
+ "RemoteFinder.jsm": ["RemoteFinder", "RemoteFinderListener"],
+ "RemotePageManager.jsm": ["RemotePages", "RemotePageManager", "PageListener"],
+ "RemoteWebProgress.jsm": ["RemoteWebProgressManager"],
+ "resource.js": ["AsyncResource", "Resource"],
+ "responsivedesign.jsm": ["ResponsiveUIManager"],
+ "rest.js": ["RESTRequest", "RESTResponse", "TokenAuthenticatedRESTRequest", "SyncStorageRequest"],
+ "rotaryengine.js": ["RotaryEngine", "RotaryRecord", "RotaryStore", "RotaryTracker"],
+ "RTCStatsReport.jsm": ["convertToRTCStatsReport"],
+ "scratchpad-manager.jsm": ["ScratchpadManager"],
+ "server.js": ["MarionetteServer"],
+ "service.js": ["Service"],
+ "SettingsDB.jsm": ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"],
+ "SharedPromptUtils.jsm": ["PromptUtils", "EnableDelayHelper"],
+ "ShutdownLeaksCollector.jsm": ["ContentCollector"],
+ "SignInToWebsite.jsm": ["SignInToWebsiteController"],
+ "Social.jsm": ["Social", "OpenGraphBuilder", "DynamicResizeWatcher", "sizeSocialPanelToContent"],
+ "SpecialPowersObserver.jsm": ["SpecialPowersObserver", "SpecialPowersObserverFactory"],
+ "stack.js": ["findCallerFrame"],
+ "StateMachineHelper.jsm": ["State", "CommandType"],
+ "status.js": ["Status"],
+ "storageserver.js": ["ServerBSO", "StorageServerCallback", "StorageServerCollection", "StorageServer", "storageServerForUsers"],
+ "stringbundle.js": ["StringBundle"],
+ "strings.js": ["trim", "vslice"],
+ "StructuredLog.jsm": ["StructuredLogger", "StructuredFormatter"],
+ "StyleEditorUtil.jsm": ["getString", "assert", "log", "text", "wire", "showFilePicker"],
+ "subprocess_common.jsm": ["BaseProcess", "PromiseWorker", "SubprocessConstants"],
+ "subprocess_unix.jsm": ["SubprocessImpl"],
+ "subprocess_win.jsm": ["SubprocessImpl"],
+ "sync.jsm": ["Authentication"],
+ "tabs.js": ["TabEngine", "TabSetRecord"],
+ "tabs.jsm": ["BrowserTabs"],
+ "tcpsocket_test.jsm": ["createSocket", "createServer", "enablePrefsAndPermissions", "socketCompartmentInstanceOfArrayBuffer"],
+ "telemetry.js": ["SyncTelemetry"],
+ "test.jsm": ["Foo"],
+ "test2.jsm": ["Bar"],
+ "test_bug883784.jsm": ["Test"],
+ "Timer.jsm": ["setTimeout", "clearTimeout", "setInterval", "clearInterval"],
+ "tokenserverclient.js": ["TokenServerClient", "TokenServerClientError", "TokenServerClientNetworkError", "TokenServerClientServerError"],
+ "ToolboxProcess.jsm": ["BrowserToolboxProcess"],
+ "tps.jsm": ["ACTIONS", "TPS"],
+ "Translation.jsm": ["Translation", "TranslationTelemetry"],
+ "Traversal.jsm": ["TraversalRules", "TraversalHelper"],
+ "UpdateTelemetry.jsm": ["AUSTLMY"],
+ "userapi.js": ["UserAPI10Client"],
+ "util.js": ["getChromeWindow", "XPCOMUtils", "Services", "Utils", "Async", "Svc", "Str"],
+ "utils.js": ["applicationName", "assert", "Copy", "getBrowserObject", "getChromeWindow", "getWindows", "getWindowByTitle", "getWindowByType", "getWindowId", "getMethodInWindows", "getPreference", "saveDataURL", "setPreference", "sleep", "startTimer", "stopTimer", "takeScreenshot", "unwrapNode", "waitFor", "btoa", "encryptPayload", "isConfiguredWithLegacyIdentity", "ensureLegacyIdentityManager", "setBasicCredentials", "makeIdentityConfig", "makeFxAccountsInternalMock", "configureFxAccountIdentity", "configureIdentity", "SyncTestingInfrastructure", "waitForZeroTimer", "Promise", "add_identity_test", "MockFxaStorageManager", "AccountState", "sumHistogram", "CommonUtils", "CryptoUtils", "TestingUtils"],
+ "Utils.jsm": ["Utils", "Logger", "PivotContext", "PrefCache", "SettingCache"],
+ "VariablesView.jsm": ["VariablesView", "escapeHTML"],
+ "VariablesViewController.jsm": ["VariablesViewController", "StackFrameUtils"],
+ "version.jsm": ["VERSION"],
+ "vtt.jsm": ["WebVTT"],
+ "WebChannel.jsm": ["WebChannel", "WebChannelBroker"],
+ "WindowDraggingUtils.jsm": ["WindowDraggingElement"],
+ "windows.js": ["init", "map"],
+ "windows.jsm": ["BrowserWindows"],
+ "WindowsJumpLists.jsm": ["WinTaskbarJumpList"],
+ "WindowsPreviewPerTab.jsm": ["AeroPeek"],
+ "withs.js": ["startsWith", "endsWith"],
+ "xul-app.jsm": ["XulApp"]
+}
diff --git a/tools/lint/eslint/npm-shrinkwrap.json b/tools/lint/eslint/npm-shrinkwrap.json
new file mode 100644
index 000000000..4421c4cd3
--- /dev/null
+++ b/tools/lint/eslint/npm-shrinkwrap.json
@@ -0,0 +1,718 @@
+{
+ "name": "mach-eslint",
+ "dependencies": {
+ "acorn": {
+ "version": "4.0.3",
+ "from": "acorn@>=4.0.1 <5.0.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.3.tgz"
+ },
+ "acorn-jsx": {
+ "version": "3.0.1",
+ "from": "acorn-jsx@>=3.0.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
+ "dependencies": {
+ "acorn": {
+ "version": "3.3.0",
+ "from": "acorn@>=3.0.4 <4.0.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz"
+ }
+ }
+ },
+ "ajv": {
+ "version": "4.8.2",
+ "from": "ajv@>=4.7.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.8.2.tgz"
+ },
+ "ajv-keywords": {
+ "version": "1.1.1",
+ "from": "ajv-keywords@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.1.1.tgz"
+ },
+ "ansi-escapes": {
+ "version": "1.4.0",
+ "from": "ansi-escapes@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz"
+ },
+ "ansi-regex": {
+ "version": "2.0.0",
+ "from": "ansi-regex@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "from": "ansi-styles@>=2.2.1 <3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz"
+ },
+ "argparse": {
+ "version": "1.0.9",
+ "from": "argparse@>=1.0.7 <2.0.0",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz"
+ },
+ "array-union": {
+ "version": "1.0.2",
+ "from": "array-union@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz"
+ },
+ "array-uniq": {
+ "version": "1.0.3",
+ "from": "array-uniq@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz"
+ },
+ "arrify": {
+ "version": "1.0.1",
+ "from": "arrify@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz"
+ },
+ "balanced-match": {
+ "version": "0.4.2",
+ "from": "balanced-match@>=0.4.1 <0.5.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz"
+ },
+ "brace-expansion": {
+ "version": "1.1.6",
+ "from": "brace-expansion@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz"
+ },
+ "caller-path": {
+ "version": "0.1.0",
+ "from": "caller-path@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz"
+ },
+ "callsites": {
+ "version": "0.2.0",
+ "from": "callsites@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz"
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "from": "chalk@>=1.1.3 <2.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz"
+ },
+ "circular-json": {
+ "version": "0.3.1",
+ "from": "circular-json@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz"
+ },
+ "cli-cursor": {
+ "version": "1.0.2",
+ "from": "cli-cursor@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz"
+ },
+ "cli-width": {
+ "version": "2.1.0",
+ "from": "cli-width@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz"
+ },
+ "co": {
+ "version": "4.6.0",
+ "from": "co@>=4.6.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz"
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "from": "code-point-at@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz"
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "from": "concat-map@0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ },
+ "concat-stream": {
+ "version": "1.5.2",
+ "from": "concat-stream@>=1.4.6 <2.0.0",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz"
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "from": "core-util-is@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
+ },
+ "d": {
+ "version": "0.1.1",
+ "from": "d@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz"
+ },
+ "debug": {
+ "version": "2.3.0",
+ "from": "debug@>=2.1.1 <3.0.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.0.tgz"
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "from": "deep-is@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
+ },
+ "del": {
+ "version": "2.2.2",
+ "from": "del@>=2.0.2 <3.0.0",
+ "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz"
+ },
+ "doctrine": {
+ "version": "1.5.0",
+ "from": "doctrine@>=1.2.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz"
+ },
+ "dom-serializer": {
+ "version": "0.1.0",
+ "from": "dom-serializer@>=0.0.0 <1.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+ "dependencies": {
+ "domelementtype": {
+ "version": "1.1.3",
+ "from": "domelementtype@>=1.1.1 <1.2.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz"
+ }
+ }
+ },
+ "domelementtype": {
+ "version": "1.3.0",
+ "from": "domelementtype@>=1.3.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz"
+ },
+ "domhandler": {
+ "version": "2.3.0",
+ "from": "domhandler@>=2.3.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz"
+ },
+ "domutils": {
+ "version": "1.5.1",
+ "from": "domutils@>=1.5.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz"
+ },
+ "entities": {
+ "version": "1.1.1",
+ "from": "entities@>=1.1.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz"
+ },
+ "es5-ext": {
+ "version": "0.10.12",
+ "from": "es5-ext@>=0.10.11 <0.11.0",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz"
+ },
+ "es6-iterator": {
+ "version": "2.0.0",
+ "from": "es6-iterator@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz"
+ },
+ "es6-map": {
+ "version": "0.1.4",
+ "from": "es6-map@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.4.tgz"
+ },
+ "es6-set": {
+ "version": "0.1.4",
+ "from": "es6-set@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.4.tgz"
+ },
+ "es6-symbol": {
+ "version": "3.1.0",
+ "from": "es6-symbol@>=3.1.0 <3.2.0",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz"
+ },
+ "es6-weak-map": {
+ "version": "2.0.1",
+ "from": "es6-weak-map@>=2.0.1 <3.0.0",
+ "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.1.tgz"
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "from": "escape-string-regexp@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
+ },
+ "escope": {
+ "version": "3.6.0",
+ "from": "escope@>=3.6.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz"
+ },
+ "eslint": {
+ "version": "3.8.1",
+ "from": "eslint@3.8.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.8.1.tgz"
+ },
+ "eslint-plugin-html": {
+ "version": "1.5.2",
+ "from": "eslint-plugin-html@1.5.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-1.5.2.tgz"
+ },
+ "eslint-plugin-react": {
+ "version": "4.2.3",
+ "from": "eslint-plugin-react@4.2.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-4.2.3.tgz"
+ },
+ "espree": {
+ "version": "3.3.2",
+ "from": "espree@>=3.2.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-3.3.2.tgz"
+ },
+ "esprima": {
+ "version": "2.7.3",
+ "from": "esprima@>=2.6.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz"
+ },
+ "esrecurse": {
+ "version": "4.1.0",
+ "from": "esrecurse@>=4.1.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz",
+ "dependencies": {
+ "estraverse": {
+ "version": "4.1.1",
+ "from": "estraverse@>=4.1.0 <4.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz"
+ }
+ }
+ },
+ "estraverse": {
+ "version": "4.2.0",
+ "from": "estraverse@>=4.2.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz"
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "from": "esutils@>=2.0.2 <3.0.0",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz"
+ },
+ "event-emitter": {
+ "version": "0.3.4",
+ "from": "event-emitter@>=0.3.4 <0.4.0",
+ "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz"
+ },
+ "exit-hook": {
+ "version": "1.1.1",
+ "from": "exit-hook@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz"
+ },
+ "fast-levenshtein": {
+ "version": "2.0.5",
+ "from": "fast-levenshtein@>=2.0.4 <2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.5.tgz"
+ },
+ "figures": {
+ "version": "1.7.0",
+ "from": "figures@>=1.3.5 <2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz"
+ },
+ "file-entry-cache": {
+ "version": "2.0.0",
+ "from": "file-entry-cache@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz"
+ },
+ "flat-cache": {
+ "version": "1.2.1",
+ "from": "flat-cache@>=1.2.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.1.tgz"
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "from": "fs.realpath@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
+ },
+ "generate-function": {
+ "version": "2.0.0",
+ "from": "generate-function@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
+ },
+ "generate-object-property": {
+ "version": "1.2.0",
+ "from": "generate-object-property@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz"
+ },
+ "glob": {
+ "version": "7.1.1",
+ "from": "glob@>=7.0.3 <8.0.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz"
+ },
+ "globals": {
+ "version": "9.12.0",
+ "from": "globals@>=9.2.0 <10.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-9.12.0.tgz"
+ },
+ "globby": {
+ "version": "5.0.0",
+ "from": "globby@>=5.0.0 <6.0.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz"
+ },
+ "graceful-fs": {
+ "version": "4.1.10",
+ "from": "graceful-fs@>=4.1.2 <5.0.0",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.10.tgz"
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "from": "has-ansi@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
+ },
+ "htmlparser2": {
+ "version": "3.9.2",
+ "from": "htmlparser2@>=3.8.2 <4.0.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz"
+ },
+ "ignore": {
+ "version": "3.2.0",
+ "from": "ignore@>=3.1.5 <4.0.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.2.0.tgz"
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "from": "imurmurhash@>=0.1.4 <0.2.0",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "from": "inflight@>=1.0.4 <2.0.0",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "from": "inherits@>=2.0.1 <2.1.0",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
+ },
+ "ini-parser": {
+ "version": "0.0.2",
+ "from": "ini-parser@>=0.0.2 <0.0.3",
+ "resolved": "https://registry.npmjs.org/ini-parser/-/ini-parser-0.0.2.tgz"
+ },
+ "inquirer": {
+ "version": "0.12.0",
+ "from": "inquirer@>=0.12.0 <0.13.0",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz"
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz"
+ },
+ "is-my-json-valid": {
+ "version": "2.15.0",
+ "from": "is-my-json-valid@>=2.10.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz"
+ },
+ "is-path-cwd": {
+ "version": "1.0.0",
+ "from": "is-path-cwd@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz"
+ },
+ "is-path-in-cwd": {
+ "version": "1.0.0",
+ "from": "is-path-in-cwd@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz"
+ },
+ "is-path-inside": {
+ "version": "1.0.0",
+ "from": "is-path-inside@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz"
+ },
+ "is-property": {
+ "version": "1.0.2",
+ "from": "is-property@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
+ },
+ "is-resolvable": {
+ "version": "1.0.0",
+ "from": "is-resolvable@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz"
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "from": "isarray@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
+ },
+ "js-yaml": {
+ "version": "3.6.1",
+ "from": "js-yaml@>=3.5.1 <4.0.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz"
+ },
+ "json-stable-stringify": {
+ "version": "1.0.1",
+ "from": "json-stable-stringify@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz"
+ },
+ "jsonify": {
+ "version": "0.0.0",
+ "from": "jsonify@>=0.0.0 <0.1.0",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
+ },
+ "jsonpointer": {
+ "version": "4.0.0",
+ "from": "jsonpointer@>=4.0.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.0.tgz"
+ },
+ "levn": {
+ "version": "0.3.0",
+ "from": "levn@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz"
+ },
+ "lodash": {
+ "version": "4.16.6",
+ "from": "lodash@>=4.0.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz"
+ },
+ "minimatch": {
+ "version": "3.0.3",
+ "from": "minimatch@>=3.0.2 <4.0.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz"
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "from": "minimist@0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "from": "mkdirp@>=0.5.0 <0.6.0",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
+ },
+ "ms": {
+ "version": "0.7.2",
+ "from": "ms@0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz"
+ },
+ "mute-stream": {
+ "version": "0.0.5",
+ "from": "mute-stream@0.0.5",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz"
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "from": "natural-compare@>=1.4.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "from": "number-is-nan@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz"
+ },
+ "object-assign": {
+ "version": "4.1.0",
+ "from": "object-assign@>=4.0.1 <5.0.0",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
+ },
+ "once": {
+ "version": "1.4.0",
+ "from": "once@>=1.3.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
+ },
+ "onetime": {
+ "version": "1.1.0",
+ "from": "onetime@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz"
+ },
+ "optionator": {
+ "version": "0.8.2",
+ "from": "optionator@>=0.8.2 <0.9.0",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz"
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "from": "os-homedir@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz"
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "from": "path-is-absolute@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
+ },
+ "path-is-inside": {
+ "version": "1.0.2",
+ "from": "path-is-inside@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz"
+ },
+ "pify": {
+ "version": "2.3.0",
+ "from": "pify@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "from": "pinkie@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz"
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "from": "pinkie-promise@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz"
+ },
+ "pluralize": {
+ "version": "1.2.1",
+ "from": "pluralize@>=1.2.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz"
+ },
+ "prelude-ls": {
+ "version": "1.1.2",
+ "from": "prelude-ls@>=1.1.2 <1.2.0",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
+ },
+ "process-nextick-args": {
+ "version": "1.0.7",
+ "from": "process-nextick-args@>=1.0.6 <1.1.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz"
+ },
+ "progress": {
+ "version": "1.1.8",
+ "from": "progress@>=1.1.8 <2.0.0",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz"
+ },
+ "readable-stream": {
+ "version": "2.0.6",
+ "from": "readable-stream@>=2.0.0 <2.1.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz"
+ },
+ "readline2": {
+ "version": "1.0.1",
+ "from": "readline2@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz"
+ },
+ "require-uncached": {
+ "version": "1.0.3",
+ "from": "require-uncached@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz"
+ },
+ "resolve-from": {
+ "version": "1.0.1",
+ "from": "resolve-from@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz"
+ },
+ "restore-cursor": {
+ "version": "1.0.1",
+ "from": "restore-cursor@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz"
+ },
+ "rimraf": {
+ "version": "2.5.4",
+ "from": "rimraf@>=2.2.8 <3.0.0",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz"
+ },
+ "run-async": {
+ "version": "0.1.0",
+ "from": "run-async@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz"
+ },
+ "rx-lite": {
+ "version": "3.1.2",
+ "from": "rx-lite@>=3.1.2 <4.0.0",
+ "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz"
+ },
+ "sax": {
+ "version": "1.2.1",
+ "from": "sax@>=1.1.4 <2.0.0",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz"
+ },
+ "shelljs": {
+ "version": "0.6.1",
+ "from": "shelljs@>=0.6.0 <0.7.0",
+ "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz"
+ },
+ "slice-ansi": {
+ "version": "0.0.4",
+ "from": "slice-ansi@0.0.4",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz"
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "from": "sprintf-js@>=1.0.2 <1.1.0",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "from": "string_decoder@>=0.10.0 <0.11.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "from": "string-width@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz"
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "from": "strip-ansi@>=3.0.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
+ },
+ "strip-bom": {
+ "version": "3.0.0",
+ "from": "strip-bom@>=3.0.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz"
+ },
+ "strip-json-comments": {
+ "version": "1.0.4",
+ "from": "strip-json-comments@>=1.0.1 <1.1.0",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz"
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "from": "supports-color@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
+ },
+ "table": {
+ "version": "3.8.3",
+ "from": "table@>=3.7.8 <4.0.0",
+ "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
+ "dependencies": {
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "from": "is-fullwidth-code-point@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz"
+ },
+ "string-width": {
+ "version": "2.0.0",
+ "from": "string-width@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz"
+ }
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "from": "text-table@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
+ },
+ "through": {
+ "version": "2.3.8",
+ "from": "through@>=2.3.6 <3.0.0",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+ },
+ "tryit": {
+ "version": "1.0.3",
+ "from": "tryit@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz"
+ },
+ "type-check": {
+ "version": "0.3.2",
+ "from": "type-check@>=0.3.2 <0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz"
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "from": "typedarray@>=0.0.5 <0.1.0",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
+ },
+ "user-home": {
+ "version": "2.0.0",
+ "from": "user-home@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz"
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "from": "util-deprecate@>=1.0.1 <1.1.0",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+ },
+ "wordwrap": {
+ "version": "1.0.0",
+ "from": "wordwrap@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "from": "wrappy@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
+ },
+ "write": {
+ "version": "0.2.1",
+ "from": "write@>=0.2.1 <0.3.0",
+ "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz"
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "from": "xtend@>=4.0.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
+ }
+ }
+}
diff --git a/tools/lint/eslint/package.json b/tools/lint/eslint/package.json
new file mode 100644
index 000000000..9c0a8f803
--- /dev/null
+++ b/tools/lint/eslint/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "mach-eslint",
+ "description": "ESLint and external plugins for use with mach",
+ "repository": {},
+ "license": "MPL-2.0",
+ "dependencies": {
+ "eslint": "3.8.1",
+ "eslint-plugin-html": "1.5.2",
+ "eslint-plugin-react": "4.2.3",
+ "escope": "^3.6.0",
+ "espree": "^3.2.0",
+ "estraverse": "^4.2.0",
+ "ini-parser": "^0.0.2",
+ "sax": "^1.1.4"
+ }
+}
diff --git a/tools/lint/eslint/update b/tools/lint/eslint/update
new file mode 100755
index 000000000..477584236
--- /dev/null
+++ b/tools/lint/eslint/update
@@ -0,0 +1,70 @@
+#!/bin/sh
+# Force the scripts working directory to be projdir/tools/lint/eslint.
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+cd $DIR
+
+echo "To complete this script you will need the following tokens from https://api.pub.build.mozilla.org/tokenauth/"
+echo " - tooltool.upload.public"
+echo " - tooltool.download.public"
+echo ""
+read -p "Are these tokens visible at the above URL (y/n)?" choice
+case "$choice" in
+ y|Y )
+ echo ""
+ echo "1. Go to https://api.pub.build.mozilla.org/"
+ echo "2. Log in using your Mozilla LDAP account."
+ echo "3. Click on \"Tokens.\""
+ echo "4. Issue a user token with the permissions tooltool.upload.public and tooltool.download.public."
+ echo ""
+ echo "When you click issue you will be presented with a long string. Paste the string into a temporary file called ~/.tooltool-token."
+ echo ""
+ read -rsp $'Press any key to continue...\n' -n 1
+ ;;
+ n|N )
+ echo ""
+ echo "You will need to contact somebody that has these permissions... people most likely to have these permissions are members of the releng, ateam, a sheriff, mratcliffe, or jryans"
+ exit 1
+ ;;
+ * )
+ echo ""
+ echo "Invalid input."
+ continue
+ ;;
+esac
+
+echo ""
+echo "Removing node_modules and npm_shrinkwrap.json..."
+rm -rf node_modules/
+rm npm-shrinkwrap.json
+
+echo "Installing eslint and external plugins..."
+# ESLint and all _external_ plugins are listed in this directory's package.json,
+# so a regular `npm install` will install them at the specified versions.
+# The in-tree eslint-plugin-mozilla is kept out of this tooltool archive on
+# purpose so that it can be changed by any developer without requiring tooltool
+# access to make changes.
+npm install
+
+echo "Creating npm shrinkwrap..."
+npm shrinkwrap
+
+echo "Creating eslint.tar.gz..."
+tar cvfz eslint.tar.gz node_modules
+
+echo "Downloading tooltool..."
+wget https://raw.githubusercontent.com/mozilla/build-tooltool/master/tooltool.py
+chmod +x tooltool.py
+
+echo "Adding eslint.tar.gz to tooltool..."
+rm manifest.tt
+./tooltool.py add --visibility public eslint.tar.gz
+
+echo "Uploading eslint.tar.gz to tooltool..."
+./tooltool.py upload --authentication-file=~/.tooltool-token --message "node_modules folder update for tools/lint/eslint"
+
+echo "Cleaning up..."
+rm eslint.tar.gz
+rm tooltool.py
+
+echo ""
+echo "Update complete, please commit and check in your changes."
diff --git a/tools/lint/flake8.lint b/tools/lint/flake8.lint
new file mode 100644
index 000000000..eaf4a4dff
--- /dev/null
+++ b/tools/lint/flake8.lint
@@ -0,0 +1,195 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import json
+import os
+import signal
+import subprocess
+
+import which
+from mozprocess import ProcessHandler
+
+from mozlint import result
+
+
+here = os.path.abspath(os.path.dirname(__file__))
+FLAKE8_REQUIREMENTS_PATH = os.path.join(here, 'flake8', 'flake8_requirements.txt')
+
+FLAKE8_NOT_FOUND = """
+Could not find flake8! Install flake8 and try again.
+
+ $ pip install -U --require-hashes -r {}
+""".strip().format(FLAKE8_REQUIREMENTS_PATH)
+
+
+FLAKE8_INSTALL_ERROR = """
+Unable to install correct version of flake8
+Try to install it manually with:
+ $ pip install -U --require-hashes -r {}
+""".strip().format(FLAKE8_REQUIREMENTS_PATH)
+
+LINE_OFFSETS = {
+ # continuation line under-indented for hanging indent
+ 'E121': (-1, 2),
+ # continuation line missing indentation or outdented
+ 'E122': (-1, 2),
+ # continuation line over-indented for hanging indent
+ 'E126': (-1, 2),
+ # continuation line over-indented for visual indent
+ 'E127': (-1, 2),
+ # continuation line under-indented for visual indent
+ 'E128': (-1, 2),
+ # continuation line unaligned for hanging indend
+ 'E131': (-1, 2),
+ # expected 1 blank line, found 0
+ 'E301': (-1, 2),
+ # expected 2 blank lines, found 1
+ 'E302': (-2, 3),
+}
+"""Maps a flake8 error to a lineoffset tuple.
+
+The offset is of the form (lineno_offset, num_lines) and is passed
+to the lineoffset property of `ResultContainer`.
+"""
+
+EXTENSIONS = ['.py', '.lint']
+results = []
+
+
+def process_line(line):
+ # Escape slashes otherwise JSON conversion will not work
+ line = line.replace('\\', '\\\\')
+ try:
+ res = json.loads(line)
+ except ValueError:
+ print('Non JSON output from linter, will not be processed: {}'.format(line))
+ return
+
+ if 'code' in res:
+ if res['code'].startswith('W'):
+ res['level'] = 'warning'
+
+ if res['code'] in LINE_OFFSETS:
+ res['lineoffset'] = LINE_OFFSETS[res['code']]
+
+ results.append(result.from_linter(LINTER, **res))
+
+
+def run_process(cmdargs):
+ # flake8 seems to handle SIGINT poorly. Handle it here instead
+ # so we can kill the process without a cryptic traceback.
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ proc = ProcessHandler(cmdargs, env=os.environ,
+ processOutputLine=process_line)
+ proc.run()
+ signal.signal(signal.SIGINT, orig)
+
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+
+def get_flake8_binary():
+ """
+ Returns the path of the first flake8 binary available
+ if not found returns None
+ """
+ binary = os.environ.get('FLAKE8')
+ if binary:
+ return binary
+
+ try:
+ return which.which('flake8')
+ except which.WhichError:
+ return None
+
+
+def _run_pip(*args):
+ """
+ Helper function that runs pip with subprocess
+ """
+ try:
+ subprocess.check_output(['pip'] + list(args),
+ stderr=subprocess.STDOUT)
+ return True
+ except subprocess.CalledProcessError as e:
+ print(e.output)
+ return False
+
+
+def reinstall_flake8():
+ """
+ Try to install flake8 at the target version, returns True on success
+ otherwise prints the otuput of the pip command and returns False
+ """
+ if _run_pip('install', '-U',
+ '--require-hashes', '-r',
+ FLAKE8_REQUIREMENTS_PATH):
+ return True
+
+ return False
+
+
+def lint(files, **lintargs):
+
+ if not reinstall_flake8():
+ print(FLAKE8_INSTALL_ERROR)
+ return 1
+
+ binary = get_flake8_binary()
+
+ cmdargs = [
+ binary,
+ '--format', '{"path":"%(path)s","lineno":%(row)s,'
+ '"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}',
+ ]
+
+ # Run any paths with a .flake8 file in the directory separately so
+ # it gets picked up. This means only .flake8 files that live in
+ # directories that are explicitly included will be considered.
+ # See bug 1277851
+ no_config = []
+ for f in files:
+ if not os.path.isfile(os.path.join(f, '.flake8')):
+ no_config.append(f)
+ continue
+ run_process(cmdargs+[f])
+
+ # XXX For some reason passing in --exclude results in flake8 not using
+ # the local .flake8 file. So for now only pass in --exclude if there
+ # is no local config.
+ exclude = lintargs.get('exclude')
+ if exclude:
+ cmdargs += ['--exclude', ','.join(lintargs['exclude'])]
+
+ if no_config:
+ run_process(cmdargs+no_config)
+
+ return results
+
+
+LINTER = {
+ 'name': "flake8",
+ 'description': "Python linter",
+ 'include': [
+ 'python/mozlint',
+ 'taskcluster',
+ 'testing/firefox-ui',
+ 'testing/marionette/client',
+ 'testing/marionette/harness',
+ 'testing/marionette/puppeteer',
+ 'testing/mozbase',
+ 'testing/mochitest',
+ 'testing/talos/',
+ 'tools/lint',
+ ],
+ 'exclude': ["testing/mozbase/mozdevice/mozdevice/Zeroconf.py",
+ 'testing/mochitest/pywebsocket'],
+ 'extensions': EXTENSIONS,
+ 'type': 'external',
+ 'payload': lint,
+}
diff --git a/tools/lint/flake8/flake8_requirements.txt b/tools/lint/flake8/flake8_requirements.txt
new file mode 100644
index 000000000..df927d826
--- /dev/null
+++ b/tools/lint/flake8/flake8_requirements.txt
@@ -0,0 +1,4 @@
+flake8==2.5.4 --hash=sha256:fb5a67af4024622287a76abf6b7fe4fb3cfacf765a790976ce64f52c44c88e4a
+mccabe==0.4.0 --hash=sha256:cbc2938f6c01061bc6d21d0c838c2489664755cb18676f0734d7617f4577d09e
+pep8==1.7.0 --hash=sha256:4fc2e478addcf17016657dff30b2d8d611e8341fac19ccf2768802f6635d7b8a
+pyflakes==1.2.3 --hash=sha256:e87bac26c62ea5b45067cc89e4a12f56e1483f1f2cda17e7c9b375b9fd2f40da
diff --git a/tools/lint/mach_commands.py b/tools/lint/mach_commands.py
new file mode 100644
index 000000000..f0f3c9bdf
--- /dev/null
+++ b/tools/lint/mach_commands.py
@@ -0,0 +1,62 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import argparse
+import os
+
+from mozbuild.base import (
+ MachCommandBase,
+)
+
+
+from mach.decorators import (
+ CommandArgument,
+ CommandProvider,
+ Command,
+)
+
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+
+def setup_argument_parser():
+ from mozlint import cli
+ return cli.MozlintParser()
+
+
+@CommandProvider
+class MachCommands(MachCommandBase):
+
+ @Command(
+ 'lint', category='devenv',
+ description='Run linters.',
+ parser=setup_argument_parser)
+ def lint(self, *runargs, **lintargs):
+ """Run linters."""
+ from mozlint import cli
+ lintargs['exclude'] = ['obj*']
+ cli.SEARCH_PATHS.append(here)
+ self._activate_virtualenv()
+ return cli.run(*runargs, **lintargs)
+
+ @Command('eslint', category='devenv',
+ description='Run eslint or help configure eslint for optimal development.')
+ @CommandArgument('paths', default=None, nargs='*',
+ help="Paths to file or directories to lint, like "
+ "'browser/components/loop' Defaults to the "
+ "current directory if not given.")
+ @CommandArgument('-s', '--setup', default=False, action='store_true',
+ help='Configure eslint for optimal development.')
+ @CommandArgument('-b', '--binary', default=None,
+ help='Path to eslint binary.')
+ @CommandArgument('--fix', default=False, action='store_true',
+ help='Request that eslint automatically fix errors, where possible.')
+ @CommandArgument('extra_args', nargs=argparse.REMAINDER,
+ help='Extra args that will be forwarded to eslint.')
+ def eslint(self, paths, extra_args=[], **kwargs):
+ self._mach_context.commands.dispatch('lint', self._mach_context,
+ linters=['eslint'], paths=paths,
+ argv=extra_args, **kwargs)
diff --git a/tools/lint/wpt.lint b/tools/lint/wpt.lint
new file mode 100644
index 000000000..a7f2ae597
--- /dev/null
+++ b/tools/lint/wpt.lint
@@ -0,0 +1,55 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import json
+import os
+
+from mozprocess import ProcessHandler
+
+from mozlint import result
+
+top_src_dir = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
+tests_dir = os.path.join(top_src_dir, "testing", "web-platform", "tests")
+
+results = []
+
+
+def process_line(line):
+ try:
+ data = json.loads(line)
+ except ValueError:
+ return
+ data["level"] = "error"
+ data["path"] = os.path.relpath(os.path.join(tests_dir, data["path"]), top_src_dir)
+ results.append(result.from_linter(LINTER, **data))
+
+
+def run_process():
+ path = os.path.join(tests_dir, "lint")
+ proc = ProcessHandler([path, "--json"], env=os.environ,
+ processOutputLine=process_line)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+
+def lint(files, **kwargs):
+ run_process()
+ return results
+
+
+LINTER = {
+ 'name': "wpt",
+ 'description': "web-platform-tests lint",
+ 'include': [
+ 'testing/web-platform/tests',
+ ],
+ 'exclude': [],
+ 'type': 'external',
+ 'payload': lint,
+}
diff --git a/tools/lint/wpt_manifest.lint b/tools/lint/wpt_manifest.lint
new file mode 100644
index 000000000..584b33b4e
--- /dev/null
+++ b/tools/lint/wpt_manifest.lint
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import imp
+import json
+import os
+import sys
+
+from mozprocess import ProcessHandler
+
+from mozlint import result
+
+
+def lint(files, logger, **kwargs):
+ wpt_dir = os.path.join(kwargs["root"], "testing", "web-platform")
+ manifestupdate = imp.load_source("manifestupdate",
+ os.path.join(wpt_dir, "manifestupdate.py"))
+ manifestupdate.update(logger, wpt_dir, True)
+
+
+LINTER = {
+ 'name': "wpt_manifest",
+ 'description': "web-platform-tests manifest lint",
+ 'include': [
+ 'testing/web-platform/tests',
+ 'testing/web-platform/mozilla/tests',
+ ],
+ 'exclude': [],
+ 'type': 'structured_log',
+ 'payload': lint,
+}