diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /tools/lint | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'tools/lint')
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, +} |