def test_diagnostic_formatter() -> None: path = Path("/path/to/file") formatter = FLCMFormatter err = formatter.format( Diagnostic( start_line=10, end_line=12, start_column=3, file_path=path, message="line1\nline2", ), "my_command", ) assert err == f"{path.resolve()}:10:3:my_command: line1\\nline2" err = formatter.format( Diagnostic( start_line=10, end_line=12, start_column=3, file_path=path, diff="-line1\n+line2\n", ), "my_command2", ) assert err == f"{path.resolve()}:10:3:my_command2: -line1\\n+line2\\n"
def test__format_diagnostic_position() -> None: path = Path("/path/to/file") position = _format_diagnostic_position( Diagnostic(start_line=10, end_line=12, start_column=3, file_path=path, diff="") ) assert position == f"{path.resolve()}:10:3" position = _format_diagnostic_position(Diagnostic(file_path=path, diff="")) assert position == f"{path.resolve()}:1:1"
def test_reporter_report_diagnostics() -> None: r = Reporter("foo") d1 = Diagnostic(pathlib.Path("hoge").resolve(), 1, 2, 3, message="hoge") d2 = Diagnostic(pathlib.Path("fuga").resolve(), 4, 5, 6, message="fuga") d3 = Diagnostic(pathlib.Path("piyo").resolve(), 7, 8, 9, message="piyo") with r: r.report_diagnostics([d1]) assert r.diagnostics == [d1] r.report_diagnostics([d2, d3]) assert r.diagnostics == [d1, d2, d3]
def test_reporter_factory() -> None: factory = ReporterFactory() assert len(factory.reporters) == 0 with factory.create("foo") as r: r.set_result(True, 0) r.report_diagnostics( [Diagnostic(BASE_DIR / "hoge.py", 1, 2, 3, message="error")]) assert len(factory.reporters) == 1 assert not factory.has_error() out = factory.format_summary() assert "foo" in out with factory.create("bar") as r: r.set_result(False, 128) assert len(factory.reporters) == 2 assert factory.has_error() out = factory.format_summary() assert "foo" in out and "bar" in out err_summary = factory.format_error_summary() assert "\n - bar\n" in err_summary and "foo" not in err_summary out = factory.format_diagnostic_summary(FLCMFormatter) assert f"{BASE_DIR / 'hoge.py'}:1:3:foo: error" in out
def parse_error_lines( errors: str, logger: Optional[logging.Logger] = None) -> Iterable[Diagnostic]: """ Compatible with flake8, mypy """ number = r"(?:0|[1-9]\d*)" _file_path = r"^(?P<file_path>.*?)" _line = fr":(?P<line>{number})" _column = fr"(:(?P<column>{number}))?" _message = r": (?P<message>.*$)" pattern = _file_path + _line + _column + _message invalid_lines = [] for el in errors.splitlines(): m = re.match(pattern, el) if m is None: invalid_lines.append(el) continue line = int(m.group("line")) if m.group("column") is None: column = None else: column = int(m.group("column")) yield Diagnostic( start_line=line, end_line=line, start_column=column, message=m.group("message").lstrip(" ").rstrip("\n"), file_path=Path(m.group("file_path")), ) if invalid_lines: _warn_parse_error("\n".join(invalid_lines), logger)
def parse_error_diffs( errors: str, file_path_parser: FilePathParserType, logger: Optional[logging.Logger] = None, ) -> Iterable[Diagnostic]: """ Compatible with isort, black """ def _is_changed(line: unidiff.patch.Line) -> bool: return not line.is_context try: patches = unidiff.PatchSet(errors) except unidiff.errors.UnidiffParseError: _warn_parse_error(errors, logger) return for patch in patches: for hunk in patch: source_changes = list(filter(_is_changed, hunk.source_lines())) if source_changes: start_line = source_changes[0].source_line_no end_line = source_changes[-1].source_line_no else: target_changes = list(filter(_is_changed, hunk.target_lines())) assert target_changes, "expected either source or target line number" start_line = target_changes[0].target_line_no end_line = target_changes[-1].target_line_no try: file_path = file_path_parser(patch.source_file) except UnexpectedErrorFormat: _warn_parse_error(patch, logger) continue def filter_hunk( hunk: unidiff.patch.Hunk, ) -> Generator[unidiff.patch.Line, None, None]: for line in hunk: if _is_changed(line): yield line elif line.source_line_no is not None: if start_line <= line.source_line_no <= end_line: yield line yield Diagnostic( start_line=start_line, end_line=end_line, start_column=1, file_path=file_path, diff="".join(map(str, filter_hunk(hunk))), )
def test_diagnostic_post_init() -> None: path = Path("/path/to/file") Diagnostic(file_path=path, message="error") Diagnostic(file_path=path, diff="diff") with pytest.raises(ValueError): Diagnostic(file_path=path)
def test_single_file_format_command_base() -> None: with TemporaryDirectory() as t: base_dir = pathlib.Path(t) for file_path in {"foo.py", "bar.pyi", "baz.txt"}: (base_dir / file_path).touch() command = FakeSingleFileFormatCommand( base_dir, FakeSource(), inplace_edit=False ) with mock.patch.object(command, "format", return_value="") as format_method: reporter = Reporter("fake") handler = FakeHandler() reporter.process_output.addHandler(handler) assert command(reporter) == 0 assert format_method.call_count == 2 assert len(handler.messages) == 0 assert len(reporter.diagnostics) == 0 with mock.patch.object(command, "format", return_value="diff") as format_method: reporter = Reporter("fake") handler = FakeHandler() reporter.process_output.addHandler(handler) assert command(reporter) == 1 assert format_method.call_count == 2 assert len(handler.messages) == 2 assert len(reporter.diagnostics) == 2 for file_path in {"foo.py", "bar.pyi"}: assert ( f"--- {base_dir / file_path}\n" f"+++ {base_dir / file_path}\n" "@@ -0,0 +1 @@\n" "+diff" ) in handler.messages assert ( Diagnostic( start_line=1, end_line=1, start_column=1, file_path=base_dir / file_path, diff="+diff", ) in reporter.diagnostics ) command = FakeSingleFileFormatCommand(base_dir, FakeSource(), inplace_edit=True) with mock.patch.object(command, "format", return_value=None) as format_method: reporter = Reporter("fake") handler = FakeHandler() reporter.process_output.addHandler(handler) assert command(reporter) == 1 assert format_method.call_count == 2 assert len(handler.messages) == 0 assert len(reporter.diagnostics) == 0 with (base_dir / "foo.py").open() as f: assert f.read() == "" with (base_dir / "bar.pyi").open() as f: assert f.read() == "" with mock.patch.object(command, "format", return_value="diff") as format_method: reporter = Reporter("fake") handler = FakeHandler() reporter.process_output.addHandler(handler) assert command(reporter) == 0 assert format_method.call_count == 2 assert len(handler.messages) == 0 assert len(reporter.diagnostics) == 0 with (base_dir / "foo.py").open() as f: assert f.read() == "diff" with (base_dir / "bar.pyi").open() as f: assert f.read() == "diff"