def test_lint_file_with_config(self) -> None: source = b"obj.attr.another_attr\n" config = LintConfig( rule_config={"ParenthesizeAttributeLintRule": { "disabled": True }}) reports = rule_lint_engine.lint_file( Path("dummy_file.py"), source, config=config, rules={ParenthesizeAttributeLintRule}, ) # Expect no reports cause disabled set to True self.assertEqual(len(reports), 0) config = LintConfig( rule_config={"ParenthesizeAttributeLintRule": { "disabled": False }}) reports = rule_lint_engine.lint_file( Path("dummy_file.py"), source, config=config, rules={ParenthesizeAttributeLintRule}, ) self.assertEqual(len(reports), 2)
def get_formatted_reports_for_path( path: Path, opts: LintOpts, metadata_cache: Optional[Mapping["ProviderT", object]] = None, ) -> Iterable[str]: with open(path, "rb") as f: source = f.read() try: cst_wrapper = None if metadata_cache is not None: cst_wrapper = MetadataWrapper(parse_module(source), True, metadata_cache) raw_reports = lint_file( path, source, rules=opts.rules, use_ignore_byte_markers=opts.use_ignore_byte_markers, use_ignore_comments=opts.use_ignore_comments, cst_wrapper=cst_wrapper, find_unused_suppressions=True, ) except (SyntaxError, ParserSyntaxError) as e: print_red( f"Encountered the following error while parsing source code in file {path}:" ) print(e) return [] # linter completed successfully return [opts.formatter.format(rr) for rr in raw_reports]
def get_file_lint_result_json( path: Path, opts: LintOpts, metadata_cache: Optional[Mapping["ProviderT", object]] = None, ) -> Sequence[str]: try: with open(path, "rb") as f: source = f.read() cst_wrapper = None if metadata_cache is not None: cst_wrapper = MetadataWrapper( cst.parse_module(source), True, metadata_cache, ) results = opts.success_report.create_reports( path, lint_file( path, source, rules=opts.rules, config=opts.config, cst_wrapper=cst_wrapper, ), **opts.extra, ) except Exception: tb_str = traceback.format_exc() results = opts.failure_report.create_reports(path, tb_str, **opts.extra) return [json.dumps(asdict(r)) for r in results]
def _test_method( self, test_case: Union[ValidTestCase, InvalidTestCase], rule: Type[CstLintRule], fixture_file: Optional[Path] = None, ) -> None: cst_wrapper: Optional[MetadataWrapper] = None if fixture_file is not None: cst_wrapper = gen_type_inference_wrapper(test_case.code, fixture_file) reports = lint_file( Path(test_case.filename), _dedent(test_case.code).encode("utf-8"), config=test_case.config, rules={rule}, cst_wrapper=cst_wrapper, ) if isinstance(test_case, ValidTestCase): self.assertEqual( len(reports), 0, 'Expected zero reports for this "valid" test case. Instead, found:\n' + "\n".join(str(e) for e in reports), ) else: self.assertGreater( len(reports), 0, 'Expected a report for this "invalid" test case but `self.report` was ' + "not called:\n" + test_case.code, ) self.assertLessEqual( len(reports), 1, 'Expected one report from this "invalid" test case. Found multiple:\n' + "\n".join(str(e) for e in reports), ) # pyre-fixme[16]: `Collection` has no attribute `__getitem__`. report = reports[0] if not (test_case.line is None or test_case.line == report.line): raise AssertionError( f"Expected line: {test_case.line} but found line: {report.line}" ) if not (test_case.column is None or test_case.column == report.column): raise AssertionError( f"Expected column: {test_case.column} but found column: {report.column}" ) kind = test_case.kind if test_case.kind is not None else rule.__name__ if kind != report.code: raise AssertionError( f"Expected:\n {test_case.expected_str}\nBut found:\n {report}" ) validate_patch(report, test_case)
def test_lint_ignore_with_framework(self) -> None: results = list( lint_file( file_path=Path("dummy/file/path.py"), source= b"# lint-ignore: F821: testing ignores\nundefined_fn()\n", rules={Flake8PseudoLintRule}, config=LintConfig(), )) self.assertEqual(results, [])
def test_lint_file_with_framework(self) -> None: results = list( lint_file( file_path=Path("dummy/file/path.py"), source=b"undefined_fn()\n", rules={Flake8PseudoLintRule}, config=LintConfig(), )) self.assertEqual(len(results), 1) self.assertEqual(results[0].code, "F821") # undefined name
def get_formatted_reports_for_path( path: Path, opts: InsertSuppressionsOpts, metadata_cache: Optional[Mapping["ProviderT", object]] = None, ) -> Iterable[str]: with open(path, "rb") as f: source = f.read() try: cst_wrapper = None if metadata_cache is not None: cst_wrapper = MetadataWrapper( parse_module(source), True, metadata_cache, ) raw_reports = lint_file( path, source, rules={opts.rule}, cst_wrapper=cst_wrapper ) except (SyntaxError, ParserSyntaxError) as e: print_red( f"Encountered the following error while parsing source code in file {path}:" ) print(e) return [] opts_message = opts.message comments = [] for rr in raw_reports: if isinstance(opts_message, str): message = opts_message elif opts_message == MessageKind.USE_LINT_REPORT: message = rr.message else: # opts_message == MessageKind.NO_MESSAGE message = None comments.append( SuppressionComment(opts.kind, rr.line, rr.code, message, opts.max_lines) ) insert_suppressions_result = insert_suppressions(source, comments) updated_source = insert_suppressions_result.updated_source assert ( not insert_suppressions_result.failed_insertions ), "Failed to insert some comments. This should not be possible." if updated_source != source: if not opts.skip_autoformatter: # Format the code using the config file's formatter. updated_source = invoke_formatter( get_lint_config().formatter, updated_source ) with open(path, "wb") as f: f.write(updated_source) # linter completed successfully return [opts.formatter.format(rr) for rr in raw_reports]
def mock_operation( path: Path, opts: LintOpts, _=None, ) -> Sequence[FakeLintSuccessReport]: results = opts.success_report.create_reports( path, lint_file(path, b"test", rules=opts.rules, config=LintConfig()), **opts.extra, ) return cast(Sequence[FakeLintSuccessReport], results)
def parse(self, file: File, source: bytes) -> None: """Run the lint engine on the given *source* for the *file*.""" try: reports = lint_file( file.path, source, use_ignore_byte_markers=False, use_ignore_comments=False, config=DEFAULT_CONFIG, rules=self._rules, ) self._pr_record.add_comments(reports, file.name) except (SyntaxError, ParserSyntaxError) as exc: self._pr_record.add_error(exc, file.name) logger.info("Invalid Python code for the file: [%s] %s", file.name, self.pr_html_url)
def test_lint_file( self, *, source: bytes, use_ignore_byte_markers: bool, use_ignore_comments: bool, expected_report_count: int, ) -> None: reports = rule_lint_engine.lint_file( Path("dummy_filename.py"), source, use_ignore_byte_markers=use_ignore_byte_markers, use_ignore_comments=use_ignore_comments, config=LintConfig(), rules={BadCallCstLintRule}, ) self.assertEqual(len(reports), expected_report_count)
def map_paths_operation( path: Path, rules: Set[LintRuleT], type_cache: Optional[Mapping[ProviderT, object]], ) -> Union[str, Collection[BaseLintRuleReport]]: # A top-level function to be accessible by `map_paths` from `fixit.cli`. cst_wrapper = None try: if type_cache is not None: cst_wrapper = MetadataWrapper( cst.parse_module(SOURCE_CODE), True, type_cache, ) return lint_file( file_path=path, source=SOURCE_CODE, rules=rules, cst_wrapper=cst_wrapper, config=LintConfig(), ) except Exception as e: return str(e)
def test_pseudo_lint_rule(self) -> None: class DummyLintRuleReport(BaseLintRuleReport): pass dummy_report = DummyLintRuleReport( file_path=DUMMY_FILE_PATH, code=DUMMY_LINT_CODE, message=DUMMY_LINT_MESSAGE, line=1, column=0, ) class DummyPseudoLintRule(PseudoLintRule): def lint_file(self) -> Iterable[BaseLintRuleReport]: return [dummy_report] reports = lint_file( DUMMY_FILE_PATH, DUMMY_SOURCE, config=LintConfig(), rules={DummyPseudoLintRule}, ) self.assertEqual(reports, [dummy_report])
def _test_method( self, test_case: Union[ValidTestCase, InvalidTestCase], rule: Type[CstLintRule], fixture_file: Optional[Path] = None, ) -> None: cst_wrapper: Optional[MetadataWrapper] = None if fixture_file is not None: cst_wrapper = gen_type_inference_wrapper(test_case.code, fixture_file) reports = lint_file( Path(test_case.filename), _dedent(test_case.code).encode("utf-8"), config=test_case.config, rules={rule}, cst_wrapper=cst_wrapper, ) if isinstance(test_case, ValidTestCase): self.assertEqual( len(reports), 0, 'Expected zero reports for this "valid" test case. Instead, found:\n' + "\n".join(str(e) for e in reports), ) else: self.assertGreater( len(reports), 0, 'Expected a report for this "invalid" test case but `self.report` was ' + "not called:\n" + test_case.code, ) positions = test_case.positions if positions: self.assertEqual( len(reports), len(positions), f'Expected report count to match positions count for this "invalid" test case, Reports : {len(reports)}, Positions: {len(positions)}.\n' + "\n".join(str(e) for e in reports), ) # We assert above that reports and positions are the same length for report, position in zip(reports, positions): if not (position.line is None or position.line == report.line): raise AssertionError( f"Expected line: {position.line} but found line: {report.line}" ) if not ( position.column is None or position.column == report.column ): raise AssertionError( f"Expected column: {position.column} but found column: {report.column}" ) kind = ( test_case.kind if test_case.kind is not None else rule.__name__ ) if kind != report.code: raise AssertionError( f"Expected:\n {test_case.expected_str}\nBut found:\n {report}" ) if ( test_case.expected_message is not None and test_case.expected_message != report.message ): raise AssertionError( f"Expected message:\n {test_case.expected_message}\nBut got:\n {report.message}" ) validate_patch(reports, test_case) else: self.assertEqual( len(reports), 0, f'Expected report count to match positions count for this "invalid" test case, Reports : {len(reports)}, Positions: 0.\n' + "\n".join(str(e) for e in reports), )
def test_rules( rule: Type[CstLintRule], test_case: Union[ValidTestCase, InvalidTestCase], test_case_id: str, ) -> None: """Test all the rules with the generated test cases. All the test cases comes directly from the `VALID` and `INVALID` attributes for the provided rules. Some of the points to keep in mind: - Invalid test case should be written so as to generate only one report. - Attributes should be in all caps: `INVALID` and `VALID` - The code can be written in triple quoted string with indented blocks, they will be removed with the helper function: ``_dedent`` The logic of the code is the same as that of ``fixit.common.testing`` but this has been converted to using ``pytest`` and removed the fixture feature. This might be added if there's any need for that in the future. """ reports = lint_file( Path(test_case.filename), _dedent(test_case.code).encode("utf-8"), config=test_case.config, rules={rule}, ) if isinstance(test_case, ValidTestCase): assert len(reports) == 0, ( 'Expected zero reports for this "valid" test case. Instead, found:\n' + "\n".join(str(e) for e in reports), ) else: assert len(reports) > 0, ( 'Expected a report for this "invalid" test case but `self.report` was ' + "not called:\n" + test_case.code, ) assert len(reports) <= 1, ( 'Expected one report from this "invalid" test case. Found multiple:\n' + "\n".join(str(e) for e in reports), ) report = reports[0] # type: ignore if test_case.line is not None: assert ( test_case.line == report.line ), f"Expected line: {test_case.line} but found line: {report.line}" if test_case.column is not None: assert ( test_case.column == report.column ), f"Expected column: {test_case.column} but found column: {report.column}" kind = test_case.kind if test_case.kind is not None else rule.__name__ assert ( kind == report.code ), f"Expected:\n {test_case.expected_str}\nBut found:\n {report}" if test_case.expected_message is not None: assert test_case.expected_message == report.message, ( f"Expected message:\n {test_case.expected_message}\n" + f"But got:\n {report.message}" ) patch = report.patch expected_replacement = test_case.expected_replacement if patch is None: assert expected_replacement is None, ( "The rule for this test case has no auto-fix, but expected source was " + "specified." ) return assert expected_replacement is not None, ( "The rule for this test case has an auto-fix, but no expected source was " + "specified." ) expected_replacement = _dedent(expected_replacement) patched_code = patch.apply(_dedent(test_case.code)) assert patched_code == expected_replacement, ( "Auto-fix did not produce expected result.\n" + f"Expected:\n{expected_replacement}\n" + f"But found:\n{patched_code}" )