# 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)