Several linters (gecko-trace, python-sites, clippy, ruff, eslint, libpref) populate Issue.hint with a suggested remedy, but of the mozlint's formatters actually render it. The hint is only ever visible in the raw JSON output. Render it in the default "stylish" formatter, while deduplicating identical hint text across a whole run (not just within one file), since a lint that shares one generic hint across many findings would otherwise repeat it verbatim after every single issue. Differential Revision: https://phabricator.services.mozilla.com/D316509
178 lines
4.9 KiB
Python
178 lines
4.9 KiB
Python
# 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 textwrap import dedent
|
|
|
|
import attr
|
|
import mozpack.path as mozpath
|
|
import mozunit
|
|
import pytest
|
|
|
|
from mozlint import formatters
|
|
from mozlint.result import Issue, ResultSummary
|
|
|
|
NORMALISED_PATHS = {
|
|
"abc": mozpath.normpath("a/b/c.txt"),
|
|
"def": mozpath.normpath("d/e/f.txt"),
|
|
"root": mozpath.abspath("/fake/root"),
|
|
}
|
|
|
|
EXPECTED = {
|
|
"compact": {
|
|
"kwargs": {},
|
|
"format": """
|
|
/fake/root/a/b/c.txt: line 1, Error - oh no foo (foo)
|
|
/fake/root/a/b/c.txt: line 4, col 10, Error - oh no baz (baz)
|
|
/fake/root/a/b/c.txt: line 5, Error - oh no foo-diff (foo-diff)
|
|
/fake/root/d/e/f.txt: line 4, col 2, Warning - oh no bar (bar-not-allowed)
|
|
|
|
4 problems
|
|
""".strip(),
|
|
},
|
|
"stylish": {
|
|
"kwargs": {"disable_colors": True},
|
|
"format": """
|
|
/fake/root/a/b/c.txt
|
|
1 error oh no foo (foo)
|
|
4:10 error oh no baz (baz)
|
|
|
|
|
4 | if baz:
|
|
| ^^^
|
|
5 error oh no foo-diff (foo-diff)
|
|
diff 1
|
|
- hello
|
|
+ hello2
|
|
|
|
/fake/root/d/e/f.txt
|
|
4:2 warning oh no bar bar-not-allowed (bar)
|
|
Hint: try baz instead
|
|
|
|
\u2716 4 problems (3 errors, 1 warning, 0 fixed)
|
|
""".strip(),
|
|
},
|
|
"treeherder": {
|
|
"kwargs": {},
|
|
"format": """
|
|
TEST-UNEXPECTED-ERROR | /fake/root/a/b/c.txt:1 | oh no foo (foo)
|
|
TEST-UNEXPECTED-ERROR | /fake/root/a/b/c.txt:4:10 | oh no baz (baz)
|
|
TEST-UNEXPECTED-ERROR | /fake/root/a/b/c.txt:5 | oh no foo-diff (foo-diff)
|
|
TEST-UNEXPECTED-WARNING | /fake/root/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:10: baz error: oh no baz
|
|
{abc}:5: foo-diff error: oh no foo-diff
|
|
{def}:4:2: bar-not-allowed warning: oh no bar
|
|
""".format(**NORMALISED_PATHS).strip(),
|
|
},
|
|
"summary": {
|
|
"kwargs": {},
|
|
"format": """
|
|
{root}/a: 3 errors
|
|
{root}/d: 0 errors, 1 warning
|
|
""".format(**NORMALISED_PATHS).strip(),
|
|
},
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def result(scope="module"):
|
|
result = ResultSummary("/fake/root")
|
|
containers = (
|
|
Issue(linter="foo", path="a/b/c.txt", message="oh no foo", lineno=1),
|
|
Issue(
|
|
linter="bar",
|
|
path="d/e/f.txt",
|
|
message="oh no bar",
|
|
hint="try baz instead",
|
|
level="warning",
|
|
lineno="4",
|
|
column="2",
|
|
rule="bar-not-allowed",
|
|
),
|
|
Issue(
|
|
linter="baz",
|
|
path="a/b/c.txt",
|
|
message="oh no baz",
|
|
lineno=4,
|
|
column=10,
|
|
source=dedent(
|
|
"""
|
|
|
|
|
4 | if baz:
|
|
| ^^^
|
|
"""
|
|
).lstrip("\n"),
|
|
),
|
|
Issue(
|
|
linter="foo-diff",
|
|
path="a/b/c.txt",
|
|
message="oh no foo-diff",
|
|
lineno=5,
|
|
diff="diff 1\n- hello\n+ hello2",
|
|
),
|
|
)
|
|
result = ResultSummary("/fake/root")
|
|
for c in containers:
|
|
result.issues[c.path].append(c)
|
|
return result
|
|
|
|
|
|
@pytest.mark.parametrize("name", EXPECTED.keys())
|
|
def test_formatters(result, name):
|
|
opts = EXPECTED[name]
|
|
fmt = formatters.get(name, **opts["kwargs"])
|
|
# encoding to str bypasses a UnicodeEncodeError in pytest
|
|
assert fmt(result) == opts["format"]
|
|
|
|
|
|
def test_json_formatter(result):
|
|
fmt = formatters.get("json")
|
|
formatted = json.loads(fmt(result))
|
|
|
|
assert set(formatted.keys()) == set(result.issues.keys())
|
|
|
|
attrs = attr.fields(Issue)
|
|
for errors in formatted.values():
|
|
for err in errors:
|
|
assert all(a.name in err for a in attrs)
|
|
|
|
|
|
def test_stylish_hint_shown_once_per_distinct_text():
|
|
# "fix it" is repeated on issues in both a.txt and b.txt: dedup must hold
|
|
# across files, not just within a single one, since seen hints are
|
|
# tracked for the whole formatter run.
|
|
result = ResultSummary("/fake/root")
|
|
containers = (
|
|
Issue(linter="foo", path="a.txt", message="oh no foo", lineno=1, hint="fix it"),
|
|
Issue(linter="bar", path="a.txt", message="oh no bar", lineno=2, hint="fix it"),
|
|
Issue(
|
|
linter="baz",
|
|
path="a.txt",
|
|
message="oh no baz",
|
|
lineno=3,
|
|
hint="fix it differently",
|
|
),
|
|
Issue(linter="qux", path="b.txt", message="oh no qux", lineno=1, hint="fix it"),
|
|
Issue(linter="norf", path="b.txt", message="oh no norf", lineno=2),
|
|
)
|
|
for c in containers:
|
|
result.issues[c.path].append(c)
|
|
|
|
fmt = formatters.get("stylish", disable_colors=True)
|
|
output = fmt(result)
|
|
|
|
assert output.count("Hint: fix it\n") == 1
|
|
assert "Hint: fix it differently" in output
|
|
assert "oh no qux" in output
|
|
assert "oh no norf" in output
|
|
|
|
|
|
if __name__ == "__main__":
|
|
mozunit.main()
|