diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index a7d31dea6b0..ece3e0624d4 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -87,6 +87,14 @@ def expand_modules( if _is_ignored_file( something, ignore_list, ignore_list_re, ignore_list_paths_re ): + result[something] = { + "path": something, + "name": "", + "isarg": False, + "basepath": something, + "basename": "", + "isignored": True, + } continue module_package_path = discover_package_path(something, source_roots) additional_search_path = [".", module_package_path, *path] @@ -138,6 +146,7 @@ def expand_modules( "isarg": True, "basepath": filepath, "basename": modname, + "isignored": False, } has_init = ( not (modname.endswith(".__init__") or modname == "__init__") @@ -153,6 +162,14 @@ def expand_modules( if _is_in_ignore_list_re( os.path.basename(subfilepath), ignore_list_re ) or _is_in_ignore_list_re(subfilepath, ignore_list_paths_re): + result[subfilepath] = { + "path": subfilepath, + "name": "", + "isarg": False, + "basepath": subfilepath, + "basename": "", + "isignored": True, + } continue modpath = _modpath_from_file( @@ -167,5 +184,6 @@ def expand_modules( "isarg": isarg, "basepath": filepath, "basename": modname, + "isignored": False, } return result, errors diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index cc4605b96af..b007b37c880 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -316,6 +316,7 @@ def __init__( # Attributes related to stats self.stats = LinterStats() + self.skipped_paths: set[str] = set() # Attributes related to (command-line) options and their parsing self.options: Options = options + _make_linter_options(self) @@ -614,31 +615,39 @@ def initialize(self) -> None: if not msg.may_be_emitted(self.config.py_version): self._msgs_state[msg.msgid] = False - def _discover_files(self, files_or_modules: Sequence[str]) -> Iterator[str]: + def _discover_files( + self, files_or_modules: Sequence[str], all_files: bool = False + ) -> Iterator[str]: """Discover python modules and packages in sub-directory. + :param Sequence[str] files_or_modules: list of directories to explore + :param str all_files: whether to return _all_ files, entering modules + and not considering ignored paths Returns iterator of paths to discovered modules and packages. """ for something in files_or_modules: - if os.path.isdir(something) and not os.path.isfile( - os.path.join(something, "__init__.py") - ): + if os.path.isdir(something): + if not all_files and not os.path.isfile( + os.path.join(something, "__init__.py") + ): + continue skip_subtrees: list[str] = [] for root, _, files in os.walk(something): if any(root.startswith(s) for s in skip_subtrees): # Skip subtree of already discovered package. continue - if _is_ignored_file( + if not all_files and _is_ignored_file( root, self.config.ignore, self.config.ignore_patterns, self.config.ignore_paths, ): + self.skipped_paths.add(root) skip_subtrees.append(root) continue - if "__init__.py" in files: + if not all_files and "__init__.py" in files: skip_subtrees.append(root) yield root else: @@ -851,6 +860,7 @@ def _get_file_descr_from_stdin(self, filepath: str) -> Iterator[FileItem]: self.config.ignore_patterns, self.config.ignore_paths, ): + self.skipped_paths.add(filepath) return try: @@ -873,7 +883,9 @@ def _iterate_file_descrs( """ for descr in self._expand_files(files_or_modules).values(): name, filepath, is_arg = descr["name"], descr["path"], descr["isarg"] - if self.should_analyze_file(name, filepath, is_argument=is_arg): + if descr["isignored"]: + self.skipped_paths.add(filepath) + elif self.should_analyze_file(name, filepath, is_argument=is_arg): yield FileItem(name, filepath, descr["basename"]) def _expand_files( @@ -1100,6 +1112,7 @@ def generate_reports(self, verbose: bool = False) -> int | None: if self.config.reports: self.reporter.display_reports(sect) + score_value = self._report_evaluation(verbose) # save results if persistent run if self.config.persistent: @@ -1143,8 +1156,11 @@ def _report_evaluation(self, verbose: bool = False) -> int | None: if verbose: checked_files_count = self.stats.node_count["module"] - unchecked_files_count = self.stats.undocumented["module"] - msg += f"\nChecked {checked_files_count} files, skipped {unchecked_files_count} files" + skipped_files = list( + self._discover_files(list(self.skipped_paths), all_files=True) + ) + skipped_files_count = len(skipped_files) + msg += f"\nChecked {checked_files_count} files, skipped {skipped_files_count} files" if self.config.score: sect = report_nodes.EvaluationSection(msg) diff --git a/pylint/typing.py b/pylint/typing.py index 963222871fd..779a78aa7c9 100644 --- a/pylint/typing.py +++ b/pylint/typing.py @@ -51,6 +51,7 @@ class ModuleDescriptionDict(TypedDict): isarg: bool basepath: str basename: str + isignored: bool class ErrorDescriptionDict(TypedDict): diff --git a/tests/test_self.py b/tests/test_self.py index d01821c34c5..72ad215ab1c 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -241,8 +241,23 @@ def test_disable_all_enable_invalid(self) -> None: def test_output_with_verbose(self) -> None: out = StringIO() - self._runtest([UNNECESSARY_LAMBDA, "--verbose"], out=out, code=4) - assert "Checked 1 files, skipped 0 files" in out.getvalue().strip() + self._runtest( + [ + UNNECESSARY_LAMBDA, + join( + HERE, + "regrtest_data", + "directory", + "ignored_subdirectory", + "failing.py", + ), + "--ignore-paths=.*failing.*", + "--verbose", + ], + out=out, + code=4, + ) + assert "Checked 1 files, skipped 1 files" in out.getvalue().strip() def test_no_out_encoding(self) -> None: """Test redirection of stdout with non ascii characters.""" @@ -1104,7 +1119,7 @@ def test_fail_on_info_only_exit_code(self, args: list[str], expected: int) -> No ( "colorized", ( - "{path}:4:4: W0612: \x1B[35mUnused variable 'variable'\x1B[0m (\x1B[35munused-variable\x1B[0m)" + "{path}:4:4: W0612: \x1b[35mUnused variable 'variable'\x1b[0m (\x1b[35munused-variable\x1b[0m)" ), ), ("json", '"message": "Unused variable \'variable\'",'), @@ -1212,7 +1227,10 @@ def test_ignore_recursive(self, ignore_value: str) -> None: test would fail due these errors. """ directory = join(HERE, "regrtest_data", "directory") - self._runtest([directory, "--recursive=y", f"--ignore={ignore_value}"], code=0) + self._runtest( + [directory, "--verbose", "--recursive=y", f"--ignore={ignore_value}"], + code=0, + ) @pytest.mark.parametrize("ignore_pattern_value", ["ignored_.*", "failing.*"]) def test_ignore_pattern_recursive(self, ignore_pattern_value: str) -> None: