Bug 1387555 - [mozlint] Make use of quickfix when using --edit with vim/nvim, r?dylan draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 09 Aug 2017 10:03:02 -0400
changeset 645014 92d726d2065a1cea322486af965ed10fb6406ab9
parent 645013 28edb0a33c8db39328cdad2c7ff76e3050bd79f4
child 725786 751fc1dd8dadaaa014bd9dd72f1a102c8f80087f
push id73629
push userahalberstadt@mozilla.com
push dateFri, 11 Aug 2017 18:50:20 +0000
reviewersdylan
bugs1387555
milestone57.0a1
Bug 1387555 - [mozlint] Make use of quickfix when using --edit with vim/nvim, r?dylan MozReview-Commit-ID: BlJbWVv1CeO
python/mozlint/mozlint/cli.py
python/mozlint/mozlint/editor.py
python/mozlint/mozlint/formatters/compact.py
python/mozlint/test/test_cli.py
tools/lint/docs/usage.rst
--- a/python/mozlint/mozlint/cli.py
+++ b/python/mozlint/mozlint/cli.py
@@ -1,16 +1,15 @@
 # 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 REMAINDER, ArgumentParser
 
 from mozlint.formatters import all_formatters
 
 SEARCH_PATHS = []
 
 
@@ -137,34 +136,33 @@ def find_linters(linters=None):
                 continue
 
             lints.append(os.path.join(search_path, f))
     return lints
 
 
 def run(paths, linters, fmt, outgoing, workdir, edit, list_linters=None, **lintargs):
     from mozlint import LintRoller, formatters
+    from mozlint.editor import edit_results
 
     if list_linters:
         lint_paths = find_linters(linters)
         print("Available linters: {}".format(
             [os.path.splitext(os.path.basename(l))[0] for l in lint_paths]
         ))
         return 0
 
     lint = LintRoller(**lintargs)
     lint.read(find_linters(linters))
 
     # 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])
+    if edit and results:
+        edit_results(results)
         results = lint.roll(results.keys())
 
     formatter = formatters.get(fmt)
 
     # Encode output with 'replace' to avoid UnicodeEncodeErrors on
     # environments that aren't using utf-8.
     out = formatter(results, failed=lint.failed).encode(
                     sys.stdout.encoding or 'ascii', 'replace')
new file mode 100644
--- /dev/null
+++ b/python/mozlint/mozlint/editor.py
@@ -0,0 +1,53 @@
+# 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 unicode_literals, print_function
+
+import os
+import subprocess
+import tempfile
+
+from mozlint import formatters
+
+
+def get_editor():
+    return os.environ.get('EDITOR')
+
+
+def edit_results(results):
+    if not results:
+        return
+
+    editor = get_editor()
+    if not editor:
+        print('warning: could not find a default editor')
+        return
+
+    name = os.path.basename(editor)
+    if name in ('vim', 'nvim', 'gvim'):
+        cmd = [
+            editor,
+            # need errorformat to match both Error and Warning, with or without a column
+            '--cmd', 'set errorformat+=%f:\\ line\\ %l\\\\,\\ col\\ %c\\\\,\\ %trror\\ -\\ %m',
+            '--cmd', 'set errorformat+=%f:\\ line\\ %l\\\\,\\ col\\ %c\\\\,\\ %tarning\\ -\\ %m',
+            '--cmd', 'set errorformat+=%f:\\ line\\ %l\\\\,\\ %trror\\ -\\ %m',
+            '--cmd', 'set errorformat+=%f:\\ line\\ %l\\\\,\\ %tarning\\ -\\ %m',
+            # start with quickfix window opened
+            '-c', 'copen',
+            # running with -q seems to open an empty buffer in addition to the
+            # first file, this removes that empty buffer
+            '-c', '1bd',
+        ]
+
+        with tempfile.NamedTemporaryFile() as fh:
+            s = formatters.get('compact', summary=False)(results)
+            fh.write(s)
+            fh.flush()
+
+            cmd.extend(['-q', fh.name])
+            subprocess.call(cmd)
+
+    else:
+        for path, errors in results.iteritems():
+            subprocess.call([editor, path])
--- a/python/mozlint/mozlint/formatters/compact.py
+++ b/python/mozlint/mozlint/formatters/compact.py
@@ -8,16 +8,17 @@ from ..result import ResultContainer
 
 
 class CompactFormatter(object):
     """Formatter for compact output.
 
     This formatter prints one error per line, mimicking the
     eslint 'compact' formatter.
     """
+    # If modifying this format, please also update the vim errorformats in editor.py
     fmt = "{path}: line {lineno}{column}, {level} - {message} ({rule})"
 
     def __init__(self, summary=True):
         self.summary = summary
 
     def __call__(self, result, **kwargs):
         message = []
         num_problems = 0
--- a/python/mozlint/test/test_cli.py
+++ b/python/mozlint/test/test_cli.py
@@ -42,18 +42,18 @@ def test_cli_run_with_edit(run, parser, 
     os.environ['EDITOR'] = 'echo'
 
     ret = run(['-f', 'compact', '--edit', '--linter', 'external'])
     out, err = capfd.readouterr()
     out = out.splitlines()
     assert ret == 1
     assert len(out) == 5
     assert out[0].endswith('foobar.js')  # from the `echo` editor
-    assert "files/foobar.js: line 1, col 1, Error" in out[1]
-    assert "files/foobar.js: line 2, col 1, Error" in out[2]
+    assert "foobar.js: line 1, col 1, Error" in out[1]
+    assert "foobar.js: line 2, col 1, Error" in out[2]
 
     del os.environ['EDITOR']
     with pytest.raises(SystemExit):
         parser.parse_args(['--edit'])
 
 
 if __name__ == '__main__':
     sys.exit(pytest.main(['--verbose', __file__]))
--- a/tools/lint/docs/usage.rst
+++ b/tools/lint/docs/usage.rst
@@ -70,8 +70,36 @@ To enable a pre-push git hook, run the f
     $ ln -s /path/to/gecko/tools/lint/hooks.py .git/hooks/pre-push
 
 
 To enable a pre-commit git hook, run the following command:
 
 .. parsed-literal::
 
     $ ln -s /path/to/gecko/tools/lint/hooks.py .git/hooks/pre-commit
+
+
+Fixing Lint Errors
+==================
+
+``Mozlint`` has a best-effort ability to fix lint errors:
+
+.. parsed-literal::
+
+    $ ./mach lint --fix
+
+Not all linters support fixing, and even the ones that do can not usually fix
+all types of errors. Any errors that cannot be automatically fixed, will be
+printed to stdout like normal. In that case, you can also fix errors manually:
+
+.. parsed-literal::
+
+    $ ./mach lint --edit
+
+This requires the $EDITOR environment variable be defined. For most editors,
+this will simply open each file containing errors one at a time. For vim (or
+neovim), this will populate the `quickfix list`_ with the errors.
+
+The ``--fix`` and ``--edit`` arguments can be combined, in which case any
+errors that can be fixed automatically will be, and the rest will be opened
+with your $EDITOR.
+
+.. _quickfix list: http://vimdoc.sourceforge.net/htmldoc/quickfix.html