summaryrefslogtreecommitdiffstats
path: root/python/mozlint/mozlint/formatters
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozlint/mozlint/formatters')
-rw-r--r--python/mozlint/mozlint/formatters/__init__.py31
-rw-r--r--python/mozlint/mozlint/formatters/compact.py41
-rw-r--r--python/mozlint/mozlint/formatters/stylish.py156
-rw-r--r--python/mozlint/mozlint/formatters/summary.py50
-rw-r--r--python/mozlint/mozlint/formatters/treeherder.py34
-rw-r--r--python/mozlint/mozlint/formatters/unix.py33
6 files changed, 345 insertions, 0 deletions
diff --git a/python/mozlint/mozlint/formatters/__init__.py b/python/mozlint/mozlint/formatters/__init__.py
new file mode 100644
index 0000000000..e50616216f
--- /dev/null
+++ b/python/mozlint/mozlint/formatters/__init__.py
@@ -0,0 +1,31 @@
+# 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 IssueEncoder
+from .compact import CompactFormatter
+from .stylish import StylishFormatter
+from .summary import SummaryFormatter
+from .treeherder import TreeherderFormatter
+from .unix import UnixFormatter
+
+
+class JSONFormatter(object):
+ def __call__(self, result):
+ return json.dumps(result.issues, cls=IssueEncoder)
+
+
+all_formatters = {
+ "compact": CompactFormatter,
+ "json": JSONFormatter,
+ "stylish": StylishFormatter,
+ "summary": SummaryFormatter,
+ "treeherder": TreeherderFormatter,
+ "unix": UnixFormatter,
+}
+
+
+def get(name, **fmtargs):
+ return all_formatters[name](**fmtargs)
diff --git a/python/mozlint/mozlint/formatters/compact.py b/python/mozlint/mozlint/formatters/compact.py
new file mode 100644
index 0000000000..54ee194215
--- /dev/null
+++ b/python/mozlint/mozlint/formatters/compact.py
@@ -0,0 +1,41 @@
+# 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 attr
+
+from ..result import Issue
+
+
+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):
+ message = []
+ num_problems = 0
+ for path, errors in sorted(result.issues.items()):
+ num_problems += len(errors)
+ for err in errors:
+ assert isinstance(err, Issue)
+
+ d = attr.asdict(err)
+ 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)
diff --git a/python/mozlint/mozlint/formatters/stylish.py b/python/mozlint/mozlint/formatters/stylish.py
new file mode 100644
index 0000000000..3f80bc7ad2
--- /dev/null
+++ b/python/mozlint/mozlint/formatters/stylish.py
@@ -0,0 +1,156 @@
+# 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 mozterm import Terminal
+
+from ..result import Issue
+from ..util.string import pluralize
+
+
+class StylishFormatter(object):
+ """Formatter based on the eslint default."""
+
+ _indent_ = " "
+
+ # Colors later on in the list are fallbacks in case the terminal
+ # doesn't support colors earlier in the list.
+ # See http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
+ _colors = {
+ "grey": [247, 8, 7],
+ "red": [1],
+ "green": [2],
+ "yellow": [3],
+ "brightred": [9, 1],
+ "brightyellow": [11, 3],
+ }
+
+ fmt = """
+ {c1}{lineno}{column} {c2}{level}{normal} {message} {c1}{rule}({linter}){normal}
+{diff}""".lstrip(
+ "\n"
+ )
+ fmt_summary = (
+ "{t.bold}{c}\u2716 {problem} ({error}, {warning}{failure}, {fixed}){t.normal}"
+ )
+
+ def __init__(self, disable_colors=False):
+ self.term = Terminal(disable_styling=disable_colors)
+ self.num_colors = self.term.number_of_colors
+
+ def color(self, color):
+ for num in self._colors[color]:
+ if num < self.num_colors:
+ return self.term.color(num)
+ return ""
+
+ def _reset_max(self):
+ self.max_lineno = 0
+ self.max_column = 0
+ self.max_level = 0
+ self.max_message = 0
+
+ def _update_max(self, err):
+ """Calculates the longest length of each token for spacing."""
+ self.max_lineno = max(self.max_lineno, len(str(err.lineno)))
+ if err.column:
+ self.max_column = max(self.max_column, len(str(err.column)))
+ self.max_level = max(self.max_level, len(str(err.level)))
+ self.max_message = max(self.max_message, len(err.message))
+
+ def _get_colored_diff(self, diff):
+ if not diff:
+ return ""
+
+ new_diff = ""
+ for line in diff.split("\n"):
+ if line.startswith("+"):
+ new_diff += self.color("green")
+ elif line.startswith("-"):
+ new_diff += self.color("red")
+ else:
+ new_diff += self.term.normal
+ new_diff += self._indent_ + line + "\n"
+ return new_diff
+
+ def __call__(self, result):
+ message = []
+ failed = result.failed
+
+ num_errors = 0
+ num_warnings = 0
+ num_fixed = result.fixed
+ for path, errors in sorted(result.issues.items()):
+ self._reset_max()
+
+ message.append(self.term.underline(path))
+ # Do a first pass to calculate required padding
+ for err in errors:
+ assert isinstance(err, Issue)
+ self._update_max(err)
+ if err.level == "error":
+ num_errors += 1
+ else:
+ num_warnings += 1
+
+ for err in sorted(
+ errors, key=lambda e: (int(e.lineno), int(e.column or 0))
+ ):
+ if err.column:
+ col = ":" + str(err.column).ljust(self.max_column)
+ else:
+ col = "".ljust(self.max_column + 1)
+
+ args = {
+ "normal": self.term.normal,
+ "c1": self.color("grey"),
+ "c2": self.color("red")
+ if err.level == "error"
+ else self.color("yellow"),
+ "lineno": str(err.lineno).rjust(self.max_lineno),
+ "column": col,
+ "level": err.level.ljust(self.max_level),
+ "rule": "{} ".format(err.rule) if err.rule else "",
+ "linter": err.linter.lower(),
+ "message": err.message.ljust(self.max_message),
+ "diff": self._get_colored_diff(err.diff).ljust(self.max_message),
+ }
+ message.append(self.fmt.format(**args).rstrip().rstrip("\n"))
+
+ message.append("") # newline
+
+ # If there were failures, make it clear which linters failed
+ for fail in failed:
+ message.append(
+ "{c}A failure occurred in the {name} linter.".format(
+ c=self.color("brightred"), name=fail
+ )
+ )
+
+ # Print a summary
+ message.append(
+ self.fmt_summary.format(
+ t=self.term,
+ c=self.color("brightred")
+ if num_errors or failed
+ else self.color("brightyellow"),
+ problem=pluralize("problem", num_errors + num_warnings + len(failed)),
+ error=pluralize("error", num_errors),
+ warning=pluralize(
+ "warning", num_warnings or result.total_suppressed_warnings
+ ),
+ failure=", {}".format(pluralize("failure", len(failed)))
+ if failed
+ else "",
+ fixed="{} fixed".format(num_fixed),
+ )
+ )
+
+ if result.total_suppressed_warnings > 0 and num_errors == 0:
+ message.append(
+ "(pass {c1}-W/--warnings{c2} to see warnings.)".format(
+ c1=self.color("grey"), c2=self.term.normal
+ )
+ )
+
+ return "\n".join(message)
diff --git a/python/mozlint/mozlint/formatters/summary.py b/python/mozlint/mozlint/formatters/summary.py
new file mode 100644
index 0000000000..e6ecf37508
--- /dev/null
+++ b/python/mozlint/mozlint/formatters/summary.py
@@ -0,0 +1,50 @@
+# 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
+from collections import defaultdict
+
+import mozpack.path as mozpath
+
+from ..util.string import pluralize
+
+
+class SummaryFormatter(object):
+ def __init__(self, depth=None):
+ self.depth = depth or int(os.environ.get("MOZLINT_SUMMARY_DEPTH", 1))
+
+ def __call__(self, result):
+ paths = set(
+ list(result.issues.keys()) + list(result.suppressed_warnings.keys())
+ )
+
+ commonprefix = mozpath.commonprefix([mozpath.abspath(p) for p in paths])
+ commonprefix = commonprefix.rsplit("/", 1)[0] + "/"
+
+ summary = defaultdict(lambda: [0, 0])
+ for path in paths:
+ abspath = mozpath.abspath(path)
+ assert abspath.startswith(commonprefix)
+
+ if abspath != commonprefix:
+ parts = mozpath.split(mozpath.relpath(abspath, commonprefix))[
+ : self.depth
+ ]
+ abspath = mozpath.join(commonprefix, *parts)
+
+ summary[abspath][0] += len(
+ [r for r in result.issues[path] if r.level == "error"]
+ )
+ summary[abspath][1] += len(
+ [r for r in result.issues[path] if r.level == "warning"]
+ )
+ summary[abspath][1] += result.suppressed_warnings[path]
+
+ msg = []
+ for path, (errors, warnings) in sorted(summary.items()):
+ warning_str = (
+ ", {}".format(pluralize("warning", warnings)) if warnings else ""
+ )
+ msg.append("{}: {}{}".format(path, pluralize("error", errors), warning_str))
+ return "\n".join(msg)
diff --git a/python/mozlint/mozlint/formatters/treeherder.py b/python/mozlint/mozlint/formatters/treeherder.py
new file mode 100644
index 0000000000..66c7c59eee
--- /dev/null
+++ b/python/mozlint/mozlint/formatters/treeherder.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/.
+
+import attr
+
+from ..result import Issue
+
+
+class TreeherderFormatter(object):
+ """Formatter for treeherder friendly output.
+
+ This formatter looks ugly, but prints output such that
+ treeherder is able to highlight the errors and warnings.
+ This is a stop-gap until bug 1276486 is fixed.
+ """
+
+ fmt = "TEST-UNEXPECTED-{level} | {path}:{lineno}{column} | {message} ({rule})"
+
+ def __call__(self, result):
+ message = []
+ for path, errors in sorted(result.issues.items()):
+ for err in errors:
+ assert isinstance(err, Issue)
+
+ d = attr.asdict(err)
+ d["column"] = ":%s" % d["column"] if d["column"] else ""
+ d["level"] = d["level"].upper()
+ d["rule"] = d["rule"] or d["linter"]
+ message.append(self.fmt.format(**d))
+
+ if not message:
+ message.append("No lint issues found.")
+ return "\n".join(message)
diff --git a/python/mozlint/mozlint/formatters/unix.py b/python/mozlint/mozlint/formatters/unix.py
new file mode 100644
index 0000000000..ae096f3e2e
--- /dev/null
+++ b/python/mozlint/mozlint/formatters/unix.py
@@ -0,0 +1,33 @@
+# 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 attr
+
+from ..result import Issue
+
+
+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):
+ msg = []
+
+ for path, errors in sorted(result.issues.items()):
+ for err in errors:
+ assert isinstance(err, Issue)
+
+ slots = attr.asdict(err)
+ slots["path"] = slots["relpath"]
+ 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)