Bug 1405304 - Add Unix formatter for mozlint. r?ahal draft
authorAndreas Tolfsen <ato@sny.no>
Tue, 03 Oct 2017 14:45:17 +0100
changeset 674439 f890ab76b5c993648f2998e413a35b50e6dd5cd9
parent 674396 9be05b2177667ed8221f9da4fdcc200dbdf3de62
child 734325 0d2fddc16367579112d1bb77e82a422c6ef6d9b5
push id82831
push userbmo:ato@sny.no
push dateTue, 03 Oct 2017 18:52:05 +0000
reviewersahal
bugs1405304
milestone58.0a1
Bug 1405304 - Add Unix formatter for mozlint. r?ahal This patch introduces a new report formatter for the mozlint framework used by "./mach lint" that respects Unix output conventions, popularised by grep(1), compilers, and preprocessors. The output format looks like this: testing/marionette/driver.js:1153:48: no-unused-vars error: 'resp' is defined but never used. Many editors (ed, Emacs, vi, Acme) recognise this format, allowing users to interact with the output like a hyperlink to jump to the specified location in a file. MozReview-Commit-ID: 3IyiFmNbtMY
python/mozlint/mozlint/formatters/__init__.py
python/mozlint/mozlint/formatters/unix.py
python/mozlint/test/test_formatters.py
--- a/python/mozlint/mozlint/formatters/__init__.py
+++ b/python/mozlint/mozlint/formatters/__init__.py
@@ -5,25 +5,27 @@
 from __future__ import absolute_import
 
 import json
 
 from ..result import ResultEncoder
 from .compact import CompactFormatter
 from .stylish import StylishFormatter
 from .treeherder import TreeherderFormatter
+from .unix import UnixFormatter
 
 
 class JSONFormatter(object):
     def __call__(self, results, **kwargs):
         return json.dumps(results, cls=ResultEncoder)
 
 
 all_formatters = {
     'compact': CompactFormatter,
     'json': JSONFormatter,
     'stylish': StylishFormatter,
     'treeherder': TreeherderFormatter,
+    'unix': UnixFormatter,
 }
 
 
 def get(name, **fmtargs):
     return all_formatters[name](**fmtargs)
new file mode 100644
--- /dev/null
+++ b/python/mozlint/mozlint/formatters/unix.py
@@ -0,0 +1,34 @@
+# 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, unicode_literals
+
+import os
+
+from ..result import ResultContainer
+
+
+class UnixFormatter(object):
+    """Formatter that respects Unix output conventions frequently
+    employed by preprocessors and compilers.  The format is
+    `<FILENAME>:<LINE>[:<COL>]: <RULE> <LEVEL>: <MESSAGE>`.
+
+    """
+    fmt = "{path}:{lineno}:{column} {rule} {level}: {message}"
+
+    def __call__(self, result, **kwargs):
+        msg = []
+
+        for path, errors in sorted(result.iteritems()):
+            for err in errors:
+                assert isinstance(err, ResultContainer)
+
+                slots = {s: getattr(err, s) for s in err.__slots__}
+                slots["path"] = os.path.relpath(slots["path"])
+                slots["column"] = "%d:" % slots["column"] if slots["column"] else ""
+                slots["rule"] = slots["rule"] or slots["linter"]
+
+                msg.append(self.fmt.format(**slots))
+
+        return "\n".join(msg)
--- a/python/mozlint/test/test_formatters.py
+++ b/python/mozlint/test/test_formatters.py
@@ -1,23 +1,28 @@
 # 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, unicode_literals
 
 import json
+import os
 from collections import defaultdict
 
 import mozunit
 import pytest
 
 from mozlint import ResultContainer
 from mozlint import formatters
 
+NORMALISED_PATHS = {
+    'abc': os.path.normpath('a/b/c.txt'),
+    'def': os.path.normpath('d/e/f.txt'),
+}
 
 EXPECTED = {
     'compact': {
         'kwargs': {},
         'format': """
 a/b/c.txt: line 1, Error - oh no foo (foo)
 a/b/c.txt: line 4, Error - oh no baz (baz)
 d/e/f.txt: line 4, col 2, Warning - oh no bar (bar-not-allowed)
@@ -43,16 +48,24 @@ d/e/f.txt
     'treeherder': {
         'kwargs': {},
         'format': """
 TEST-UNEXPECTED-ERROR | a/b/c.txt:1 | oh no foo (foo)
 TEST-UNEXPECTED-ERROR | a/b/c.txt:4 | oh no baz (baz)
 TEST-UNEXPECTED-WARNING | d/e/f.txt:4:2 | oh no bar (bar-not-allowed)
 """.strip(),
     },
+    'unix': {
+        'kwargs': {},
+        'format': """
+{abc}:1: foo error: oh no foo
+{abc}:4: baz error: oh no baz
+{def}:4:2: bar-not-allowed warning: oh no bar
+""".format(**NORMALISED_PATHS).strip(),
+    },
 }
 
 
 @pytest.fixture
 def results(scope='module'):
     containers = (
         ResultContainer(
             linter='foo',