From 0fad1a97d3328cf66cce2c93d847faa784cdcde8 Mon Sep 17 00:00:00 2001 From: Andrew Halberstadt Date: Thu, 2 Jun 2016 15:38:36 -0400 Subject: [PATCH] Bug 1271734 - Move all eslint related infrastructure to tools/lint, r=miker This commit simply moves 'testing/eslint' to 'tools/lint/eslint' and the eslint related mach command from 'python/mach_commands.py' to 'tools/lint/mach_commands.py'. It shouldn't have any functional change on running eslint, either through mach or taskcluster. This is in preparation for bug 1258341, to make the diffs there a little easier to read. MozReview-Commit-ID: K03sn9lv9Lv --- .eslintignore | 4 +- .gitignore | 2 +- .hgignore | 2 +- python/mach_commands.py | 307 ----------------- .../ci/legacy/tasks/branches/base_jobs.yml | 2 +- .../ci/legacy/tasks/tests/eslint-gecko.yml | 6 +- .../eslint/eslint-plugin-mozilla/LICENSE | 0 .../docs/balanced-listeners.rst | 0 .../docs/import-browserjs-globals.rst | 0 .../docs/import-globals.rst | 0 .../docs/import-headjs-globals.rst | 0 .../eslint-plugin-mozilla/docs/index.rst | 0 .../docs/mark-test-function-used.rst | 0 .../eslint-plugin-mozilla/docs/no-aArgs.rst | 0 .../docs/no-cpows-in-tests.rst | 0 .../docs/reject-importGlobalProperties.rst | 0 .../docs/var-only-at-top-level.rst | 0 .../eslint-plugin-mozilla/lib/globals.js | 0 .../eslint-plugin-mozilla/lib/helpers.js | 0 .../eslint/eslint-plugin-mozilla/lib/index.js | 0 .../lib/processors/xbl-bindings.js | 0 .../eslint-plugin-mozilla/lib/rules/.eslintrc | 0 .../lib/rules/balanced-listeners.js | 0 .../lib/rules/import-browserjs-globals.js | 0 .../lib/rules/import-globals.js | 0 .../lib/rules/import-headjs-globals.js | 0 .../lib/rules/mark-test-function-used.js | 0 .../lib/rules/no-aArgs.js | 0 .../lib/rules/no-cpows-in-tests.js | 0 .../rules/reject-importGlobalProperties.js | 0 .../lib/rules/var-only-at-top-level.js | 0 .../eslint/eslint-plugin-mozilla/moz.build | 0 .../eslint/eslint-plugin-mozilla/package.json | 0 {testing => tools/lint}/eslint/manifest.tt | 0 .../lint}/eslint/npm-shrinkwrap.json | 0 {testing => tools/lint}/eslint/package.json | 0 {testing => tools/lint}/eslint/update | 4 +- tools/lint/mach_commands.py | 312 ++++++++++++++++++ 38 files changed, 322 insertions(+), 317 deletions(-) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/LICENSE (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/balanced-listeners.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/import-browserjs-globals.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/import-globals.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/import-headjs-globals.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/index.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/mark-test-function-used.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/no-aArgs.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/no-cpows-in-tests.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/reject-importGlobalProperties.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/docs/var-only-at-top-level.rst (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/globals.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/helpers.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/index.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/moz.build (100%) rename {testing => tools/lint}/eslint/eslint-plugin-mozilla/package.json (100%) rename {testing => tools/lint}/eslint/manifest.tt (100%) rename {testing => tools/lint}/eslint/npm-shrinkwrap.json (100%) rename {testing => tools/lint}/eslint/package.json (100%) rename {testing => tools/lint}/eslint/update (93%) diff --git a/.eslintignore b/.eslintignore index 8659c53b9435..5ef4a2d8ea11 100644 --- a/.eslintignore +++ b/.eslintignore @@ -40,8 +40,8 @@ python/** rdf/** startupcache/** testing/** -!testing/eslint-plugin-mozilla/ -testing/eslint-plugin-mozilla/node_modules/** +!tools/lint/eslint/eslint-plugin-mozilla/ +tools/lint/eslint/eslint-plugin-mozilla/node_modules/** tools/** uriloader/** view/** diff --git a/.gitignore b/.gitignore index abe5e331f985..82c58ba8c360 100644 --- a/.gitignore +++ b/.gitignore @@ -101,7 +101,7 @@ testing/mozharness/.coverage testing/mozharness/nosetests.xml # Ignore node_modules -testing/eslint/node_modules/ +tools/lint/eslint/node_modules/ # Ignore talos virtualenv and tp5n files. # The tp5n set is supposed to be decompressed at diff --git a/.hgignore b/.hgignore index eb75839bbbc9..445c55b97fb5 100644 --- a/.hgignore +++ b/.hgignore @@ -118,7 +118,7 @@ GPATH .tox/ # Ignore node_modules -^testing/eslint/node_modules/ +^tools/lint/eslint/node_modules/ # Ignore talos virtualenv and tp5n files. # The tp5n set is supposed to be decompressed at diff --git a/python/mach_commands.py b/python/mach_commands.py index cdea67981acd..4f3cfd76a487 100644 --- a/python/mach_commands.py +++ b/python/mach_commands.py @@ -6,15 +6,9 @@ from __future__ import absolute_import, print_function, unicode_literals import __main__ import argparse -import json import logging import mozpack.path as mozpath import os -import platform -import subprocess -import sys -import which -from distutils.version import LooseVersion from mozbuild.base import ( MachCommandBase, @@ -26,38 +20,6 @@ from mach.decorators import ( Command, ) -ESLINT_PACKAGES = [ - "eslint@2.9.0", - "eslint-plugin-html@1.4.0", - "eslint-plugin-mozilla@0.0.3", - "eslint-plugin-react@4.2.3" -] - -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() - @CommandProvider class MachCommands(MachCommandBase): @@ -199,272 +161,3 @@ class MachCommands(MachCommandBase): return 0 if return_code == 0 else 1 - @Command('eslint', category='devenv', - description='Run eslint or help configure eslint for optimal development.') - @CommandArgument('-s', '--setup', default=False, action='store_true', - help='configure eslint for optimal development.') - @CommandArgument('-e', '--ext', default='[.js,.jsm,.jsx,.xml,.html]', - help='Filename extensions to lint, default: "[.js,.jsm,.jsx,.xml,.html]".') - @CommandArgument('-b', '--binary', default=None, - help='Path to eslint binary.') - @CommandArgument('args', nargs=argparse.REMAINDER) # Passed through to eslint. - def eslint(self, setup, ext=None, binary=None, args=None): - '''Run eslint.''' - - module_path = self.get_eslint_module_path() - - # eslint requires at least node 4.2.3 - nodePath = self.getNodeOrNpmPath("node", LooseVersion("4.2.3")) - if not nodePath: - return 1 - - if setup: - return self.eslint_setup() - - npmPath = self.getNodeOrNpmPath("npm") - if not npmPath: - return 1 - - if self.eslintModuleHasIssues(): - install = self._prompt_yn("\nContinuing will automatically fix " - "these issues. Would you like to " - "continue") - if install: - self.eslint_setup() - else: - return 1 - - # 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 - - self.log(logging.INFO, 'eslint', {'binary': binary, 'args': args}, - 'Running {binary}') - - args = 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', - '--ext', ext, # This keeps ext as a single argument. - ] + args - - success = self.run_process(cmd_args, - pass_thru=True, # Allow user to run eslint interactively. - ensure_exit_code=False, # Don't throw on non-zero exit code. - require_unix_environment=True # eslint is not a valid Win32 binary. - ) - - self.log(logging.INFO, 'eslint', {'msg': ('No errors' if success == 0 else 'Errors')}, - 'Finished eslint. {msg} encountered.') - return success - - def eslint_setup(self, update_only=False): - """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 = self.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) - - npmPath = self.getNodeOrNpmPath("npm") - if not npmPath: - return 1 - - # Install eslint and necessary plugins. - for pkg in ESLINT_PACKAGES: - name, version = pkg.split("@") - success = False - - if self.node_package_installed(pkg, cwd=module_path): - success = True - else: - if pkg.startswith("eslint-plugin-mozilla"): - cmd = [npmPath, "install", - os.path.join(module_path, "eslint-plugin-mozilla")] - else: - cmd = [npmPath, "install", pkg] - - print("Installing %s v%s using \"%s\"..." - % (name, version, " ".join(cmd))) - success = self.callProcess(pkg, cmd) - - if not success: - 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 callProcess(self, 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 eslintModuleHasIssues(self): - has_issues = False - node_module_path = os.path.join(self.get_eslint_module_path(), "node_modules") - - for pkg in ESLINT_PACKAGES: - name, req_version = pkg.split("@") - path = os.path.join(node_module_path, name, "package.json") - - if not os.path.exists(path): - print("%s v%s needs to be installed locally." % (name, req_version)) - has_issues = True - continue - - data = json.load(open(path)) - - if data["version"] != req_version: - print("%s v%s should be v%s." % (name, version, req_version)) - has_issues = True - - return has_issues - - def node_package_installed(self, package_name="", globalInstall=False, cwd=None): - try: - npmPath = self.getNodeOrNpmPath("npm") - - cmd = [npmPath, "ls", "--parseable", package_name] - - if globalInstall: - cmd.append("-g") - - with open(os.devnull, "w") as fnull: - subprocess.check_call(cmd, stdout=fnull, stderr=fnull, cwd=cwd) - - return True - except subprocess.CalledProcessError: - return False - - def getPossibleNodePathsWin(self): - """ - 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 getNodeOrNpmPath(self, filename, minversion=None): - """ - Return the nodejs or npm path. - """ - if platform.system() == "Windows": - for ext in [".cmd", ".exe", ""]: - try: - nodeOrNpmPath = which.which(filename + ext, - path=self.getPossibleNodePathsWin()) - if self.is_valid(nodeOrNpmPath, minversion): - return nodeOrNpmPath - except which.WhichError: - pass - else: - try: - nodeOrNpmPath = which.which(filename) - if self.is_valid(nodeOrNpmPath, minversion): - return nodeOrNpmPath - except which.WhichError: - pass - - if filename == "node": - print(NODE_NOT_FOUND_MESSAGE) - elif filename == "npm": - print(NPM_NOT_FOUND_MESSAGE) - - if platform.system() == "Windows": - appPaths = self.getPossibleNodePathsWin() - - for p in appPaths: - 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(self, 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(self): - fullpath = os.path.abspath(sys.modules['__main__'].__file__) - return os.path.dirname(fullpath) - - def get_eslint_module_path(self): - return os.path.join(self.get_project_root(), "testing", "eslint") - - def _prompt_yn(self, msg): - if not sys.stdin.isatty(): - return False - - print('%s? [Y/n]' % msg) - - while True: - choice = raw_input().lower().strip() - - if not choice: - return True - - if choice in ('y', 'yes'): - return True - - if choice in ('n', 'no'): - return False - - print('Must reply with one of {yes, no, y, n}.') diff --git a/taskcluster/ci/legacy/tasks/branches/base_jobs.yml b/taskcluster/ci/legacy/tasks/branches/base_jobs.yml index 0ce120d69df7..d99ba2fb8ab0 100644 --- a/taskcluster/ci/legacy/tasks/branches/base_jobs.yml +++ b/taskcluster/ci/legacy/tasks/branches/base_jobs.yml @@ -528,7 +528,7 @@ tasks: - '**/.eslintignore' - '**/*eslintrc*' # The plugin implementing custom checks. - - 'testing/eslint/eslint-plugin-mozilla/**' + - 'tools/lint/eslint/eslint-plugin-mozilla/**' # Other misc lint related files. - 'tools/lint/**' - 'testing/docker/lint/**' diff --git a/taskcluster/ci/legacy/tasks/tests/eslint-gecko.yml b/taskcluster/ci/legacy/tasks/tests/eslint-gecko.yml index fda48d443a75..6eed081d984d 100644 --- a/taskcluster/ci/legacy/tasks/tests/eslint-gecko.yml +++ b/taskcluster/ci/legacy/tasks/tests/eslint-gecko.yml @@ -24,12 +24,12 @@ task: - -cx - > tc-vcs checkout ./gecko {{base_repository}} {{head_repository}} {{head_rev}} {{head_ref}} && - cd gecko/testing/eslint && + cd gecko/tools/lint/eslint && /build/tooltool.py fetch -m manifest.tt && tar xvfz eslint.tar.gz && rm eslint.tar.gz && - cd ../.. && - testing/eslint/node_modules/.bin/eslint --quiet --plugin html --ext [.js,.jsm,.jsx,.xml,.html] -f tools/lint/eslint-formatter . + cd ../../.. && + tools/lint/eslint/node_modules/.bin/eslint --quiet --plugin html --ext [.js,.jsm,.jsx,.xml,.html] -f tools/lint/eslint-formatter . extra: locations: diff --git a/testing/eslint/eslint-plugin-mozilla/LICENSE b/tools/lint/eslint/eslint-plugin-mozilla/LICENSE similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/LICENSE rename to tools/lint/eslint/eslint-plugin-mozilla/LICENSE diff --git a/testing/eslint/eslint-plugin-mozilla/docs/balanced-listeners.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/balanced-listeners.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/balanced-listeners.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/balanced-listeners.rst diff --git a/testing/eslint/eslint-plugin-mozilla/docs/import-browserjs-globals.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/import-browserjs-globals.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/import-browserjs-globals.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/import-browserjs-globals.rst diff --git a/testing/eslint/eslint-plugin-mozilla/docs/import-globals.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/import-globals.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/import-globals.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/import-globals.rst diff --git a/testing/eslint/eslint-plugin-mozilla/docs/import-headjs-globals.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/import-headjs-globals.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/import-headjs-globals.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/import-headjs-globals.rst diff --git a/testing/eslint/eslint-plugin-mozilla/docs/index.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/index.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/index.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/index.rst diff --git a/testing/eslint/eslint-plugin-mozilla/docs/mark-test-function-used.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/mark-test-function-used.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/mark-test-function-used.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/mark-test-function-used.rst diff --git a/testing/eslint/eslint-plugin-mozilla/docs/no-aArgs.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/no-aArgs.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/no-aArgs.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/no-aArgs.rst diff --git a/testing/eslint/eslint-plugin-mozilla/docs/no-cpows-in-tests.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/no-cpows-in-tests.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/no-cpows-in-tests.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/no-cpows-in-tests.rst diff --git a/testing/eslint/eslint-plugin-mozilla/docs/reject-importGlobalProperties.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/reject-importGlobalProperties.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/reject-importGlobalProperties.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/reject-importGlobalProperties.rst diff --git a/testing/eslint/eslint-plugin-mozilla/docs/var-only-at-top-level.rst b/tools/lint/eslint/eslint-plugin-mozilla/docs/var-only-at-top-level.rst similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/docs/var-only-at-top-level.rst rename to tools/lint/eslint/eslint-plugin-mozilla/docs/var-only-at-top-level.rst diff --git a/testing/eslint/eslint-plugin-mozilla/lib/globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/globals.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js diff --git a/testing/eslint/eslint-plugin-mozilla/lib/helpers.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/helpers.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js diff --git a/testing/eslint/eslint-plugin-mozilla/lib/index.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/index.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/index.js diff --git a/testing/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js diff --git a/testing/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc diff --git a/testing/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js diff --git a/testing/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js diff --git a/testing/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js diff --git a/testing/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js diff --git a/testing/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 similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js diff --git a/testing/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js diff --git a/testing/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 similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js diff --git a/testing/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js diff --git a/testing/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 similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js rename to tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js diff --git a/testing/eslint/eslint-plugin-mozilla/moz.build b/tools/lint/eslint/eslint-plugin-mozilla/moz.build similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/moz.build rename to tools/lint/eslint/eslint-plugin-mozilla/moz.build diff --git a/testing/eslint/eslint-plugin-mozilla/package.json b/tools/lint/eslint/eslint-plugin-mozilla/package.json similarity index 100% rename from testing/eslint/eslint-plugin-mozilla/package.json rename to tools/lint/eslint/eslint-plugin-mozilla/package.json diff --git a/testing/eslint/manifest.tt b/tools/lint/eslint/manifest.tt similarity index 100% rename from testing/eslint/manifest.tt rename to tools/lint/eslint/manifest.tt diff --git a/testing/eslint/npm-shrinkwrap.json b/tools/lint/eslint/npm-shrinkwrap.json similarity index 100% rename from testing/eslint/npm-shrinkwrap.json rename to tools/lint/eslint/npm-shrinkwrap.json diff --git a/testing/eslint/package.json b/tools/lint/eslint/package.json similarity index 100% rename from testing/eslint/package.json rename to tools/lint/eslint/package.json diff --git a/testing/eslint/update b/tools/lint/eslint/update similarity index 93% rename from testing/eslint/update rename to tools/lint/eslint/update index b554670dcc23..940fe51bcc6d 100755 --- a/testing/eslint/update +++ b/tools/lint/eslint/update @@ -1,5 +1,5 @@ #!/bin/sh -# Force the scripts working directory to be projdir/testing/eslint. +# Force the scripts working directory to be projdir/tools/lint/eslint. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd $DIR @@ -55,7 +55,7 @@ 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 testing/eslint" +./tooltool.py upload --authentication-file=~/.tooltool-token --message "node_modules folder update for tools/lint/eslint" echo "Cleaning up..." rm eslint.tar.gz diff --git a/tools/lint/mach_commands.py b/tools/lint/mach_commands.py index 2c52f3e8089c..6218344ec348 100644 --- a/tools/lint/mach_commands.py +++ b/tools/lint/mach_commands.py @@ -4,7 +4,15 @@ from __future__ import absolute_import, print_function, unicode_literals +import argparse +import json +import logging import os +import platform +import subprocess +import sys +import which +from distutils.version import LooseVersion from mozbuild.base import ( MachCommandBase, @@ -22,6 +30,39 @@ from mach.decorators import ( here = os.path.abspath(os.path.dirname(__file__)) +ESLINT_PACKAGES = [ + "eslint@2.9.0", + "eslint-plugin-html@1.4.0", + "eslint-plugin-mozilla@0.0.3", + "eslint-plugin-react@4.2.3" +] + +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() + + @CommandProvider class MachCommands(MachCommandBase): @@ -93,3 +134,274 @@ class MachCommands(MachCommandBase): lints.append(os.path.join(here, f)) return lints + + @Command('eslint', category='devenv', + description='Run eslint or help configure eslint for optimal development.') + @CommandArgument('-s', '--setup', default=False, action='store_true', + help='configure eslint for optimal development.') + @CommandArgument('-e', '--ext', default='[.js,.jsm,.jsx,.xml,.html]', + help='Filename extensions to lint, default: "[.js,.jsm,.jsx,.xml,.html]".') + @CommandArgument('-b', '--binary', default=None, + help='Path to eslint binary.') + @CommandArgument('args', nargs=argparse.REMAINDER) # Passed through to eslint. + def eslint(self, setup, ext=None, binary=None, args=None): + '''Run eslint.''' + + module_path = self.get_eslint_module_path() + + # eslint requires at least node 4.2.3 + nodePath = self.getNodeOrNpmPath("node", LooseVersion("4.2.3")) + if not nodePath: + return 1 + + if setup: + return self.eslint_setup() + + npmPath = self.getNodeOrNpmPath("npm") + if not npmPath: + return 1 + + if self.eslintModuleHasIssues(): + install = self._prompt_yn("\nContinuing will automatically fix " + "these issues. Would you like to " + "continue") + if install: + self.eslint_setup() + else: + return 1 + + # 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 + + self.log(logging.INFO, 'eslint', {'binary': binary, 'args': args}, + 'Running {binary}') + + args = 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', + '--ext', ext, # This keeps ext as a single argument. + ] + args + + success = self.run_process( + cmd_args, + pass_thru=True, # Allow user to run eslint interactively. + ensure_exit_code=False, # Don't throw on non-zero exit code. + require_unix_environment=True # eslint is not a valid Win32 binary. + ) + + self.log(logging.INFO, 'eslint', {'msg': ('No errors' if success == 0 else 'Errors')}, + 'Finished eslint. {msg} encountered.') + return success + + def eslint_setup(self, update_only=False): + """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 = self.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) + + npmPath = self.getNodeOrNpmPath("npm") + if not npmPath: + return 1 + + # Install eslint and necessary plugins. + for pkg in ESLINT_PACKAGES: + name, version = pkg.split("@") + success = False + + if self.node_package_installed(pkg, cwd=module_path): + success = True + else: + if pkg.startswith("eslint-plugin-mozilla"): + cmd = [npmPath, "install", + os.path.join(module_path, "eslint-plugin-mozilla")] + else: + cmd = [npmPath, "install", pkg] + + print("Installing %s v%s using \"%s\"..." + % (name, version, " ".join(cmd))) + success = self.callProcess(pkg, cmd) + + if not success: + 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 callProcess(self, 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 eslintModuleHasIssues(self): + has_issues = False + node_module_path = os.path.join(self.get_eslint_module_path(), "node_modules") + + for pkg in ESLINT_PACKAGES: + name, req_version = pkg.split("@") + path = os.path.join(node_module_path, name, "package.json") + + if not os.path.exists(path): + print("%s v%s needs to be installed locally." % (name, req_version)) + has_issues = True + continue + + data = json.load(open(path)) + + if data["version"] != req_version: + print("%s v%s should be v%s." % (name, data["version"], req_version)) + has_issues = True + + return has_issues + + def node_package_installed(self, package_name="", globalInstall=False, cwd=None): + try: + npmPath = self.getNodeOrNpmPath("npm") + + cmd = [npmPath, "ls", "--parseable", package_name] + + if globalInstall: + cmd.append("-g") + + with open(os.devnull, "w") as fnull: + subprocess.check_call(cmd, stdout=fnull, stderr=fnull, cwd=cwd) + + return True + except subprocess.CalledProcessError: + return False + + def getPossibleNodePathsWin(self): + """ + 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 getNodeOrNpmPath(self, filename, minversion=None): + """ + Return the nodejs or npm path. + """ + if platform.system() == "Windows": + for ext in [".cmd", ".exe", ""]: + try: + nodeOrNpmPath = which.which(filename + ext, + path=self.getPossibleNodePathsWin()) + if self.is_valid(nodeOrNpmPath, minversion): + return nodeOrNpmPath + except which.WhichError: + pass + else: + try: + nodeOrNpmPath = which.which(filename) + if self.is_valid(nodeOrNpmPath, minversion): + return nodeOrNpmPath + except which.WhichError: + pass + + if filename == "node": + print(NODE_NOT_FOUND_MESSAGE) + elif filename == "npm": + print(NPM_NOT_FOUND_MESSAGE) + + if platform.system() == "Windows": + appPaths = self.getPossibleNodePathsWin() + + for p in appPaths: + 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(self, 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(self): + fullpath = os.path.abspath(sys.modules['__main__'].__file__) + return os.path.dirname(fullpath) + + def get_eslint_module_path(self): + return os.path.join(self.get_project_root(), "testing", "eslint") + + def _prompt_yn(self, msg): + if not sys.stdin.isatty(): + return False + + print('%s? [Y/n]' % msg) + + while True: + choice = raw_input().lower().strip() + + if not choice: + return True + + if choice in ('y', 'yes'): + return True + + if choice in ('n', 'no'): + return False + + print('Must reply with one of {yes, no, y, n}.')