Пример #1
0
 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))
Пример #2
0
    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)
Пример #3
0
    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])
Пример #4
0
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)
Пример #5
0
 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",
     )
Пример #6
0
    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),
        )