prospector 1.12.0__py3-none-any.whl → 1.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prospector/autodetect.py +10 -9
- prospector/blender.py +18 -11
- prospector/config/__init__.py +66 -49
- prospector/config/configuration.py +14 -12
- prospector/config/datatype.py +1 -1
- prospector/encoding.py +2 -2
- prospector/exceptions.py +1 -5
- prospector/finder.py +9 -8
- prospector/formatters/__init__.py +1 -1
- prospector/formatters/base.py +16 -11
- prospector/formatters/base_summary.py +43 -0
- prospector/formatters/emacs.py +5 -5
- prospector/formatters/grouped.py +9 -7
- prospector/formatters/json.py +3 -2
- prospector/formatters/pylint.py +31 -8
- prospector/formatters/text.py +10 -58
- prospector/formatters/vscode.py +17 -6
- prospector/formatters/xunit.py +6 -6
- prospector/formatters/yaml.py +4 -2
- prospector/message.py +18 -13
- prospector/pathutils.py +3 -10
- prospector/postfilter.py +8 -7
- prospector/profiles/exceptions.py +14 -11
- prospector/profiles/profile.py +69 -58
- prospector/run.py +20 -18
- prospector/suppression.py +12 -10
- prospector/tools/__init__.py +19 -13
- prospector/tools/bandit/__init__.py +27 -15
- prospector/tools/base.py +11 -3
- prospector/tools/dodgy/__init__.py +7 -3
- prospector/tools/mccabe/__init__.py +13 -6
- prospector/tools/mypy/__init__.py +44 -81
- prospector/tools/profile_validator/__init__.py +24 -15
- prospector/tools/pycodestyle/__init__.py +22 -15
- prospector/tools/pydocstyle/__init__.py +12 -6
- prospector/tools/pyflakes/__init__.py +35 -19
- prospector/tools/pylint/__init__.py +47 -28
- prospector/tools/pylint/collector.py +3 -5
- prospector/tools/pylint/linter.py +18 -51
- prospector/tools/pyright/__init__.py +18 -7
- prospector/tools/pyroma/__init__.py +10 -6
- prospector/tools/ruff/__init__.py +75 -0
- prospector/tools/utils.py +25 -17
- prospector/tools/vulture/__init__.py +25 -15
- {prospector-1.12.0.dist-info → prospector-1.13.0.dist-info}/METADATA +7 -8
- prospector-1.13.0.dist-info/RECORD +71 -0
- prospector-1.12.0.dist-info/RECORD +0 -69
- {prospector-1.12.0.dist-info → prospector-1.13.0.dist-info}/LICENSE +0 -0
- {prospector-1.12.0.dist-info → prospector-1.13.0.dist-info}/WHEEL +0 -0
- {prospector-1.12.0.dist-info → prospector-1.13.0.dist-info}/entry_points.txt +0 -0
|
@@ -2,8 +2,9 @@ import os
|
|
|
2
2
|
import re
|
|
3
3
|
import sys
|
|
4
4
|
from collections import defaultdict
|
|
5
|
+
from collections.abc import Iterable
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
7
8
|
|
|
8
9
|
from pylint.config import find_default_config_files
|
|
9
10
|
from pylint.exceptions import UnknownMessageError
|
|
@@ -15,6 +16,9 @@ from prospector.tools.base import ToolBase
|
|
|
15
16
|
from prospector.tools.pylint.collector import Collector
|
|
16
17
|
from prospector.tools.pylint.linter import ProspectorLinter
|
|
17
18
|
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from prospector.config import ProspectorConfig
|
|
21
|
+
|
|
18
22
|
_UNUSED_WILDCARD_IMPORT_RE = re.compile(r"^Unused import(\(s\))? (.*) from wildcard import")
|
|
19
23
|
|
|
20
24
|
|
|
@@ -27,12 +31,13 @@ class PylintTool(ToolBase):
|
|
|
27
31
|
# be functions (they don't use the 'self' argument) but that would
|
|
28
32
|
# make this module/class a bit ugly.
|
|
29
33
|
|
|
30
|
-
def __init__(self):
|
|
31
|
-
self._args = None
|
|
32
|
-
self._collector
|
|
33
|
-
self.
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
self._args: Any = None
|
|
36
|
+
self._collector: Optional[Collector] = None
|
|
37
|
+
self._linter: Optional[ProspectorLinter] = None
|
|
38
|
+
self._orig_sys_path: list[str] = []
|
|
34
39
|
|
|
35
|
-
def _prospector_configure(self, prospector_config, linter: ProspectorLinter):
|
|
40
|
+
def _prospector_configure(self, prospector_config: "ProspectorConfig", linter: ProspectorLinter) -> list[Message]:
|
|
36
41
|
errors = []
|
|
37
42
|
|
|
38
43
|
if "django" in prospector_config.libraries:
|
|
@@ -43,14 +48,14 @@ class PylintTool(ToolBase):
|
|
|
43
48
|
linter.load_plugin_modules(["pylint_flask"])
|
|
44
49
|
|
|
45
50
|
profile_path = os.path.join(prospector_config.workdir, prospector_config.profile.name)
|
|
46
|
-
for plugin in prospector_config.profile.pylint.get("load-plugins", []):
|
|
51
|
+
for plugin in prospector_config.profile.pylint.get("load-plugins", []): # type: ignore[attr-defined]
|
|
47
52
|
try:
|
|
48
53
|
linter.load_plugin_modules([plugin])
|
|
49
54
|
except ImportError:
|
|
50
55
|
errors.append(self._error_message(profile_path, f"Could not load plugin {plugin}"))
|
|
51
56
|
|
|
52
57
|
for msg_id in prospector_config.get_disabled_messages("pylint"):
|
|
53
|
-
try:
|
|
58
|
+
try: # noqa: SIM105
|
|
54
59
|
linter.disable(msg_id)
|
|
55
60
|
except UnknownMessageError:
|
|
56
61
|
# If the msg_id doesn't exist in PyLint any more,
|
|
@@ -64,7 +69,9 @@ class PylintTool(ToolBase):
|
|
|
64
69
|
continue
|
|
65
70
|
for option in checker.options:
|
|
66
71
|
if option[0] in options:
|
|
67
|
-
checker._arguments_manager.set_option(
|
|
72
|
+
checker._arguments_manager.set_option( # pylint: disable=protected-access
|
|
73
|
+
option[0], options[option[0]]
|
|
74
|
+
)
|
|
68
75
|
|
|
69
76
|
# The warnings about disabling warnings are useful for figuring out
|
|
70
77
|
# with other tools to suppress messages from. For example, an unused
|
|
@@ -82,16 +89,17 @@ class PylintTool(ToolBase):
|
|
|
82
89
|
if not hasattr(checker, "options"):
|
|
83
90
|
continue
|
|
84
91
|
for option in checker.options:
|
|
85
|
-
if max_line_length is not None:
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
if max_line_length is not None and option[0] == "max-line-length":
|
|
93
|
+
checker._arguments_manager.set_option( # pylint: disable=protected-access
|
|
94
|
+
"max-line-length", max_line_length
|
|
95
|
+
)
|
|
88
96
|
return errors
|
|
89
97
|
|
|
90
|
-
def _error_message(self, filepath, message):
|
|
98
|
+
def _error_message(self, filepath: Union[str, Path], message: str) -> Message:
|
|
91
99
|
location = Location(filepath, None, None, 0, 0)
|
|
92
100
|
return Message("prospector", "config-problem", location, message)
|
|
93
101
|
|
|
94
|
-
def _pylintrc_configure(self, pylintrc, linter):
|
|
102
|
+
def _pylintrc_configure(self, pylintrc: Union[str, Path], linter: ProspectorLinter) -> list[Message]:
|
|
95
103
|
errors = []
|
|
96
104
|
are_plugins_loaded = linter.config_from_file(pylintrc)
|
|
97
105
|
if not are_plugins_loaded and hasattr(linter.config, "load_plugins"):
|
|
@@ -102,7 +110,9 @@ class PylintTool(ToolBase):
|
|
|
102
110
|
errors.append(self._error_message(pylintrc, f"Could not load plugin {plugin}"))
|
|
103
111
|
return errors
|
|
104
112
|
|
|
105
|
-
def configure(
|
|
113
|
+
def configure(
|
|
114
|
+
self, prospector_config: "ProspectorConfig", found_files: FileFinder
|
|
115
|
+
) -> Optional[tuple[Optional[Union[str, Path]], Optional[Iterable[Message]]]]:
|
|
106
116
|
extra_sys_path = found_files.make_syspath()
|
|
107
117
|
check_paths = self._get_pylint_check_paths(found_files)
|
|
108
118
|
|
|
@@ -127,13 +137,13 @@ class PylintTool(ToolBase):
|
|
|
127
137
|
self._linter = linter
|
|
128
138
|
return configured_by, config_messages
|
|
129
139
|
|
|
130
|
-
def _set_path_finder(self, extra_sys_path:
|
|
140
|
+
def _set_path_finder(self, extra_sys_path: list[Path], pylint_options: dict[str, Any]) -> None:
|
|
131
141
|
# insert the target path into the system path to get correct behaviour
|
|
132
142
|
self._orig_sys_path = sys.path
|
|
133
143
|
if not pylint_options.get("use_pylint_default_path_finder"):
|
|
134
144
|
sys.path = sys.path + [str(path.absolute()) for path in extra_sys_path]
|
|
135
145
|
|
|
136
|
-
def _get_pylint_check_paths(self, found_files: FileFinder) ->
|
|
146
|
+
def _get_pylint_check_paths(self, found_files: FileFinder) -> list[Path]:
|
|
137
147
|
# create a list of packages, but don't include packages which are
|
|
138
148
|
# subpackages of others as checks will be duplicated
|
|
139
149
|
check_paths = set()
|
|
@@ -165,17 +175,21 @@ class PylintTool(ToolBase):
|
|
|
165
175
|
return sorted(check_paths)
|
|
166
176
|
|
|
167
177
|
def _get_pylint_configuration(
|
|
168
|
-
self,
|
|
169
|
-
|
|
178
|
+
self,
|
|
179
|
+
check_paths: list[Path],
|
|
180
|
+
linter: ProspectorLinter,
|
|
181
|
+
prospector_config: "ProspectorConfig",
|
|
182
|
+
pylint_options: dict[str, Any],
|
|
183
|
+
) -> tuple[list[Message], Optional[Union[Path, str]]]:
|
|
170
184
|
self._args = check_paths
|
|
171
185
|
linter.load_default_plugins()
|
|
172
186
|
|
|
173
|
-
config_messages = self._prospector_configure(prospector_config, linter)
|
|
174
|
-
configured_by = None
|
|
187
|
+
config_messages: list[Message] = self._prospector_configure(prospector_config, linter)
|
|
188
|
+
configured_by: Optional[Union[str, Path]] = None
|
|
175
189
|
|
|
176
190
|
if prospector_config.use_external_config("pylint"):
|
|
177
|
-
#
|
|
178
|
-
pylintrc = pylint_options.get("config_file")
|
|
191
|
+
# Try to find a .pylintrc
|
|
192
|
+
pylintrc: Optional[Union[str, Path]] = pylint_options.get("config_file")
|
|
179
193
|
external_config = prospector_config.external_config_location("pylint")
|
|
180
194
|
|
|
181
195
|
pylintrc = pylintrc or external_config
|
|
@@ -201,7 +215,7 @@ class PylintTool(ToolBase):
|
|
|
201
215
|
|
|
202
216
|
return config_messages, configured_by
|
|
203
217
|
|
|
204
|
-
def _combine_w0614(self, messages):
|
|
218
|
+
def _combine_w0614(self, messages: list[Message]) -> list[Message]:
|
|
205
219
|
"""
|
|
206
220
|
For the "unused import from wildcard import" messages,
|
|
207
221
|
we want to combine all warnings about the same line into
|
|
@@ -219,15 +233,17 @@ class PylintTool(ToolBase):
|
|
|
219
233
|
for location, message_list in by_loc.items():
|
|
220
234
|
names = []
|
|
221
235
|
for msg in message_list:
|
|
222
|
-
|
|
236
|
+
match_ = _UNUSED_WILDCARD_IMPORT_RE.match(msg.message)
|
|
237
|
+
assert match_ is not None
|
|
238
|
+
names.append(match_.group(1))
|
|
223
239
|
|
|
224
|
-
msgtxt = "Unused imports from wildcard import:
|
|
240
|
+
msgtxt = "Unused imports from wildcard import: {}".format(", ".join(names))
|
|
225
241
|
combined_message = Message("pylint", "unused-wildcard-import", location, msgtxt)
|
|
226
242
|
out.append(combined_message)
|
|
227
243
|
|
|
228
244
|
return out
|
|
229
245
|
|
|
230
|
-
def combine(self, messages):
|
|
246
|
+
def combine(self, messages: list[Message]) -> list[Message]:
|
|
231
247
|
"""
|
|
232
248
|
Combine repeated messages.
|
|
233
249
|
|
|
@@ -241,7 +257,10 @@ class PylintTool(ToolBase):
|
|
|
241
257
|
combined = self._combine_w0614(messages)
|
|
242
258
|
return sorted(combined)
|
|
243
259
|
|
|
244
|
-
def run(self, found_files) ->
|
|
260
|
+
def run(self, found_files: FileFinder) -> list[Message]:
|
|
261
|
+
assert self._collector is not None
|
|
262
|
+
assert self._linter is not None
|
|
263
|
+
|
|
245
264
|
self._linter.check(self._args)
|
|
246
265
|
sys.path = self._orig_sys_path
|
|
247
266
|
|
|
@@ -3,6 +3,7 @@ from typing import List
|
|
|
3
3
|
|
|
4
4
|
from pylint.exceptions import UnknownMessageError
|
|
5
5
|
from pylint.message import Message as PylintMessage
|
|
6
|
+
from pylint.message import MessageDefinitionStore
|
|
6
7
|
from pylint.reporters import BaseReporter
|
|
7
8
|
|
|
8
9
|
from prospector.message import Location, Message
|
|
@@ -11,10 +12,10 @@ from prospector.message import Location, Message
|
|
|
11
12
|
class Collector(BaseReporter):
|
|
12
13
|
name = "collector"
|
|
13
14
|
|
|
14
|
-
def __init__(self, message_store):
|
|
15
|
+
def __init__(self, message_store: MessageDefinitionStore) -> None:
|
|
15
16
|
BaseReporter.__init__(self, output=StringIO())
|
|
16
17
|
self._message_store = message_store
|
|
17
|
-
self._messages = []
|
|
18
|
+
self._messages: list[Message] = []
|
|
18
19
|
|
|
19
20
|
def handle_message(self, msg: PylintMessage) -> None:
|
|
20
21
|
loc = Location(msg.abspath, msg.module, msg.obj, msg.line, msg.column)
|
|
@@ -34,8 +35,5 @@ class Collector(BaseReporter):
|
|
|
34
35
|
message = Message("pylint", msg_symbol, loc, msg.msg)
|
|
35
36
|
self._messages.append(message)
|
|
36
37
|
|
|
37
|
-
def _display(self, layout) -> None:
|
|
38
|
-
pass
|
|
39
|
-
|
|
40
38
|
def get_messages(self) -> List[Message]:
|
|
41
39
|
return self._messages
|
|
@@ -1,72 +1,39 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
1
2
|
from pathlib import Path
|
|
3
|
+
from typing import Any, Optional, Union
|
|
2
4
|
|
|
3
5
|
from packaging import version as packaging_version
|
|
4
6
|
from pylint import version as pylint_version
|
|
5
|
-
from pylint.config.
|
|
6
|
-
from pylint.config.config_initialization import _order_all_first
|
|
7
|
+
from pylint.config.config_initialization import _config_initialization
|
|
7
8
|
from pylint.lint import PyLinter
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
from prospector.finder import FileFinder
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UnrecognizedOptions(Exception):
|
|
14
|
+
"""Raised when an unrecognized option is found in the Pylint configuration."""
|
|
9
15
|
|
|
10
16
|
|
|
11
17
|
class ProspectorLinter(PyLinter):
|
|
12
|
-
def __init__(self, found_files, *args, **kwargs):
|
|
18
|
+
def __init__(self, found_files: FileFinder, *args: Any, **kwargs: Any) -> None:
|
|
13
19
|
self._files = found_files
|
|
14
20
|
# set up the standard PyLint linter
|
|
15
21
|
PyLinter.__init__(self, *args, **kwargs)
|
|
16
22
|
|
|
17
23
|
# Largely inspired by https://github.com/pylint-dev/pylint/blob/main/pylint/config/config_initialization.py#L26
|
|
18
|
-
def config_from_file(self, config_file=None):
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
config_data, config_args = config_file_parser.parse_config_file(file_path=config_file)
|
|
22
|
-
if config_data.get("MASTER", {}).get("load-plugins"):
|
|
23
|
-
plugins = _splitstrip(config_data["MASTER"]["load-plugins"])
|
|
24
|
-
self.load_plugin_modules(plugins)
|
|
25
|
-
|
|
26
|
-
config_args = _order_all_first(config_args, joined=False)
|
|
27
|
-
|
|
28
|
-
if "init-hook" in config_data:
|
|
29
|
-
exec(utils._unquote(config_data["init-hook"])) # pylint: disable=exec-used
|
|
30
|
-
|
|
31
|
-
# Load plugins if specified in the config file
|
|
32
|
-
if "load-plugins" in config_data:
|
|
33
|
-
self.load_plugin_modules(utils._splitstrip(config_data["load-plugins"]))
|
|
34
|
-
|
|
35
|
-
self._parse_configuration_file(config_args)
|
|
36
|
-
|
|
37
|
-
# Set the current module to the command line
|
|
38
|
-
# to allow raising messages on it
|
|
39
|
-
self.set_current_module(config_file)
|
|
40
|
-
|
|
41
|
-
self._emit_stashed_messages()
|
|
42
|
-
|
|
43
|
-
# Set the current module to configuration as we don't know where
|
|
44
|
-
# the --load-plugins key is coming from
|
|
45
|
-
self.set_current_module("Command line or configuration file")
|
|
46
|
-
|
|
47
|
-
# We have loaded configuration from config file and command line. Now, we can
|
|
48
|
-
# load plugin specific configuration.
|
|
49
|
-
self.load_plugin_configuration()
|
|
50
|
-
|
|
51
|
-
# Now that plugins are loaded, get list of all fail_on messages, and
|
|
52
|
-
# enable them
|
|
53
|
-
self.enable_fail_on_messages()
|
|
54
|
-
|
|
55
|
-
self._parse_error_mode()
|
|
56
|
-
|
|
57
|
-
# Link the base Namespace object on the current directory
|
|
58
|
-
self._directory_namespaces[Path().resolve()] = (self.config, {})
|
|
59
|
-
|
|
24
|
+
def config_from_file(self, config_file: Optional[Union[str, Path]] = None) -> bool:
|
|
25
|
+
"""Initialize the configuration from a file."""
|
|
26
|
+
_config_initialization(self, [], config_file=config_file)
|
|
60
27
|
return True
|
|
61
28
|
|
|
62
|
-
def _expand_files(self,
|
|
63
|
-
expanded = super()._expand_files(
|
|
64
|
-
filtered = {}
|
|
29
|
+
def _expand_files(self, files_or_modules: list[str]) -> Union[Iterable[Any], dict[str, Any]]:
|
|
30
|
+
expanded = super()._expand_files(files_or_modules)
|
|
31
|
+
filtered: dict[str, Any] = {}
|
|
65
32
|
# PyLinter._expand_files returns dict since 2.15.7.
|
|
66
33
|
if packaging_version.parse(pylint_version) > packaging_version.parse("2.15.6"):
|
|
67
|
-
for module in expanded:
|
|
34
|
+
for module, expanded_module in expanded.items():
|
|
68
35
|
if not self._files.is_excluded(Path(module)):
|
|
69
|
-
filtered[module] =
|
|
36
|
+
filtered[module] = expanded_module
|
|
70
37
|
return filtered
|
|
71
38
|
else:
|
|
72
39
|
for module in expanded:
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import subprocess
|
|
2
|
+
import subprocess # nosec
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
3
5
|
|
|
4
6
|
import pyright
|
|
5
7
|
|
|
8
|
+
from prospector.finder import FileFinder
|
|
6
9
|
from prospector.message import Location, Message
|
|
7
10
|
from prospector.tools import ToolBase
|
|
11
|
+
from prospector.tools.exceptions import BadToolConfig
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from prospector.config import ProspectorConfig
|
|
15
|
+
|
|
8
16
|
|
|
9
17
|
__all__ = ("PyrightTool",)
|
|
10
18
|
|
|
11
|
-
from prospector.tools.exceptions import BadToolConfig
|
|
12
19
|
|
|
13
20
|
VALID_OPTIONS = [
|
|
14
21
|
"level",
|
|
@@ -21,7 +28,7 @@ VALID_OPTIONS = [
|
|
|
21
28
|
]
|
|
22
29
|
|
|
23
30
|
|
|
24
|
-
def format_messages(json_encoded):
|
|
31
|
+
def format_messages(json_encoded: str) -> list[Message]:
|
|
25
32
|
json_decoded = json.loads(json_encoded)
|
|
26
33
|
diagnostics = json_decoded.get("generalDiagnostics", [])
|
|
27
34
|
messages = []
|
|
@@ -42,15 +49,17 @@ def format_messages(json_encoded):
|
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
class PyrightTool(ToolBase):
|
|
45
|
-
def __init__(self, *args, **kwargs):
|
|
52
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
46
53
|
super().__init__(*args, **kwargs)
|
|
47
54
|
self.checker = pyright
|
|
48
55
|
self.options = ["--outputjson"]
|
|
49
56
|
|
|
50
|
-
def configure(
|
|
57
|
+
def configure( # pylint: disable=useless-return
|
|
58
|
+
self, prospector_config: "ProspectorConfig", _: Any
|
|
59
|
+
) -> Optional[tuple[str, Optional[Iterable[Message]]]]:
|
|
51
60
|
options = prospector_config.tool_options("pyright")
|
|
52
61
|
|
|
53
|
-
for option_key in options
|
|
62
|
+
for option_key in options:
|
|
54
63
|
if option_key not in VALID_OPTIONS:
|
|
55
64
|
url = "https://github.com/PyCQA/prospector/blob/master/prospector/tools/pyright/__init__.py"
|
|
56
65
|
raise BadToolConfig(
|
|
@@ -80,7 +89,9 @@ class PyrightTool(ToolBase):
|
|
|
80
89
|
if venv_path:
|
|
81
90
|
self.options.extend(["--venv-path", venv_path])
|
|
82
91
|
|
|
83
|
-
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def run(self, found_files: FileFinder) -> list[Message]:
|
|
84
95
|
paths = [str(path) for path in found_files.python_modules]
|
|
85
96
|
paths.extend(self.options)
|
|
86
97
|
result = self.checker.run(*paths, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
3
4
|
|
|
4
5
|
from prospector.finder import FileFinder
|
|
5
6
|
from prospector.message import Location, Message
|
|
@@ -48,7 +49,7 @@ PYROMA_ALL_CODES = {
|
|
|
48
49
|
PYROMA_CODES = {}
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
def _copy_codes():
|
|
52
|
+
def _copy_codes() -> None:
|
|
52
53
|
for name, code in PYROMA_ALL_CODES.items():
|
|
53
54
|
if hasattr(ratings, name):
|
|
54
55
|
PYROMA_CODES[getattr(ratings, name)] = code
|
|
@@ -60,14 +61,17 @@ PYROMA_TEST_CLASSES = [t.__class__ for t in ratings.ALL_TESTS]
|
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
class PyromaTool(ToolBase):
|
|
63
|
-
def __init__(self, *args, **kwargs):
|
|
64
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
64
65
|
super().__init__(*args, **kwargs)
|
|
65
|
-
self.ignore_codes =
|
|
66
|
+
self.ignore_codes: list[str] = []
|
|
66
67
|
|
|
67
|
-
def configure(
|
|
68
|
+
def configure( # pylint: disable=useless-return
|
|
69
|
+
self, prospector_config: "ProspectorConfig", found_files: FileFinder
|
|
70
|
+
) -> Optional[tuple[str, Optional[Iterable[Message]]]]:
|
|
68
71
|
self.ignore_codes = prospector_config.get_disabled_messages("pyroma")
|
|
72
|
+
return None
|
|
69
73
|
|
|
70
|
-
def run(self, found_files: FileFinder) ->
|
|
74
|
+
def run(self, found_files: FileFinder) -> list[Message]:
|
|
71
75
|
messages = []
|
|
72
76
|
for directory in found_files.directories:
|
|
73
77
|
# just list directories which are not ignored, but find any `setup.py` ourselves
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import subprocess # nosec
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from ruff.__main__ import find_ruff_bin
|
|
6
|
+
|
|
7
|
+
from prospector.finder import FileFinder
|
|
8
|
+
from prospector.message import Location, Message
|
|
9
|
+
from prospector.tools.base import ToolBase
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from prospector.config import ProspectorConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RuffTool(ToolBase):
|
|
16
|
+
def configure(self, prospector_config: "ProspectorConfig", _: Any) -> None:
|
|
17
|
+
self.ruff_bin = find_ruff_bin()
|
|
18
|
+
self.ruff_args = ["check", "--output-format=json"]
|
|
19
|
+
|
|
20
|
+
enabled = prospector_config.get_enabled_messages("ruff")
|
|
21
|
+
if enabled:
|
|
22
|
+
enabled_arg_value = ",".join(enabled)
|
|
23
|
+
self.ruff_args.append(f"--select={enabled_arg_value}")
|
|
24
|
+
disabled = prospector_config.get_disabled_messages("ruff")
|
|
25
|
+
if disabled:
|
|
26
|
+
disabled_arg_value = ",".join(disabled)
|
|
27
|
+
self.ruff_args.append(f"--ignore={disabled_arg_value}")
|
|
28
|
+
|
|
29
|
+
options = prospector_config.tool_options("ruff")
|
|
30
|
+
for key, value in options.items():
|
|
31
|
+
if value is True:
|
|
32
|
+
self.ruff_args.append(f"--{key}")
|
|
33
|
+
elif value is False:
|
|
34
|
+
pass
|
|
35
|
+
elif isinstance(value, list):
|
|
36
|
+
arg_value = ",".join(value)
|
|
37
|
+
self.ruff_args.append(f"--{key}={arg_value}")
|
|
38
|
+
# dict is like array but with a dict with true/false value to be able to merge profiles
|
|
39
|
+
elif isinstance(value, dict):
|
|
40
|
+
arg_value = ",".join(k for k, v in value.items() if v)
|
|
41
|
+
self.ruff_args.append(f"--{key}={arg_value}")
|
|
42
|
+
else:
|
|
43
|
+
self.ruff_args.append(f"--{key}={value}")
|
|
44
|
+
|
|
45
|
+
def run(self, found_files: FileFinder) -> list[Message]:
|
|
46
|
+
print([self.ruff_bin, *self.ruff_args])
|
|
47
|
+
messages = []
|
|
48
|
+
completed_process = subprocess.run( # noqa: S603
|
|
49
|
+
[self.ruff_bin, *self.ruff_args, *found_files.python_modules], capture_output=True
|
|
50
|
+
)
|
|
51
|
+
for message in json.loads(completed_process.stdout):
|
|
52
|
+
sub_message = {}
|
|
53
|
+
if message.get("url"):
|
|
54
|
+
sub_message["See"] = message["url"]
|
|
55
|
+
if message.get("fix") and message["fix"].get("applicability"):
|
|
56
|
+
sub_message["Fix applicability"] = message["fix"]["applicability"]
|
|
57
|
+
message_str = message.get("message", "")
|
|
58
|
+
if sub_message:
|
|
59
|
+
message_str += f" [{', '.join(f'{k}: {v}' for k, v in sub_message.items())}]"
|
|
60
|
+
|
|
61
|
+
messages.append(
|
|
62
|
+
Message(
|
|
63
|
+
"ruff",
|
|
64
|
+
message.get("code") or "unknown",
|
|
65
|
+
Location(
|
|
66
|
+
message.get("filename") or "unknown",
|
|
67
|
+
None,
|
|
68
|
+
None,
|
|
69
|
+
line=message.get("location", {}).get("row"),
|
|
70
|
+
character=message.get("location", {}).get("column"),
|
|
71
|
+
),
|
|
72
|
+
message_str,
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
return messages
|
prospector/tools/utils.py
CHANGED
|
@@ -1,47 +1,55 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
from io import TextIOWrapper
|
|
3
|
+
from typing import Optional
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
class CaptureStream:
|
|
5
|
-
def __init__(self):
|
|
6
|
+
class CaptureStream(TextIOWrapper):
|
|
7
|
+
def __init__(self) -> None:
|
|
6
8
|
self.contents = ""
|
|
7
9
|
|
|
8
|
-
def write(self, text):
|
|
10
|
+
def write(self, text: str, /) -> int:
|
|
9
11
|
self.contents += text
|
|
12
|
+
return len(text)
|
|
10
13
|
|
|
11
|
-
def close(self):
|
|
14
|
+
def close(self) -> None:
|
|
12
15
|
pass
|
|
13
16
|
|
|
14
|
-
def flush(self):
|
|
17
|
+
def flush(self) -> None:
|
|
15
18
|
pass
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
class CaptureOutput:
|
|
19
|
-
|
|
22
|
+
_prev_streams = None
|
|
23
|
+
stdout: Optional[TextIOWrapper] = None
|
|
24
|
+
stderr: Optional[TextIOWrapper] = None
|
|
25
|
+
|
|
26
|
+
def __init__(self, hide: bool) -> None:
|
|
20
27
|
self.hide = hide
|
|
21
|
-
self._prev_streams = None
|
|
22
|
-
self.stdout, self.stderr = None, None
|
|
23
28
|
|
|
24
|
-
def __enter__(self):
|
|
29
|
+
def __enter__(self) -> "CaptureOutput":
|
|
25
30
|
if self.hide:
|
|
26
|
-
self._prev_streams =
|
|
31
|
+
self._prev_streams = (
|
|
27
32
|
sys.stdout,
|
|
28
33
|
sys.stderr,
|
|
29
34
|
sys.__stdout__,
|
|
30
35
|
sys.__stderr__,
|
|
31
|
-
|
|
36
|
+
)
|
|
32
37
|
self.stdout = CaptureStream()
|
|
33
38
|
self.stderr = CaptureStream()
|
|
34
|
-
sys.stdout, sys.__stdout__ = self.stdout, self.stdout
|
|
35
|
-
sys.stderr, sys.__stderr__ = self.stderr, self.stderr
|
|
39
|
+
sys.stdout, sys.__stdout__ = self.stdout, self.stdout # type: ignore[misc]
|
|
40
|
+
sys.stderr, sys.__stderr__ = self.stderr, self.stderr # type: ignore[misc]
|
|
36
41
|
return self
|
|
37
42
|
|
|
38
|
-
def get_hidden_stdout(self):
|
|
43
|
+
def get_hidden_stdout(self) -> str:
|
|
44
|
+
assert isinstance(self.stdout, CaptureStream)
|
|
39
45
|
return self.stdout.contents
|
|
40
46
|
|
|
41
|
-
def get_hidden_stderr(self):
|
|
47
|
+
def get_hidden_stderr(self) -> str:
|
|
48
|
+
assert isinstance(self.stderr, CaptureStream)
|
|
42
49
|
return self.stderr.contents
|
|
43
50
|
|
|
44
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
51
|
+
def __exit__(self, exc_type: type, exc_val: Exception, exc_tb: type) -> None:
|
|
45
52
|
if self.hide:
|
|
46
|
-
|
|
53
|
+
assert self._prev_streams is not None
|
|
54
|
+
sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__ = self._prev_streams # type: ignore[misc]
|
|
47
55
|
del self._prev_streams
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
4
|
+
|
|
1
5
|
from vulture import Vulture
|
|
2
6
|
|
|
3
7
|
from prospector.encoding import CouldNotHandleEncoding, read_py_file
|
|
8
|
+
from prospector.finder import FileFinder
|
|
4
9
|
from prospector.message import Location, Message, make_tool_error_message
|
|
5
10
|
from prospector.tools.base import ToolBase
|
|
6
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from prospector.config import ProspectorConfig
|
|
14
|
+
|
|
7
15
|
|
|
8
16
|
class ProspectorVulture(Vulture):
|
|
9
|
-
def __init__(self, found_files):
|
|
17
|
+
def __init__(self, found_files: FileFinder) -> None:
|
|
10
18
|
Vulture.__init__(self, verbose=False)
|
|
11
19
|
self._files = found_files
|
|
12
|
-
self._internal_messages = []
|
|
13
|
-
self.file = None
|
|
14
|
-
self.filename = None
|
|
20
|
+
self._internal_messages: list[Message] = []
|
|
21
|
+
self.file: Optional[Path] = None
|
|
22
|
+
self.filename: Optional[Path] = None
|
|
15
23
|
|
|
16
|
-
def scavenge(self, _=None, __=None):
|
|
24
|
+
def scavenge(self, _: Any = None, __: Any = None) -> None:
|
|
17
25
|
# The argument is a list of paths, but we don't care
|
|
18
26
|
# about that as we use the found_files object. The
|
|
19
27
|
# argument is here to explicitly acknowledge that we
|
|
@@ -27,7 +35,9 @@ class ProspectorVulture(Vulture):
|
|
|
27
35
|
module,
|
|
28
36
|
"vulture",
|
|
29
37
|
"V000",
|
|
30
|
-
message=
|
|
38
|
+
message=(
|
|
39
|
+
f"Could not handle the encoding of this file: {err.encoding}" # type: ignore[attr-defined]
|
|
40
|
+
),
|
|
31
41
|
)
|
|
32
42
|
)
|
|
33
43
|
continue
|
|
@@ -38,7 +48,7 @@ class ProspectorVulture(Vulture):
|
|
|
38
48
|
except TypeError:
|
|
39
49
|
self.scan(module_string)
|
|
40
50
|
|
|
41
|
-
def get_messages(self):
|
|
51
|
+
def get_messages(self) -> list[Message]:
|
|
42
52
|
all_items = (
|
|
43
53
|
("unused-function", "Unused function %s", self.unused_funcs),
|
|
44
54
|
("unused-property", "Unused property %s", self.unused_props),
|
|
@@ -53,10 +63,7 @@ class ProspectorVulture(Vulture):
|
|
|
53
63
|
filename = item.file
|
|
54
64
|
except AttributeError:
|
|
55
65
|
filename = item.filename
|
|
56
|
-
if hasattr(item, "lineno")
|
|
57
|
-
lineno = item.lineno # for older versions of vulture
|
|
58
|
-
else:
|
|
59
|
-
lineno = item.first_lineno
|
|
66
|
+
lineno = item.lineno if hasattr(item, "lineno") else item.first_lineno
|
|
60
67
|
loc = Location(filename, None, None, lineno, -1)
|
|
61
68
|
message_text = template % item
|
|
62
69
|
message = Message("vulture", code, loc, message_text)
|
|
@@ -66,15 +73,18 @@ class ProspectorVulture(Vulture):
|
|
|
66
73
|
|
|
67
74
|
|
|
68
75
|
class VultureTool(ToolBase):
|
|
69
|
-
def __init__(self):
|
|
76
|
+
def __init__(self) -> None:
|
|
70
77
|
ToolBase.__init__(self)
|
|
71
78
|
self._vulture = None
|
|
72
|
-
self.ignore_codes =
|
|
79
|
+
self.ignore_codes: list[str] = []
|
|
73
80
|
|
|
74
|
-
def configure(
|
|
81
|
+
def configure( # pylint: disable=useless-return
|
|
82
|
+
self, prospector_config: "ProspectorConfig", found_files: FileFinder
|
|
83
|
+
) -> Optional[tuple[Optional[str], Optional[Iterable[Message]]]]:
|
|
75
84
|
self.ignore_codes = prospector_config.get_disabled_messages("vulture")
|
|
85
|
+
return None
|
|
76
86
|
|
|
77
|
-
def run(self, found_files):
|
|
87
|
+
def run(self, found_files: FileFinder) -> list[Message]:
|
|
78
88
|
vulture = ProspectorVulture(found_files)
|
|
79
89
|
vulture.scavenge()
|
|
80
90
|
return [message for message in vulture.get_messages() if message.code not in self.ignore_codes]
|