def test_ignored_lines(self, *, source: str, ignored_code: str, ignored_lines: Container[int]) -> None: tokens = tuple( tokenize.tokenize(BytesIO(source.encode("utf-8")).readline)) ignore_info = IgnoreInfo.compute( comment_info=CommentInfo.compute(tokens=tokens), line_mapping_info=LineMappingInfo.compute(tokens=tokens), ) lines = range(1, tokens[-1].end[0] + 1) actual_ignored_lines = [] for line in lines: ignored = ignore_info.should_ignore_report( CstLintRuleReport( file_path=Path("fake/path.py"), node=cst.EmptyLine(), code=ignored_code, message="message", line=line, column=0, module=cst.MetadataWrapper(cst.parse_module(source)), module_bytes=source.encode("utf-8"), )) if ignored: actual_ignored_lines.append(line) # pyre-fixme[6]: Expected `Iterable[Variable[_T]]` for 1st param but got # `Container[int]`. self.assertEqual(actual_ignored_lines, list(ignored_lines))
def report( self, node: cst.CSTNode, message: Optional[str] = None, *, position: Optional[CodePosition] = None, replacement: Optional[Union[cst.CSTNode, cst.RemovalSentinel]] = None, ) -> None: """ Report a lint violation for a given node. Optionally specify a custom position to report an error at or a replacement node for an auto-fix. """ if position is None: position = self.context.wrapper.resolve( PositionProvider)[node].start if message is None: message = self.MESSAGE if message is None: raise Exception( f"No lint message was provided to rule: {self}") report = CstLintRuleReport( file_path=self.context.file_path, node=node, # TODO deprecate _get_code() completely and replace with self.__class__.__name__ code=_get_code(message, self.__class__.__name__), message=message, line=position.line, # libcst columns are 0-indexed but arc is 1-indexed column=(position.column + 1), module=self.context.wrapper, module_bytes=self.context._source, replacement_node=replacement, ) self.context.reports.append(report)
def test( self, *, source: bytes, rules_in_lint_run: Collection[Type[CstLintRule]], rules_without_report: Collection[Type[CstLintRule]], suppressed_line: int, expected_unused_suppressions_report_messages: Collection[str], expected_replacements: Optional[List[str]] = None, ) -> None: reports = [ CstLintRuleReport( file_path=FILE_PATH, node=cst.EmptyLine(), code=rule.__name__, message="message", line=suppressed_line, column=0, module=cst.MetadataWrapper(cst.parse_module(source)), module_bytes=source, ) for rule in rules_in_lint_run if rule not in rules_without_report ] tokens = _get_tokens(source) ignore_info = IgnoreInfo.compute( comment_info=CommentInfo.compute(tokens=tokens), line_mapping_info=LineMappingInfo.compute(tokens=tokens), ) cst_wrapper = MetadataWrapper(cst.parse_module(source), unsafe_skip_copy=True) config = LintConfig( rule_config={ RemoveUnusedSuppressionsRule.__name__: { "ignore_info": ignore_info, "rules": rules_in_lint_run, } }) unused_suppressions_context = CstContext(cst_wrapper, source, FILE_PATH, config) for report in reports: ignore_info.should_ignore_report(report) _visit_cst_rules_with_context(cst_wrapper, [RemoveUnusedSuppressionsRule], unused_suppressions_context) messages = [] patches = [] for report in unused_suppressions_context.reports: messages.append(report.message) patches.append(report.patch) self.assertEqual(messages, expected_unused_suppressions_report_messages) if expected_replacements is None: self.assertEqual(len(patches), 0) else: self.assertEqual(len(patches), len(expected_replacements)) for idx, patch in enumerate(patches): replacement = patch.apply(source.decode()) self.assertEqual(replacement, expected_replacements[idx])
class LintRuleReportTest(UnitTest): @data_provider( { "AstLintRuleReport": [ AstLintRuleReport( file_path=Path("fake/path.py"), node=ast.parse(""), code="SomeFakeRule", message="some message", line=1, column=1, ) ], "CstLintRuleReport": [ CstLintRuleReport( file_path=Path("fake/path.py"), node=cst.parse_statement("pass\n"), code="SomeFakeRule", message="some message", line=1, column=1, module=cst.MetadataWrapper(cst.parse_module(b"pass\n")), module_bytes=b"pass\n", ) ], } ) def test_is_not_pickleable(self, report: BaseLintRuleReport) -> None: with pytest.raises(pickle.PicklingError): pickle.dumps(report)
def setUp(self) -> None: self.fake_filepath = Path("fake/path.py") self.report = CstLintRuleReport( file_path=self.fake_filepath, node=cst.parse_statement("pass\n"), code="SomeFakeRule", message=( "Some long message that should span multiple lines.\n" + "\n" + "Another paragraph with more information about the lint rule." ), line=1, column=1, module=cst.MetadataWrapper(cst.parse_module(b"pass\n")), module_bytes=b"pass\n", )
def test_unused_comments( self, *, source: str, reports_on_lines: Iterable[Tuple[int, str]], unused_comments: Iterable[int], ) -> None: """ Verify that we can correctly track which lint comments were used and which were unused. TODO: We don't track usage of global ignore comments, so we can't know if they're unused. """ tokens = tuple( tokenize.tokenize(BytesIO(source.encode("utf-8")).readline)) ignore_info = IgnoreInfo.compute( comment_info=CommentInfo.compute(tokens=tokens), line_mapping_info=LineMappingInfo.compute(tokens=tokens), use_noqa=True, ) for line, code in reports_on_lines: ignore_info.should_ignore_report( CstLintRuleReport( file_path=Path("fake/path.py"), node=cst.EmptyLine(), code=code, message="message", line=line, column=0, module=cst.MetadataWrapper(cst.parse_module(source)), module_bytes=source.encode("utf-8"), )) self.assertEqual( sorted([ min(tok.start[0] for tok in c.tokens) for c in ignore_info.suppression_comments if not c.used_by ]), sorted(unused_comments), )