Bug 1281899 - [mozlint] Create cli module and move logic from tools/lint/mach_commands.py there, r?smacleod
There is currently no built-in user interface to mozlint. The only existing interface is the
external cli provided by |mach lint|. However, in the future mozlint may need to be used in a
context where mach isn't readily available (i.e version-control-tools). This patch basically
just moves the cli logic out of mach_commands.py, and into mozlint core. That way it can be
re-used in other places without needing to be re-implemented.
The |mach lint setup| subcommand was removed because apparently subcommands don't work with
the parser attribute. Nothing was using it yet anyway, so I removed it for now. It may get
re-added in some form in the future.
MozReview-Commit-ID: aOGp2Yrncs
new file mode 100644
--- /dev/null
+++ b/python/mozlint/mozlint/cli.py
@@ -0,0 +1,91 @@
+# 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 print_function, unicode_literals
+
+import os
+import subprocess
+import sys
+from argparse import ArgumentParser
+
+
+SEARCH_PATHS = []
+
+
+class MozlintParser(ArgumentParser):
+ arguments = [
+ [['paths'],
+ {'nargs': '*',
+ 'default': None,
+ 'help': "Paths to file or directories to lint, like "
+ "'browser/components/loop' or 'mobile/android'. "
+ "Defaults to the current directory if not given.",
+ }],
+ [['-l', '--linter'],
+ {'dest': 'linters',
+ 'default': [],
+ 'action': 'append',
+ 'help': "Linters to run, e.g 'eslint'. By default all linters "
+ "are run for all the appropriate files.",
+ }],
+ [['-f', '--format'],
+ {'dest': 'fmt',
+ 'default': 'stylish',
+ 'help': "Formatter to use. Defaults to 'stylish'.",
+ }],
+ [['-n', '--no-filter'],
+ {'dest': 'use_filters',
+ 'default': True,
+ 'action': 'store_false',
+ 'help': "Ignore all filtering. This is useful for quickly "
+ "testing a directory that otherwise wouldn't be run, "
+ "without needing to modify the config file.",
+ }],
+ ]
+
+ def __init__(self, **kwargs):
+ ArgumentParser.__init__(self, usage=self.__doc__, **kwargs)
+
+ for cli, args in self.arguments:
+ self.add_argument(*cli, **args)
+
+
+def find_linters(self, linters=None):
+ lints = []
+ for search_path in SEARCH_PATHS:
+ if not os.path.isdir(search_path):
+ continue
+
+ files = os.listdir(search_path)
+ for f in files:
+ name, ext = os.path.splitext(f)
+ if ext != '.lint':
+ continue
+
+ if linters and name not in linters:
+ continue
+
+ lints.append(os.path.join(search_path, f))
+ return lints
+
+
+def run(paths, linters, fmt, **lintargs):
+ from mozlint import LintRoller, formatters
+ paths = paths or ['.']
+
+ lint = LintRoller(**lintargs)
+ lint.read(find_linters(linters))
+
+ # run all linters
+ results = lint.roll(paths)
+
+ formatter = formatters.get(fmt)
+ print(formatter(results))
+ return 1 if results else 0
+
+
+if __name__ == '__main__':
+ parser = MozlintParser()
+ args = vars(parser.parse_args())
+ sys.exit(run(**args))
--- a/python/mozlint/mozlint/types.py
+++ b/python/mozlint/mozlint/types.py
@@ -27,17 +27,17 @@ class BaseType(object):
"""
exclude = lintargs.get('exclude', [])
exclude.extend(linter.get('exclude', []))
if lintargs.get('use_filters', True):
paths, exclude = filterpaths(paths, linter.get('include'), exclude)
if not paths:
- print("{}: No files to lint for specified paths!".format(linter['name']))
+ print("{}: no files to lint in specified paths".format(linter['name']))
return
lintargs['exclude'] = exclude
if self.batch:
return self._lint(paths, linter, **lintargs)
errors = []
try:
--- a/tools/lint/mach_commands.py
+++ b/tools/lint/mach_commands.py
@@ -18,17 +18,16 @@ from mozbuild.base import (
MachCommandBase,
)
from mach.decorators import (
CommandArgument,
CommandProvider,
Command,
- SubCommand,
)
here = os.path.abspath(os.path.dirname(__file__))
ESLINT_PACKAGES = [
"eslint@2.9.0",
@@ -58,92 +57,34 @@ 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()
+def setup_argument_parser():
+ from mozlint import cli
+ return cli.MozlintParser()
+
+
@CommandProvider
class MachCommands(MachCommandBase):
@Command(
'lint', category='devenv',
- description='Run linters.')
- @CommandArgument(
- 'paths', nargs='*', default=None,
- help="Paths to file or directories to lint, like "
- "'browser/components/loop' or 'mobile/android'. "
- "Defaults to the current directory if not given.")
- @CommandArgument(
- '-l', '--linter', dest='linters', default=None, action='append',
- help="Linters to run, e.g 'eslint'. By default all linters are run "
- "for all the appropriate files.")
- @CommandArgument(
- '-f', '--format', dest='fmt', default='stylish',
- help="Formatter to use. Defaults to 'stylish'.")
- @CommandArgument(
- '-n', '--no-filter', dest='use_filters', default=True, action='store_false',
- help="Ignore all filtering. This is useful for quickly testing a "
- "directory that otherwise wouldn't be run, without needing to "
- "modify the config file.")
- def lint(self, paths, linters=None, fmt='stylish', **lintargs):
+ description='Run linters.',
+ parser=setup_argument_parser)
+ def lint(self, paths, linters, fmt, **lintargs):
"""Run linters."""
- from mozlint import LintRoller, formatters
-
- paths = paths or ['.']
-
- lint_files = self.find_linters(linters)
-
+ from mozlint import cli
lintargs['exclude'] = ['obj*']
- lint = LintRoller(**lintargs)
- lint.read(lint_files)
-
- # run all linters
- results = lint.roll(paths)
-
- status = 0
- if results:
- status = 1
-
- formatter = formatters.get(fmt)
- print(formatter(results))
- return status
-
- @SubCommand('lint', 'setup',
- "Setup required libraries for specified lints.")
- @CommandArgument(
- '-l', '--linter', dest='linters', default=None, action='append',
- help="Linters to run, e.g 'eslint'. By default all linters are run "
- "for all the appropriate files.")
- def lint_setup(self, linters=None, **lintargs):
- from mozlint import LintRoller
-
- lint_files = self.find_linters(linters)
- lint = LintRoller(lintargs=lintargs)
- lint.read(lint_files)
-
- for l in lint.linters:
- if 'setup' in l:
- l['setup']()
-
- def find_linters(self, linters=None):
- lints = []
- files = os.listdir(here)
- for f in files:
- name, ext = os.path.splitext(f)
- if ext != '.lint':
- continue
-
- if linters and name not in linters:
- continue
-
- lints.append(os.path.join(here, f))
- return lints
+ cli.SEARCH_PATHS.append(here)
+ return cli.run(paths, linters, fmt, **lintargs)
@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,