Skip to content

Commit bccb48c

Browse files
committed
sublinters poc v2
1 parent da2da8b commit bccb48c

File tree

8 files changed

+371
-180
lines changed

8 files changed

+371
-180
lines changed

pylint/checkers/classes/class_checker.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -992,10 +992,14 @@ def _check_unused_private_attributes(self, node: nodes.ClassDef) -> None:
992992
if isinstance(attribute.expr, nodes.Call):
993993
continue
994994

995-
if assign_attr.expr.name in {
996-
"cls",
997-
node.name,
998-
} and attribute.expr.name in {"cls", "self", node.name}:
995+
if (
996+
assign_attr.expr.name
997+
in {
998+
"cls",
999+
node.name,
1000+
}
1001+
and attribute.expr.name in {"cls", "self", node.name}
1002+
):
9991003
# If assigned to cls or class name, can be accessed by cls/self/class name
10001004
break
10011005

pylint/config/config_initialization.py

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,42 +21,45 @@ def _config_initialization(
2121
linter: PyLinter,
2222
args_list: list[str],
2323
reporter: reporters.BaseReporter | reporters.MultiReporter | None = None,
24-
config_file: None | str | Path = None,
24+
config_files: None | str | Path = None,
2525
verbose_mode: bool = False,
2626
) -> list[str]:
2727
"""Parse all available options, read config files and command line arguments and
2828
set options accordingly.
2929
"""
30-
config_file = Path(config_file) if config_file else None
31-
32-
# Set the current module to the configuration file
33-
# to allow raising messages on the configuration file.
34-
linter.set_current_module(str(config_file) if config_file else None)
35-
36-
# Read the configuration file
37-
config_file_parser = _ConfigurationFileParser(verbose_mode, linter)
38-
try:
39-
config_data, config_args = config_file_parser.parse_config_file(
40-
file_path=config_file
41-
)
42-
except OSError as ex:
43-
print(ex, file=sys.stderr)
44-
sys.exit(32)
45-
46-
# Run init hook, if present, before loading plugins
47-
if "init-hook" in config_data:
48-
exec(utils._unquote(config_data["init-hook"])) # pylint: disable=exec-used
49-
50-
# Load plugins if specified in the config file
51-
if "load-plugins" in config_data:
52-
linter.load_plugin_modules(utils._splitstrip(config_data["load-plugins"]))
53-
54-
# First we parse any options from a configuration file
55-
try:
56-
linter._parse_configuration_file(config_args)
57-
except _UnrecognizedOptionError as exc:
58-
msg = ", ".join(exc.options)
59-
linter.add_message("unrecognized-option", line=0, args=msg)
30+
config_files = [
31+
Path(config_file) if config_file else None for config_file in config_files
32+
]
33+
34+
for config_file in config_files:
35+
# Set the current module to the configuration file
36+
# to allow raising messages on the configuration file.
37+
linter.set_current_module(str(config_file) if config_file else None)
38+
39+
# Read the configuration file
40+
config_file_parser = _ConfigurationFileParser(verbose_mode, linter)
41+
try:
42+
config_data, config_args = config_file_parser.parse_config_file(
43+
file_path=config_file
44+
)
45+
except OSError as ex:
46+
print(ex, file=sys.stderr)
47+
sys.exit(32)
48+
49+
# Run init hook, if present, before loading plugins
50+
if "init-hook" in config_data:
51+
exec(utils._unquote(config_data["init-hook"])) # pylint: disable=exec-used
52+
53+
# Load plugins if specified in the config file
54+
if "load-plugins" in config_data:
55+
linter.load_plugin_modules(utils._splitstrip(config_data["load-plugins"]))
56+
57+
# First we parse any options from a configuration file
58+
try:
59+
linter._parse_configuration_file(config_args)
60+
except _UnrecognizedOptionError as exc:
61+
msg = ", ".join(exc.options)
62+
linter.add_message("unrecognized-option", line=0, args=msg)
6063

6164
# Then, if a custom reporter is provided as argument, it may be overridden
6265
# by file parameters, so we re-set it here. We do this before command line

pylint/config/find_default_config_files.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,23 @@ def _cfg_has_config(path: Path | str) -> bool:
3939
return any(section.startswith("pylint.") for section in parser.sections())
4040

4141

42+
def search_parent_config_files(curdir):
43+
while (curdir / "__init__.py").is_file():
44+
curdir = curdir.parent
45+
for rc_name in RC_NAMES:
46+
rc_path = curdir / rc_name
47+
if rc_path.is_file():
48+
yield rc_path.resolve()
49+
50+
51+
def find_per_directory_config_files(path: Path) -> Iterator(Path):
52+
for config_name in RC_NAMES:
53+
config_file = path / config_name
54+
if config_file.is_file():
55+
yield config_file.resolve()
56+
yield from search_parent_config_files(path)
57+
58+
4259
def find_default_config_files() -> Iterator[Path]:
4360
"""Find all possible config files."""
4461
for config_name in CONFIG_NAMES:
@@ -52,12 +69,7 @@ def find_default_config_files() -> Iterator[Path]:
5269

5370
if Path("__init__.py").is_file():
5471
curdir = Path(os.getcwd()).resolve()
55-
while (curdir / "__init__.py").is_file():
56-
curdir = curdir.parent
57-
for rc_name in RC_NAMES:
58-
rc_path = curdir / rc_name
59-
if rc_path.is_file():
60-
yield rc_path.resolve()
72+
yield from search_parent_config_files(curdir)
6173

6274
if "PYLINTRC" in os.environ and Path(os.environ["PYLINTRC"]).exists():
6375
if Path(os.environ["PYLINTRC"]).is_file():

pylint/lint/parallel.py

Lines changed: 35 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
import dill
1414

1515
from pylint import reporters
16-
from pylint.lint.utils import _patch_sys_path
16+
from pylint.config.find_default_config_files import find_default_config_files
17+
from pylint.lint.utils import _patch_sys_path, extract_results_from_linter, insert_results_to_linter, _merge_mapreduce_data
1718
from pylint.message import Message
1819
from pylint.typing import FileItem
1920
from pylint.utils import LinterStats, merge_stats
@@ -28,25 +29,26 @@
2829

2930
# PyLinter object used by worker processes when checking files using multiprocessing
3031
# should only be used by the worker processes
31-
_worker_linter: PyLinter | None = None
32+
_worker_linters: PyLinter | None = None
3233

3334

3435
def _worker_initialize(
35-
linter: bytes, arguments: None | str | Sequence[str] = None
36+
linters: bytes, arguments: None | str | Sequence[str] = None
3637
) -> None:
3738
"""Function called to initialize a worker for a Process within a multiprocessing Pool.
3839
3940
:param linter: A linter-class (PyLinter) instance pickled with dill
4041
:param arguments: File or module name(s) to lint and to be added to sys.path
4142
"""
42-
global _worker_linter # pylint: disable=global-statement
43-
_worker_linter = dill.loads(linter)
44-
assert _worker_linter
43+
global _worker_linters # pylint: disable=global-statement
44+
_worker_linters = dill.loads(linters)
45+
assert _worker_linters
4546

4647
# On the worker process side the messages are just collected and passed back to
4748
# parent process as _worker_check_file function's return value
48-
_worker_linter.set_reporter(reporters.CollectingReporter())
49-
_worker_linter.open()
49+
for _worker_linter in _worker_linters.values():
50+
_worker_linter.set_reporter(reporters.CollectingReporter())
51+
_worker_linter.open()
5052

5153
# Patch sys.path so that each argument is importable just like in single job mode
5254
_patch_sys_path(arguments or ())
@@ -65,66 +67,39 @@ def _worker_check_single_file(
6567
int,
6668
defaultdict[str, list[Any]],
6769
]:
68-
if not _worker_linter:
70+
rcfiles = file_item[0]
71+
file_item = file_item[1]
72+
73+
if not _worker_linters[rcfiles]:
6974
raise Exception("Worker linter not yet initialised")
70-
_worker_linter.open()
71-
_worker_linter.check_single_file_item(file_item)
72-
mapreduce_data = defaultdict(list)
73-
for checker in _worker_linter.get_checkers():
74-
data = checker.get_map_data()
75-
if data is not None:
76-
mapreduce_data[checker.name].append(data)
77-
msgs = _worker_linter.reporter.messages
78-
assert isinstance(_worker_linter.reporter, reporters.CollectingReporter)
79-
_worker_linter.reporter.reset()
80-
if _worker_linter.current_name is None:
81-
warnings.warn(
82-
(
83-
"In pylint 3.0 the current_name attribute of the linter object should be a string. "
84-
"If unknown it should be initialized as an empty string."
85-
),
86-
DeprecationWarning,
87-
)
75+
_worker_linters[rcfiles].open()
76+
_worker_linters[rcfiles].check_single_file_item(file_item)
77+
(
78+
linter_current_name,
79+
_,
80+
base_name,
81+
msgs,
82+
linter_stats,
83+
linter_msg_status,
84+
mapreduce_data,
85+
) = extract_results_from_linter(_worker_linters[rcfiles])
8886
return (
8987
id(multiprocessing.current_process()),
90-
_worker_linter.current_name,
88+
linter_current_name,
9189
file_item.filepath,
92-
_worker_linter.file_state.base_name,
90+
base_name,
9391
msgs,
94-
_worker_linter.stats,
95-
_worker_linter.msg_status,
92+
linter_stats,
93+
linter_msg_status,
9694
mapreduce_data,
9795
)
9896

9997

100-
def _merge_mapreduce_data(
101-
linter: PyLinter,
102-
all_mapreduce_data: defaultdict[int, list[defaultdict[str, list[Any]]]],
103-
) -> None:
104-
"""Merges map/reduce data across workers, invoking relevant APIs on checkers."""
105-
# First collate the data and prepare it, so we can send it to the checkers for
106-
# validation. The intent here is to collect all the mapreduce data for all checker-
107-
# runs across processes - that will then be passed to a static method on the
108-
# checkers to be reduced and further processed.
109-
collated_map_reduce_data: defaultdict[str, list[Any]] = defaultdict(list)
110-
for linter_data in all_mapreduce_data.values():
111-
for run_data in linter_data:
112-
for checker_name, data in run_data.items():
113-
collated_map_reduce_data[checker_name].extend(data)
114-
115-
# Send the data to checkers that support/require consolidated data
116-
original_checkers = linter.get_checkers()
117-
for checker in original_checkers:
118-
if checker.name in collated_map_reduce_data:
119-
# Assume that if the check has returned map/reduce data that it has the
120-
# reducer function
121-
checker.reduce_map_data(linter, collated_map_reduce_data[checker.name])
122-
123-
12498
def check_parallel(
12599
linter: PyLinter,
100+
linters,
126101
jobs: int,
127-
files: Iterable[FileItem],
102+
files, #[(conf, FileItem)],
128103
arguments: None | str | Sequence[str] = None,
129104
) -> None:
130105
"""Use the given linter to lint the files with given amount of workers (jobs).
@@ -137,7 +112,7 @@ def check_parallel(
137112
# a custom PyLinter object can be used.
138113
initializer = functools.partial(_worker_initialize, arguments=arguments)
139114
with multiprocessing.Pool(
140-
jobs, initializer=initializer, initargs=[dill.dumps(linter)]
115+
jobs, initializer=initializer, initargs=[dill.dumps(linters)]
141116
) as pool:
142117
linter.open()
143118
all_stats = []
@@ -158,13 +133,11 @@ def check_parallel(
158133
msg_status,
159134
mapreduce_data,
160135
) in pool.imap_unordered(_worker_check_single_file, files):
161-
linter.file_state.base_name = base_name
162-
linter.set_current_module(module, file_path)
163-
for msg in messages:
164-
linter.reporter.handle_message(msg)
136+
insert_results_to_linter(
137+
linter, module, file_path, base_name, messages, msg_status
138+
)
165139
all_stats.append(stats)
166140
all_mapreduce_data[worker_idx].append(mapreduce_data)
167-
linter.msg_status |= msg_status
168141

169142
pool.close()
170143
pool.join()

pylint/lint/pylinter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ def __init__(
310310
("RP0003", "Messages", report_messages_stats),
311311
)
312312
self.register_checker(self)
313+
self._ignore_paths = self.linter.config.ignore_paths
313314

314315
@property
315316
def option_groups(self) -> tuple[tuple[str, str], ...]:
@@ -1039,7 +1040,6 @@ def open(self) -> None:
10391040
self.config.extension_pkg_whitelist
10401041
)
10411042
self.stats.reset_message_count()
1042-
self._ignore_paths = self.linter.config.ignore_paths
10431043

10441044
def generate_reports(self) -> int | None:
10451045
"""Close the whole package /module, it's time to make reports !

0 commit comments

Comments
 (0)