Bug 1306122 - [mozlint] Create a compact formatter that mimics the eslint 'compact' format, r=armenzg
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 04 Aug 2017 10:53:43 -0400
changeset 373451 adf616622c4a9492abaef2cfd18947842ba6a759
parent 373450 85a69f9c265a7f474d3e11106fc02fd24c7ab9b6
child 373452 02f357c8e98090bcf69c1238ace5e72801f4bdab
push id32303
push usercbook@mozilla.com
push dateWed, 09 Aug 2017 09:34:07 +0000
treeherdermozilla-central@c93fa2271ee7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarmenzg
bugs1306122
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1306122 - [mozlint] Create a compact formatter that mimics the eslint 'compact' format, r=armenzg MozReview-Commit-ID: 5JJJhMIrMIB
python/mozlint/mozlint/cli.py
python/mozlint/mozlint/formatters/__init__.py
python/mozlint/mozlint/formatters/compact.py
python/mozlint/test/test_formatters.py
--- a/python/mozlint/mozlint/cli.py
+++ b/python/mozlint/mozlint/cli.py
@@ -114,17 +114,19 @@ def run(paths, linters, fmt, outgoing, w
     lint.read(find_linters(linters))
 
     # run all linters
     results = lint.roll(paths, outgoing=outgoing, workdir=workdir)
     formatter = formatters.get(fmt)
 
     # Encode output with 'replace' to avoid UnicodeEncodeErrors on
     # environments that aren't using utf-8.
-    print(formatter(results, failed=lint.failed).encode(
-        sys.stdout.encoding or 'ascii', 'replace'))
+    out = formatter(results, failed=lint.failed).encode(
+                    sys.stdout.encoding or 'ascii', 'replace')
+    if out:
+        print(out)
     return 1 if results or lint.failed else 0
 
 
 if __name__ == '__main__':
     parser = MozlintParser()
     args = vars(parser.parse_args())
     sys.exit(run(**args))
--- a/python/mozlint/mozlint/formatters/__init__.py
+++ b/python/mozlint/mozlint/formatters/__init__.py
@@ -1,25 +1,27 @@
 # 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 json
 
 from ..result import ResultEncoder
+from .compact import CompactFormatter
 from .stylish import StylishFormatter
 from .treeherder import TreeherderFormatter
 
 
 class JSONFormatter(object):
     def __call__(self, results, **kwargs):
         return json.dumps(results, cls=ResultEncoder)
 
 
 all_formatters = {
+    'compact': CompactFormatter,
     'json': JSONFormatter,
     'stylish': StylishFormatter,
     'treeherder': TreeherderFormatter,
 }
 
 
 def get(name, **fmtargs):
     return all_formatters[name](**fmtargs)
new file mode 100644
--- /dev/null
+++ b/python/mozlint/mozlint/formatters/compact.py
@@ -0,0 +1,37 @@
+# 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
+
+from ..result import ResultContainer
+
+
+class CompactFormatter(object):
+    """Formatter for compact output.
+
+    This formatter prints one error per line, mimicking the
+    eslint 'compact' formatter.
+    """
+    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
+        for path, errors in sorted(result.iteritems()):
+            num_problems += len(errors)
+            for err in errors:
+                assert isinstance(err, ResultContainer)
+
+                d = {s: getattr(err, s) for s in err.__slots__}
+                d["column"] = ", col %s" % d["column"] if d["column"] else ""
+                d['level'] = d['level'].capitalize()
+                d['rule'] = d['rule'] or d['linter']
+                message.append(self.fmt.format(**d))
+
+        if self.summary and num_problems:
+            message.append("\n{} problem{}".format(num_problems, '' if num_problems == 1 else 's'))
+        return "\n".join(message)
--- a/python/mozlint/test/test_formatters.py
+++ b/python/mozlint/test/test_formatters.py
@@ -9,16 +9,53 @@ import sys
 from collections import defaultdict
 
 import pytest
 
 from mozlint import ResultContainer
 from mozlint import formatters
 
 
+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)
+
+3 problems
+""".strip(),
+    },
+    'stylish': {
+        'kwargs': {
+            'disable_colors': True,
+        },
+        'format': """
+a/b/c.txt
+  1  error  oh no foo  (foo)
+  4  error  oh no baz  (baz)
+
+d/e/f.txt
+  4:2  warning  oh no bar  bar-not-allowed (bar)
+
+\u2716 3 problems (2 errors, 1 warning)
+""".strip(),
+    },
+    '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(),
+    },
+}
+
+
 @pytest.fixture
 def results(scope='module'):
     containers = (
         ResultContainer(
             linter='foo',
             path='a/b/c.txt',
             message="oh no foo",
             lineno=1,
@@ -42,41 +79,21 @@ def results(scope='module'):
         ),
     )
     results = defaultdict(list)
     for c in containers:
         results[c.path].append(c)
     return results
 
 
-def test_stylish_formatter(results):
-    expected = """
-a/b/c.txt
-  1  error  oh no foo  (foo)
-  4  error  oh no baz  (baz)
-
-d/e/f.txt
-  4:2  warning  oh no bar  bar-not-allowed (bar)
-
-\u2716 3 problems (2 errors, 1 warning)
-""".strip()
-
-    fmt = formatters.get('stylish', disable_colors=True)
-    assert expected == fmt(results)
-
-
-def test_treeherder_formatter(results):
-    expected = """
-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()
-
-    fmt = formatters.get('treeherder')
-    assert expected == fmt(results)
+@pytest.mark.parametrize("name", EXPECTED.keys())
+def test_formatters(results, name):
+    opts = EXPECTED[name]
+    fmt = formatters.get(name, **opts['kwargs'])
+    assert fmt(results) == opts['format']
 
 
 def test_json_formatter(results):
     fmt = formatters.get('json')
     formatted = json.loads(fmt(results))
 
     assert set(formatted.keys()) == set(results.keys())