prospector 1.10.3__py3-none-any.whl → 1.13.2__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 -12
- prospector/config/__init__.py +66 -49
- prospector/config/configuration.py +17 -14
- prospector/config/datatype.py +1 -1
- prospector/encoding.py +2 -2
- prospector/exceptions.py +1 -6
- prospector/finder.py +9 -8
- prospector/formatters/__init__.py +1 -1
- prospector/formatters/base.py +17 -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 +41 -9
- prospector/formatters/text.py +10 -58
- prospector/formatters/vscode.py +17 -7
- prospector/formatters/xunit.py +6 -7
- prospector/formatters/yaml.py +4 -2
- prospector/message.py +32 -14
- prospector/pathutils.py +3 -10
- prospector/postfilter.py +9 -8
- prospector/profiles/exceptions.py +14 -11
- prospector/profiles/profile.py +70 -59
- prospector/run.py +20 -18
- prospector/suppression.py +19 -13
- 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 -76
- 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 +57 -31
- prospector/tools/pylint/collector.py +3 -5
- prospector/tools/pylint/linter.py +19 -14
- prospector/tools/pyright/__init__.py +18 -7
- prospector/tools/pyroma/__init__.py +10 -6
- prospector/tools/ruff/__init__.py +84 -0
- prospector/tools/utils.py +25 -19
- prospector/tools/vulture/__init__.py +25 -15
- {prospector-1.10.3.dist-info → prospector-1.13.2.dist-info}/METADATA +10 -11
- prospector-1.13.2.dist-info/RECORD +71 -0
- prospector-1.10.3.dist-info/RECORD +0 -69
- {prospector-1.10.3.dist-info → prospector-1.13.2.dist-info}/LICENSE +0 -0
- {prospector-1.10.3.dist-info → prospector-1.13.2.dist-info}/WHEEL +0 -0
- {prospector-1.10.3.dist-info → prospector-1.13.2.dist-info}/entry_points.txt +0 -0
|
@@ -1,15 +1,21 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
2
|
+
|
|
1
3
|
from pyflakes.api import checkPath
|
|
4
|
+
from pyflakes.messages import Message as FlakeMessage
|
|
2
5
|
from pyflakes.reporter import Reporter
|
|
3
6
|
|
|
7
|
+
from prospector.finder import FileFinder
|
|
4
8
|
from prospector.message import Location, Message
|
|
5
9
|
from prospector.tools.base import ToolBase
|
|
6
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from prospector.config import ProspectorConfig
|
|
7
13
|
__all__ = ("PyFlakesTool",)
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
# Prospector uses the same pyflakes codes as flake8 defines,
|
|
11
17
|
# see https://flake8.pycqa.org/en/latest/user/error-codes.html
|
|
12
|
-
# and https://
|
|
18
|
+
# and https://github.com/PyCQA/flake8/blob/e817c63a/src/flake8/plugins/pyflakes.py
|
|
13
19
|
_MESSAGE_CODES = {
|
|
14
20
|
"UnusedImport": "F401",
|
|
15
21
|
"ImportShadowedByLoopVar": "F402",
|
|
@@ -81,13 +87,22 @@ LEGACY_CODE_MAP = {
|
|
|
81
87
|
|
|
82
88
|
|
|
83
89
|
class ProspectorReporter(Reporter):
|
|
84
|
-
def __init__(self, ignore=None):
|
|
90
|
+
def __init__(self, ignore: Optional[list[str]] = None) -> None:
|
|
85
91
|
super().__init__(None, None)
|
|
86
|
-
self._messages = []
|
|
92
|
+
self._messages: list[Message] = []
|
|
87
93
|
self.ignore = ignore or ()
|
|
88
94
|
|
|
89
|
-
# pylint: disable=too-many-arguments
|
|
90
|
-
|
|
95
|
+
def record_message( # pylint: disable=too-many-arguments
|
|
96
|
+
self,
|
|
97
|
+
filename: str,
|
|
98
|
+
line: Optional[int] = None,
|
|
99
|
+
character: Optional[int] = None,
|
|
100
|
+
code: Optional[str] = None,
|
|
101
|
+
message: Optional[str] = None,
|
|
102
|
+
) -> None:
|
|
103
|
+
assert message is not None
|
|
104
|
+
assert code is not None
|
|
105
|
+
|
|
91
106
|
code = code or "F999"
|
|
92
107
|
if code in self.ignore:
|
|
93
108
|
return
|
|
@@ -99,15 +114,16 @@ class ProspectorReporter(Reporter):
|
|
|
99
114
|
line=line,
|
|
100
115
|
character=character,
|
|
101
116
|
)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
117
|
+
self._messages.append(
|
|
118
|
+
Message(
|
|
119
|
+
source="pyflakes",
|
|
120
|
+
code=code,
|
|
121
|
+
location=location,
|
|
122
|
+
message=message,
|
|
123
|
+
)
|
|
107
124
|
)
|
|
108
|
-
self._messages.append(message)
|
|
109
125
|
|
|
110
|
-
def unexpectedError(self, filename, msg):
|
|
126
|
+
def unexpectedError(self, filename: str, msg: str) -> None:
|
|
111
127
|
self.record_message(
|
|
112
128
|
filename=filename,
|
|
113
129
|
code="F999",
|
|
@@ -115,7 +131,7 @@ class ProspectorReporter(Reporter):
|
|
|
115
131
|
)
|
|
116
132
|
|
|
117
133
|
# pylint: disable=too-many-arguments
|
|
118
|
-
def syntaxError(self, filename, msg, lineno, offset, text):
|
|
134
|
+
def syntaxError(self, filename: str, msg: str, lineno: int, offset: int, text: str) -> None:
|
|
119
135
|
self.record_message(
|
|
120
136
|
filename=filename,
|
|
121
137
|
line=lineno,
|
|
@@ -124,7 +140,7 @@ class ProspectorReporter(Reporter):
|
|
|
124
140
|
message=msg,
|
|
125
141
|
)
|
|
126
142
|
|
|
127
|
-
def flake(self, message):
|
|
143
|
+
def flake(self, message: FlakeMessage) -> None:
|
|
128
144
|
code = _MESSAGE_CODES.get(message.__class__.__name__, "F999")
|
|
129
145
|
|
|
130
146
|
self.record_message(
|
|
@@ -135,21 +151,21 @@ class ProspectorReporter(Reporter):
|
|
|
135
151
|
message=message.message % message.message_args,
|
|
136
152
|
)
|
|
137
153
|
|
|
138
|
-
def get_messages(self):
|
|
154
|
+
def get_messages(self) -> list[Message]:
|
|
139
155
|
return self._messages
|
|
140
156
|
|
|
141
157
|
|
|
142
158
|
class PyFlakesTool(ToolBase):
|
|
143
|
-
def __init__(self, *args, **kwargs):
|
|
159
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
144
160
|
super().__init__(*args, **kwargs)
|
|
145
|
-
self.ignore_codes =
|
|
161
|
+
self.ignore_codes: list[str] = []
|
|
146
162
|
|
|
147
|
-
def configure(self, prospector_config, _):
|
|
163
|
+
def configure(self, prospector_config: "ProspectorConfig", _: Any) -> None:
|
|
148
164
|
ignores = prospector_config.get_disabled_messages("pyflakes")
|
|
149
165
|
# convert old style to new
|
|
150
166
|
self.ignore_codes = [LEGACY_CODE_MAP.get(code, code) for code in ignores]
|
|
151
167
|
|
|
152
|
-
def run(self, found_files):
|
|
168
|
+
def run(self, found_files: FileFinder) -> list[Message]:
|
|
153
169
|
reporter = ProspectorReporter(ignore=self.ignore_codes)
|
|
154
170
|
for filepath in found_files.python_modules:
|
|
155
171
|
checkPath(str(filepath.absolute()), reporter)
|
|
@@ -2,10 +2,11 @@ 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
|
-
from pylint.config import
|
|
9
|
+
from pylint.config import find_default_config_files
|
|
9
10
|
from pylint.exceptions import UnknownMessageError
|
|
10
11
|
from pylint.lint.run import _cpu_count
|
|
11
12
|
|
|
@@ -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.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,19 +175,30 @@ class PylintTool(ToolBase):
|
|
|
165
175
|
return sorted(check_paths)
|
|
166
176
|
|
|
167
177
|
def _get_pylint_configuration(
|
|
168
|
-
self,
|
|
169
|
-
|
|
170
|
-
|
|
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]]]:
|
|
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
|
+
|
|
195
|
+
pylintrc = pylintrc or external_config
|
|
196
|
+
|
|
197
|
+
if pylintrc is None:
|
|
198
|
+
for p in find_default_config_files():
|
|
199
|
+
pylintrc = str(p)
|
|
200
|
+
break
|
|
201
|
+
|
|
181
202
|
if pylintrc is None: # nothing explicitly configured
|
|
182
203
|
for possible in (".pylintrc", "pylintrc", "pyproject.toml", "setup.cfg"):
|
|
183
204
|
pylintrc_path = os.path.join(prospector_config.workdir, possible)
|
|
@@ -194,7 +215,7 @@ class PylintTool(ToolBase):
|
|
|
194
215
|
|
|
195
216
|
return config_messages, configured_by
|
|
196
217
|
|
|
197
|
-
def _combine_w0614(self, messages):
|
|
218
|
+
def _combine_w0614(self, messages: list[Message]) -> list[Message]:
|
|
198
219
|
"""
|
|
199
220
|
For the "unused import from wildcard import" messages,
|
|
200
221
|
we want to combine all warnings about the same line into
|
|
@@ -212,15 +233,17 @@ class PylintTool(ToolBase):
|
|
|
212
233
|
for location, message_list in by_loc.items():
|
|
213
234
|
names = []
|
|
214
235
|
for msg in message_list:
|
|
215
|
-
|
|
236
|
+
match_ = _UNUSED_WILDCARD_IMPORT_RE.match(msg.message)
|
|
237
|
+
assert match_ is not None
|
|
238
|
+
names.append(match_.group(1))
|
|
216
239
|
|
|
217
|
-
msgtxt = "Unused imports from wildcard import:
|
|
240
|
+
msgtxt = "Unused imports from wildcard import: {}".format(", ".join(names))
|
|
218
241
|
combined_message = Message("pylint", "unused-wildcard-import", location, msgtxt)
|
|
219
242
|
out.append(combined_message)
|
|
220
243
|
|
|
221
244
|
return out
|
|
222
245
|
|
|
223
|
-
def combine(self, messages):
|
|
246
|
+
def combine(self, messages: list[Message]) -> list[Message]:
|
|
224
247
|
"""
|
|
225
248
|
Combine repeated messages.
|
|
226
249
|
|
|
@@ -234,7 +257,10 @@ class PylintTool(ToolBase):
|
|
|
234
257
|
combined = self._combine_w0614(messages)
|
|
235
258
|
return sorted(combined)
|
|
236
259
|
|
|
237
|
-
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
|
+
|
|
238
264
|
self._linter.check(self._args)
|
|
239
265
|
sys.path = self._orig_sys_path
|
|
240
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,34 +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
|
|
7
|
+
from pylint.config.config_initialization import _config_initialization
|
|
5
8
|
from pylint.lint import PyLinter
|
|
6
|
-
|
|
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."""
|
|
7
15
|
|
|
8
16
|
|
|
9
17
|
class ProspectorLinter(PyLinter):
|
|
10
|
-
def __init__(self, found_files, *args, **kwargs):
|
|
18
|
+
def __init__(self, found_files: FileFinder, *args: Any, **kwargs: Any) -> None:
|
|
11
19
|
self._files = found_files
|
|
12
20
|
# set up the standard PyLint linter
|
|
13
21
|
PyLinter.__init__(self, *args, **kwargs)
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
plugins = _splitstrip(self.cfgfile_parser.get("MASTER", "load-plugins"))
|
|
20
|
-
self.load_plugin_modules(plugins)
|
|
21
|
-
self.load_config_file()
|
|
23
|
+
# Largely inspired by https://github.com/pylint-dev/pylint/blob/main/pylint/config/config_initialization.py#L26
|
|
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)
|
|
22
27
|
return True
|
|
23
28
|
|
|
24
|
-
def _expand_files(self,
|
|
25
|
-
expanded = super()._expand_files(
|
|
26
|
-
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] = {}
|
|
27
32
|
# PyLinter._expand_files returns dict since 2.15.7.
|
|
28
33
|
if packaging_version.parse(pylint_version) > packaging_version.parse("2.15.6"):
|
|
29
|
-
for module in expanded:
|
|
34
|
+
for module, expanded_module in expanded.items():
|
|
30
35
|
if not self._files.is_excluded(Path(module)):
|
|
31
|
-
filtered[module] =
|
|
36
|
+
filtered[module] = expanded_module
|
|
32
37
|
return filtered
|
|
33
38
|
else:
|
|
34
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,84 @@
|
|
|
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
|
+
messages = []
|
|
47
|
+
completed_process = subprocess.run( # noqa: S603
|
|
48
|
+
[self.ruff_bin, *self.ruff_args, *found_files.python_modules], capture_output=True
|
|
49
|
+
)
|
|
50
|
+
if not completed_process.stdout:
|
|
51
|
+
messages.append(
|
|
52
|
+
Message(
|
|
53
|
+
"ruff",
|
|
54
|
+
"",
|
|
55
|
+
Location(None, None, None, None, None),
|
|
56
|
+
completed_process.stderr.decode(),
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
return messages
|
|
60
|
+
for message in json.loads(completed_process.stdout):
|
|
61
|
+
sub_message = {}
|
|
62
|
+
if message.get("url"):
|
|
63
|
+
sub_message["See"] = message["url"]
|
|
64
|
+
if message.get("fix") and message["fix"].get("applicability"):
|
|
65
|
+
sub_message["Fix applicability"] = message["fix"]["applicability"]
|
|
66
|
+
message_str = message.get("message", "")
|
|
67
|
+
if sub_message:
|
|
68
|
+
message_str += f" [{', '.join(f'{k}: {v}' for k, v in sub_message.items())}]"
|
|
69
|
+
|
|
70
|
+
messages.append(
|
|
71
|
+
Message(
|
|
72
|
+
"ruff",
|
|
73
|
+
message.get("code") or "unknown",
|
|
74
|
+
Location(
|
|
75
|
+
message.get("filename") or "unknown",
|
|
76
|
+
None,
|
|
77
|
+
None,
|
|
78
|
+
line=message.get("location", {}).get("row"),
|
|
79
|
+
character=message.get("location", {}).get("column"),
|
|
80
|
+
),
|
|
81
|
+
message_str,
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
return messages
|
prospector/tools/utils.py
CHANGED
|
@@ -1,47 +1,53 @@
|
|
|
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[CaptureStream] = None
|
|
24
|
+
stderr: Optional[CaptureStream] = 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):
|
|
39
|
-
return self.stdout.contents
|
|
43
|
+
def get_hidden_stdout(self) -> str:
|
|
44
|
+
return "" if self.stdout is None else self.stdout.contents
|
|
40
45
|
|
|
41
|
-
def get_hidden_stderr(self):
|
|
42
|
-
return self.stderr.contents
|
|
46
|
+
def get_hidden_stderr(self) -> str:
|
|
47
|
+
return "" if self.stderr is None else self.stderr.contents
|
|
43
48
|
|
|
44
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
49
|
+
def __exit__(self, exc_type: type, exc_val: Exception, exc_tb: type) -> None:
|
|
45
50
|
if self.hide:
|
|
46
|
-
|
|
51
|
+
assert self._prev_streams is not None
|
|
52
|
+
sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__ = self._prev_streams # type: ignore[misc]
|
|
47
53
|
del self._prev_streams
|