diff options
Diffstat (limited to 'python/mozlint/mozlint/formatters')
-rw-r--r-- | python/mozlint/mozlint/formatters/__init__.py | 31 | ||||
-rw-r--r-- | python/mozlint/mozlint/formatters/compact.py | 41 | ||||
-rw-r--r-- | python/mozlint/mozlint/formatters/stylish.py | 156 | ||||
-rw-r--r-- | python/mozlint/mozlint/formatters/summary.py | 50 | ||||
-rw-r--r-- | python/mozlint/mozlint/formatters/treeherder.py | 34 | ||||
-rw-r--r-- | python/mozlint/mozlint/formatters/unix.py | 33 |
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) |