Bug 1379151 - Add --fix and --edit to mozlint, r=standard8

While --fix previously worked with eslint, it is now more official (will show
up in the |mach lint --help|).  ESlint is still the only thing that implements
it, but we can implement it for flake8 using the `autopep8` module.

--edit is a new concept that will open an editor for each failing file to let
you fix the errors manually.  For now it is very naive (just opens the file),
and is only really useful if you have an editor integration for the linter(s).
But in the future I'd like to have editor-specific implementations for this.
For example, with vim, we can use -q to pass in an error file that will start
the editor pre-populated with a list of all errors that can then be easily
jumped to. Other editors may just open up to the line containing the error.

--fix and --edit can be used in conjunction with one another. Doing that means
only errors that can't be fixed automatically will show up in your editor.


MozReview-Commit-ID: 5JJJhMIrMIB

--HG--
extra : rebase_source : 2f78a77a91133d7fcc5620ecd5caa500decbce1b
This commit is contained in:
Andrew Halberstadt 2017-08-10 09:21:17 -04:00
parent 015f8de9b5
commit 10f8b7e161
4 changed files with 90 additions and 1 deletions

View File

@ -5,9 +5,12 @@
from __future__ import print_function, unicode_literals
import os
import subprocess
import sys
from argparse import REMAINDER, ArgumentParser
from mozlint.formatters import all_formatters
SEARCH_PATHS = []
@ -36,6 +39,7 @@ class MozlintParser(ArgumentParser):
[['-f', '--format'],
{'dest': 'fmt',
'default': 'stylish',
'choices': all_formatters.keys(),
'help': "Formatter to use. Defaults to 'stylish'.",
}],
[['-n', '--no-filter'],
@ -63,6 +67,18 @@ class MozlintParser(ArgumentParser):
"can be used to only consider staged files. Works with "
"mercurial or git.",
}],
[['--fix'],
{'action': 'store_true',
'default': False,
'help': "Fix lint errors if possible. Any errors that could not be fixed "
"will be printed as normal."
}],
[['--edit'],
{'action': 'store_true',
'default': False,
'help': "Each file containing lint errors will be opened in $EDITOR one after "
"the other."
}],
[['extra_args'],
{'nargs': REMAINDER,
'help': "Extra arguments that will be forwarded to the underlying linter.",
@ -87,8 +103,14 @@ class MozlintParser(ArgumentParser):
# when using mach's dispatch functionality.
args, extra = ArgumentParser.parse_known_args(self, *args, **kwargs)
args.extra_args = extra
self.validate(args)
return args, extra
def validate(self, args):
if args.edit and not os.environ.get('EDITOR'):
self.error("must set the $EDITOR environment variable to use --edit")
def find_linters(linters=None):
lints = []
@ -113,7 +135,7 @@ def find_linters(linters=None):
return lints
def run(paths, linters, fmt, outgoing, workdir, list_linters=None, **lintargs):
def run(paths, linters, fmt, outgoing, workdir, edit, list_linters=None, **lintargs):
from mozlint import LintRoller, formatters
if list_linters:
@ -133,6 +155,13 @@ def run(paths, linters, fmt, outgoing, workdir, list_linters=None, **lintargs):
# run all linters
results = lint.roll(paths, outgoing=outgoing, workdir=workdir)
if edit:
editor = os.environ['EDITOR']
for path in results:
subprocess.call([editor, path])
return 1 if lint.failed else 0
formatter = formatters.get(fmt)
# Encode output with 'replace' to avoid UnicodeEncodeErrors on

View File

@ -11,6 +11,10 @@ def badreturncode(files, config, **lintargs):
def external(files, config, **lintargs):
if lintargs.get('fix'):
# mimics no results because they got fixed
return []
results = []
for path in files:
with open(path, 'r') as fh:

View File

@ -1,6 +1,7 @@
[DEFAULT]
subsuite = mozlint, os == "linux"
[test_cli.py]
[test_formatters.py]
[test_parser.py]
[test_roller.py]

View File

@ -0,0 +1,55 @@
# 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 os
import sys
import pytest
from mozlint import cli
here = os.path.abspath(os.path.dirname(__file__))
@pytest.fixture
def parser():
return cli.MozlintParser()
@pytest.fixture
def run(parser, lintdir, files):
if lintdir not in cli.SEARCH_PATHS:
cli.SEARCH_PATHS.append(lintdir)
def inner(args=None):
args = args or []
args.extend(files)
lintargs = vars(parser.parse_args(args))
lintargs['root'] = here
return cli.run(**lintargs)
return inner
def test_cli_run_with_fix(run, capfd):
ret = run(['-f', 'json', '--fix', '--linter', 'external'])
out, err = capfd.readouterr()
assert ret == 0
assert out.endswith('{}\n')
def test_cli_run_with_edit(run, parser, capfd):
os.environ['EDITOR'] = 'echo'
ret = run(['-f', 'json', '--edit', '--linter', 'external'])
out = capfd.readouterr()[0].strip()
assert ret == 0
assert os.path.basename(out) == 'foobar.js'
del os.environ['EDITOR']
with pytest.raises(SystemExit):
parser.parse_args(['--edit'])
if __name__ == '__main__':
sys.exit(pytest.main(['--verbose', __file__]))