prospector 1.12.1__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 +11 -9
- 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.1.dist-info → prospector-1.13.0.dist-info}/METADATA +4 -3
- prospector-1.13.0.dist-info/RECORD +71 -0
- prospector-1.12.1.dist-info/RECORD +0 -69
- {prospector-1.12.1.dist-info → prospector-1.13.0.dist-info}/LICENSE +0 -0
- {prospector-1.12.1.dist-info → prospector-1.13.0.dist-info}/WHEEL +0 -0
- {prospector-1.12.1.dist-info → prospector-1.13.0.dist-info}/entry_points.txt +0 -0
prospector/autodetect.py
CHANGED
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
import re
|
|
3
3
|
import warnings
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Union
|
|
5
6
|
|
|
6
7
|
from requirements_detector import find_requirements
|
|
7
8
|
from requirements_detector.detect import RequirementsNotFound
|
|
@@ -19,7 +20,7 @@ _IMPORT_REGEX = re.compile(r"^\s*import ([\._a-zA-Z0-9]+)$")
|
|
|
19
20
|
_IMPORT_MULTIPLE_REGEX = re.compile(r"^\s*import ([\._a-zA-Z0-9]+(, ){1})+")
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def find_from_imports(file_contents):
|
|
23
|
+
def find_from_imports(file_contents: str) -> set[str]:
|
|
23
24
|
names = set()
|
|
24
25
|
for line in file_contents.split("\n"):
|
|
25
26
|
match = _IMPORT_MULTIPLE_REGEX.match(line)
|
|
@@ -42,7 +43,7 @@ def find_from_imports(file_contents):
|
|
|
42
43
|
return names
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
def find_from_path(path: Path):
|
|
46
|
+
def find_from_path(path: Path) -> set[str]:
|
|
46
47
|
names = set()
|
|
47
48
|
|
|
48
49
|
try:
|
|
@@ -57,7 +58,7 @@ def find_from_path(path: Path):
|
|
|
57
58
|
names |= find_from_imports(contents)
|
|
58
59
|
except encoding.CouldNotHandleEncoding as err:
|
|
59
60
|
# TODO: this output will break output formats such as JSON
|
|
60
|
-
warnings.warn(f"{err.path}: {err.__cause__}", ImportWarning)
|
|
61
|
+
warnings.warn(f"{err.path}: {err.__cause__}", ImportWarning, stacklevel=0)
|
|
61
62
|
|
|
62
63
|
if len(names) == len(POSSIBLE_LIBRARIES):
|
|
63
64
|
# don't continue on recursing, there's no point!
|
|
@@ -68,22 +69,22 @@ def find_from_path(path: Path):
|
|
|
68
69
|
return names
|
|
69
70
|
|
|
70
71
|
|
|
71
|
-
def find_from_requirements(path):
|
|
72
|
+
def find_from_requirements(path: Union[str, Path]) -> set[str]:
|
|
72
73
|
reqs = find_requirements(path)
|
|
73
|
-
names =
|
|
74
|
+
names: set[str] = set()
|
|
74
75
|
for requirement in reqs:
|
|
75
76
|
if requirement.name is not None and requirement.name.lower() in POSSIBLE_LIBRARIES:
|
|
76
|
-
names.
|
|
77
|
+
names.add(requirement.name.lower())
|
|
77
78
|
return names
|
|
78
79
|
|
|
79
80
|
|
|
80
|
-
def autodetect_libraries(path):
|
|
81
|
+
def autodetect_libraries(path: Union[str, Path]) -> set[str]:
|
|
81
82
|
if os.path.isfile(path):
|
|
82
83
|
path = os.path.dirname(path)
|
|
83
84
|
if path == "":
|
|
84
85
|
path = "."
|
|
85
86
|
|
|
86
|
-
libraries =
|
|
87
|
+
libraries: set[str] = set()
|
|
87
88
|
|
|
88
89
|
try:
|
|
89
90
|
libraries = find_from_requirements(path)
|
|
@@ -91,6 +92,6 @@ def autodetect_libraries(path):
|
|
|
91
92
|
pass
|
|
92
93
|
|
|
93
94
|
if len(libraries) < len(POSSIBLE_LIBRARIES):
|
|
94
|
-
libraries = find_from_path(path)
|
|
95
|
+
libraries = find_from_path(Path(path))
|
|
95
96
|
|
|
96
97
|
return libraries
|
prospector/blender.py
CHANGED
|
@@ -6,16 +6,20 @@
|
|
|
6
6
|
# remove duplicates.
|
|
7
7
|
import pkgutil
|
|
8
8
|
from collections import defaultdict
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
9
11
|
|
|
10
12
|
import yaml
|
|
11
13
|
|
|
14
|
+
from prospector.message import Message
|
|
15
|
+
|
|
12
16
|
__all__ = (
|
|
13
17
|
"blend",
|
|
14
18
|
"BLEND_COMBOS",
|
|
15
19
|
)
|
|
16
20
|
|
|
17
21
|
|
|
18
|
-
def blend_line(messages, blend_combos=None):
|
|
22
|
+
def blend_line(messages: list[Message], blend_combos: Optional[list[list[tuple[str, str]]]] = None) -> list[Message]:
|
|
19
23
|
"""
|
|
20
24
|
Given a list of messages on the same line, blend them together so that we
|
|
21
25
|
end up with one message per actual problem. Note that we can still return
|
|
@@ -23,8 +27,8 @@ def blend_line(messages, blend_combos=None):
|
|
|
23
27
|
the line.
|
|
24
28
|
"""
|
|
25
29
|
blend_combos = blend_combos or BLEND_COMBOS
|
|
26
|
-
blend_lists = [[] for _ in range(len(blend_combos))]
|
|
27
|
-
blended = []
|
|
30
|
+
blend_lists: list[list[Message]] = [[] for _ in range(len(blend_combos))]
|
|
31
|
+
blended: list[Message] = []
|
|
28
32
|
|
|
29
33
|
# first we split messages into each of the possible blendable categories
|
|
30
34
|
# so that we have a list of lists of messages which can be blended together
|
|
@@ -72,18 +76,19 @@ def blend_line(messages, blend_combos=None):
|
|
|
72
76
|
# it will appear in two blend_lists. Therefore we mark anything not taken from the blend list
|
|
73
77
|
# as "consumed" and then filter later, to avoid such cases.
|
|
74
78
|
for now_used in blend_list[1:]:
|
|
75
|
-
now_used.used = True
|
|
79
|
+
now_used.used = True # type: ignore[attr-defined]
|
|
76
80
|
|
|
77
81
|
return [m for m in blended if not getattr(m, "used", False)]
|
|
78
82
|
|
|
79
83
|
|
|
80
|
-
def blend(messages, blend_combos=None):
|
|
84
|
+
def blend(messages: list[Message], blend_combos: Optional[list[list[tuple[str, str]]]] = None) -> list[Message]:
|
|
81
85
|
blend_combos = blend_combos or BLEND_COMBOS
|
|
82
86
|
|
|
83
87
|
# group messages by file and then line number
|
|
84
|
-
msgs_grouped = defaultdict(lambda: defaultdict(list))
|
|
88
|
+
msgs_grouped: dict[Path, dict[int, list[Message]]] = defaultdict(lambda: defaultdict(list))
|
|
85
89
|
|
|
86
90
|
for message in messages:
|
|
91
|
+
assert message.location.line is not None
|
|
87
92
|
msgs_grouped[message.location.path][message.location.line].append(
|
|
88
93
|
message,
|
|
89
94
|
)
|
|
@@ -97,18 +102,20 @@ def blend(messages, blend_combos=None):
|
|
|
97
102
|
return out
|
|
98
103
|
|
|
99
104
|
|
|
100
|
-
def get_default_blend_combinations():
|
|
101
|
-
|
|
105
|
+
def get_default_blend_combinations() -> list[list[tuple[str, str]]]:
|
|
106
|
+
blender_combinations = pkgutil.get_data(__name__, "blender_combinations.yaml")
|
|
107
|
+
assert blender_combinations is not None
|
|
108
|
+
combos = yaml.safe_load(blender_combinations)
|
|
102
109
|
combos = combos.get("combinations", [])
|
|
103
110
|
|
|
104
|
-
defaults = []
|
|
111
|
+
defaults: list[list[tuple[str, str]]] = []
|
|
105
112
|
for combo in combos:
|
|
106
113
|
toblend = []
|
|
107
114
|
for msg in combo:
|
|
108
115
|
toblend += msg.items()
|
|
109
|
-
defaults.append(
|
|
116
|
+
defaults.append(toblend)
|
|
110
117
|
|
|
111
|
-
return
|
|
118
|
+
return defaults
|
|
112
119
|
|
|
113
120
|
|
|
114
121
|
BLEND_COMBOS = get_default_blend_combinations()
|
prospector/config/__init__.py
CHANGED
|
@@ -2,12 +2,18 @@ import os
|
|
|
2
2
|
import re
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Any, Callable, Optional, Union
|
|
6
|
+
|
|
7
|
+
import setoptconf.config
|
|
8
|
+
|
|
9
|
+
from prospector.finder import FileFinder
|
|
6
10
|
|
|
7
11
|
try: # Python >= 3.11
|
|
8
12
|
import re._constants as sre_constants
|
|
9
13
|
except ImportError:
|
|
10
|
-
import sre_constants
|
|
14
|
+
import sre_constants # pylint: disable=deprecated-module
|
|
15
|
+
|
|
16
|
+
import contextlib
|
|
11
17
|
|
|
12
18
|
from prospector import tools
|
|
13
19
|
from prospector.autodetect import autodetect_libraries
|
|
@@ -26,7 +32,7 @@ class ProspectorConfig:
|
|
|
26
32
|
# Also the 'too many instance attributes' warning is ignored, as this
|
|
27
33
|
# is a config object and its sole purpose is to hold many properties!
|
|
28
34
|
|
|
29
|
-
def __init__(self, workdir: Path = None):
|
|
35
|
+
def __init__(self, workdir: Optional[Path] = None):
|
|
30
36
|
self.config, self.arguments = self._configure_prospector()
|
|
31
37
|
self.paths = self._get_work_path(self.config, self.arguments)
|
|
32
38
|
self.explicit_file_mode = all(p.is_file for p in self.paths)
|
|
@@ -36,17 +42,17 @@ class ProspectorConfig:
|
|
|
36
42
|
self.libraries = self._find_used_libraries(self.config, self.profile)
|
|
37
43
|
self.tools_to_run = self._determine_tool_runners(self.config, self.profile)
|
|
38
44
|
self.ignores = self._determine_ignores(self.config, self.profile, self.libraries)
|
|
39
|
-
self.configured_by:
|
|
40
|
-
self.messages:
|
|
45
|
+
self.configured_by: dict[str, Optional[Union[str, Path]]] = {}
|
|
46
|
+
self.messages: list[Message] = []
|
|
41
47
|
|
|
42
|
-
def make_exclusion_filter(self):
|
|
48
|
+
def make_exclusion_filter(self) -> Callable[[Path], bool]:
|
|
43
49
|
# Only close over the attributes required by the filter, rather
|
|
44
50
|
# than the entire self, because ProspectorConfig can't be pickled
|
|
45
51
|
# because of the config attribute, which would break parallel
|
|
46
52
|
# pylint.
|
|
47
53
|
ignores, workdir = self.ignores, self.workdir
|
|
48
54
|
|
|
49
|
-
def _filter(path: Path):
|
|
55
|
+
def _filter(path: Path) -> bool:
|
|
50
56
|
for ignore in ignores:
|
|
51
57
|
# first figure out where the path is, relative to the workdir
|
|
52
58
|
# ignore-paths/patterns will usually be relative to a repository
|
|
@@ -60,26 +66,25 @@ class ProspectorConfig:
|
|
|
60
66
|
|
|
61
67
|
return _filter
|
|
62
68
|
|
|
63
|
-
def get_tools(self, found_files):
|
|
69
|
+
def get_tools(self, found_files: FileFinder) -> list[tools.ToolBase]:
|
|
64
70
|
self.configured_by = {}
|
|
65
71
|
runners = []
|
|
66
72
|
for tool_name in self.tools_to_run:
|
|
67
73
|
tool = tools.TOOLS[tool_name]()
|
|
68
74
|
config_result = tool.configure(self, found_files)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
messages = []
|
|
75
|
+
messages: list[Message] = []
|
|
76
|
+
configured_by = None
|
|
77
|
+
if config_result is not None:
|
|
78
|
+
configured_by, config_messages = config_result
|
|
79
|
+
if config_messages is not None:
|
|
80
|
+
messages = list(config_messages)
|
|
76
81
|
|
|
77
82
|
self.configured_by[tool_name] = configured_by
|
|
78
83
|
self.messages += messages
|
|
79
84
|
runners.append(tool)
|
|
80
85
|
return runners
|
|
81
86
|
|
|
82
|
-
def replace_deprecated_tool_names(self) ->
|
|
87
|
+
def replace_deprecated_tool_names(self) -> list[str]:
|
|
83
88
|
# pep8 was renamed pycodestyle ; pep257 was renamed pydocstyle
|
|
84
89
|
# for backwards compatibility, these have been deprecated but will remain until prospector v2
|
|
85
90
|
deprecated_found = []
|
|
@@ -93,12 +98,13 @@ class ProspectorConfig:
|
|
|
93
98
|
self.tools_to_run = replaced
|
|
94
99
|
return deprecated_found
|
|
95
100
|
|
|
96
|
-
def get_output_report(self):
|
|
101
|
+
def get_output_report(self) -> list[tuple[str, list[str]]]:
|
|
97
102
|
# Get the output formatter
|
|
103
|
+
output_report: list[tuple[str, list[str]]]
|
|
98
104
|
if self.config.output_format is not None:
|
|
99
105
|
output_report = self.config.output_format
|
|
100
106
|
else:
|
|
101
|
-
output_report = [(self.profile.output_format, self.profile.output_target)]
|
|
107
|
+
output_report = [(self.profile.output_format, self.profile.output_target)] # type: ignore[list-item]
|
|
102
108
|
|
|
103
109
|
for index, report in enumerate(output_report):
|
|
104
110
|
if not all(report):
|
|
@@ -106,13 +112,13 @@ class ProspectorConfig:
|
|
|
106
112
|
|
|
107
113
|
return output_report
|
|
108
114
|
|
|
109
|
-
def _configure_prospector(self):
|
|
115
|
+
def _configure_prospector(self) -> tuple[setoptconf.config.Configuration, dict[str, str]]:
|
|
110
116
|
# first we will configure prospector as a whole
|
|
111
117
|
mgr = cfg.build_manager()
|
|
112
118
|
config = mgr.retrieve(*cfg.build_default_sources())
|
|
113
119
|
return config, mgr.arguments
|
|
114
120
|
|
|
115
|
-
def _get_work_path(self, config, arguments) ->
|
|
121
|
+
def _get_work_path(self, config: setoptconf.config.Configuration, arguments: dict[str, str]) -> list[Path]:
|
|
116
122
|
# Figure out what paths we're prospecting
|
|
117
123
|
if config["path"]:
|
|
118
124
|
paths = [Path(self.config["path"])]
|
|
@@ -122,7 +128,9 @@ class ProspectorConfig:
|
|
|
122
128
|
paths = [Path.cwd()]
|
|
123
129
|
return [p.resolve() for p in paths]
|
|
124
130
|
|
|
125
|
-
def _get_profile(
|
|
131
|
+
def _get_profile(
|
|
132
|
+
self, workdir: Path, config: setoptconf.config.Configuration
|
|
133
|
+
) -> tuple[ProspectorProfile, Optional[str]]:
|
|
126
134
|
# Use the specified profiles
|
|
127
135
|
profile_provided = False
|
|
128
136
|
if len(config.profiles) > 0:
|
|
@@ -168,7 +176,7 @@ class ProspectorConfig:
|
|
|
168
176
|
|
|
169
177
|
# Use the strictness profile only if no profile has been given
|
|
170
178
|
if config.strictness is not None and config.strictness:
|
|
171
|
-
cmdline_implicit.append("strictness_
|
|
179
|
+
cmdline_implicit.append(f"strictness_{config.strictness}")
|
|
172
180
|
strictness = config.strictness
|
|
173
181
|
|
|
174
182
|
# the profile path is
|
|
@@ -190,24 +198,30 @@ class ProspectorConfig:
|
|
|
190
198
|
profile = ProspectorProfile.load(profile_name, profile_path, forced_inherits=forced_inherits)
|
|
191
199
|
except CannotParseProfile as cpe:
|
|
192
200
|
sys.stderr.write(
|
|
193
|
-
"
|
|
194
|
-
|
|
201
|
+
"\n".join(
|
|
202
|
+
[
|
|
203
|
+
"Failed to run:",
|
|
204
|
+
f"Could not parse profile {cpe.filepath} as it is not valid YAML",
|
|
205
|
+
f"{cpe.get_parse_message()}",
|
|
206
|
+
"",
|
|
207
|
+
]
|
|
208
|
+
)
|
|
195
209
|
)
|
|
196
210
|
sys.exit(1)
|
|
197
211
|
except ProfileNotFound as nfe:
|
|
198
212
|
search_path = ":".join(map(str, nfe.profile_path))
|
|
199
|
-
|
|
213
|
+
module_name = nfe.name.split(":")[0]
|
|
200
214
|
sys.stderr.write(
|
|
201
215
|
f"""Failed to run:
|
|
202
216
|
Could not find profile {nfe.name}.
|
|
203
|
-
Search path: {search_path}, or in module 'prospector_profile_{
|
|
217
|
+
Search path: {search_path}, or in module 'prospector_profile_{module_name}'
|
|
204
218
|
"""
|
|
205
219
|
)
|
|
206
220
|
sys.exit(1)
|
|
207
221
|
else:
|
|
208
222
|
return profile, strictness
|
|
209
223
|
|
|
210
|
-
def _find_used_libraries(self, config, profile):
|
|
224
|
+
def _find_used_libraries(self, config: setoptconf.config.Configuration, profile: ProspectorProfile) -> list[str]:
|
|
211
225
|
libraries = []
|
|
212
226
|
|
|
213
227
|
# Bring in adaptors that we automatically detect are needed
|
|
@@ -222,7 +236,7 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
|
|
|
222
236
|
|
|
223
237
|
return libraries
|
|
224
238
|
|
|
225
|
-
def _determine_tool_runners(self, config, profile):
|
|
239
|
+
def _determine_tool_runners(self, config: setoptconf.config.Configuration, profile: ProspectorProfile) -> list[str]:
|
|
226
240
|
if config.tools is None:
|
|
227
241
|
# we had no command line settings for an explicit list of
|
|
228
242
|
# tools, so we use the defaults
|
|
@@ -255,7 +269,9 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
|
|
|
255
269
|
|
|
256
270
|
return sorted(list(to_run))
|
|
257
271
|
|
|
258
|
-
def _determine_ignores(
|
|
272
|
+
def _determine_ignores(
|
|
273
|
+
self, config: setoptconf.config.Configuration, profile: ProspectorProfile, libraries: list[str]
|
|
274
|
+
) -> list[re.Pattern[str]]:
|
|
259
275
|
# Grab ignore patterns from the options
|
|
260
276
|
ignores = []
|
|
261
277
|
for pattern in config.ignore_patterns + profile.ignore_patterns:
|
|
@@ -265,10 +281,8 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
|
|
|
265
281
|
# ignore-patterns:
|
|
266
282
|
# uses: django
|
|
267
283
|
continue
|
|
268
|
-
|
|
284
|
+
with contextlib.suppress(sre_constants.error):
|
|
269
285
|
ignores.append(re.compile(pattern))
|
|
270
|
-
except sre_constants.error:
|
|
271
|
-
pass
|
|
272
286
|
|
|
273
287
|
# Convert ignore paths into patterns
|
|
274
288
|
boundary = r"(^|/|\\)%s(/|\\|$)"
|
|
@@ -284,7 +298,7 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
|
|
|
284
298
|
|
|
285
299
|
return ignores
|
|
286
300
|
|
|
287
|
-
def get_summary_information(self):
|
|
301
|
+
def get_summary_information(self) -> dict[str, Any]:
|
|
288
302
|
return {
|
|
289
303
|
"libraries": self.libraries,
|
|
290
304
|
"strictness": self.strictness,
|
|
@@ -292,64 +306,67 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
|
|
|
292
306
|
"tools": self.tools_to_run,
|
|
293
307
|
}
|
|
294
308
|
|
|
295
|
-
def exit_with_zero_on_success(self):
|
|
309
|
+
def exit_with_zero_on_success(self) -> bool:
|
|
296
310
|
return self.config.zero_exit
|
|
297
311
|
|
|
298
|
-
def get_disabled_messages(self, tool_name):
|
|
312
|
+
def get_disabled_messages(self, tool_name: str) -> list[str]:
|
|
299
313
|
return self.profile.get_disabled_messages(tool_name)
|
|
300
314
|
|
|
301
|
-
def
|
|
315
|
+
def get_enabled_messages(self, tool_name: str) -> list[str]:
|
|
316
|
+
return self.profile.get_enabled_messages(tool_name)
|
|
317
|
+
|
|
318
|
+
def use_external_config(self, _: Any) -> bool:
|
|
302
319
|
# Currently there is only one single global setting for whether to use
|
|
303
320
|
# global config, but this could be extended in the future
|
|
304
321
|
return not self.config.no_external_config
|
|
305
322
|
|
|
306
|
-
def tool_options(self, tool_name):
|
|
323
|
+
def tool_options(self, tool_name: str) -> dict[str, Any]:
|
|
307
324
|
tool = getattr(self.profile, tool_name, None)
|
|
308
325
|
if tool is None:
|
|
309
326
|
return {}
|
|
310
327
|
return tool.get("options", {})
|
|
311
328
|
|
|
312
|
-
def external_config_location(self, tool_name):
|
|
313
|
-
return getattr(self.config, "
|
|
329
|
+
def external_config_location(self, tool_name: str) -> Optional[Path]:
|
|
330
|
+
return getattr(self.config, f"{tool_name}_config_file", None)
|
|
314
331
|
|
|
315
332
|
@property
|
|
316
|
-
def die_on_tool_error(self):
|
|
333
|
+
def die_on_tool_error(self) -> bool:
|
|
317
334
|
return self.config.die_on_tool_error
|
|
318
335
|
|
|
319
336
|
@property
|
|
320
|
-
def summary_only(self):
|
|
337
|
+
def summary_only(self) -> bool:
|
|
321
338
|
return self.config.summary_only
|
|
322
339
|
|
|
323
340
|
@property
|
|
324
|
-
def messages_only(self):
|
|
341
|
+
def messages_only(self) -> bool:
|
|
325
342
|
return self.config.messages_only
|
|
326
343
|
|
|
327
344
|
@property
|
|
328
|
-
def quiet(self):
|
|
345
|
+
def quiet(self) -> bool:
|
|
329
346
|
return self.config.quiet
|
|
330
347
|
|
|
331
348
|
@property
|
|
332
|
-
def blending(self):
|
|
349
|
+
def blending(self) -> bool:
|
|
333
350
|
return self.config.blending
|
|
334
351
|
|
|
335
352
|
@property
|
|
336
|
-
def absolute_paths(self):
|
|
353
|
+
def absolute_paths(self) -> bool:
|
|
337
354
|
return self.config.absolute_paths
|
|
338
355
|
|
|
339
356
|
@property
|
|
340
|
-
def max_line_length(self):
|
|
357
|
+
def max_line_length(self) -> int:
|
|
341
358
|
return self.config.max_line_length
|
|
342
359
|
|
|
343
360
|
@property
|
|
344
|
-
def include_tool_stdout(self):
|
|
361
|
+
def include_tool_stdout(self) -> bool:
|
|
345
362
|
return self.config.include_tool_stdout
|
|
346
363
|
|
|
347
364
|
@property
|
|
348
|
-
def direct_tool_stdout(self):
|
|
365
|
+
def direct_tool_stdout(self) -> bool:
|
|
349
366
|
return self.config.direct_tool_stdout
|
|
350
367
|
|
|
351
368
|
@property
|
|
352
|
-
def show_profile(self):
|
|
369
|
+
def show_profile(self) -> bool:
|
|
353
370
|
return self.config.show_profile
|
|
354
371
|
|
|
355
372
|
@property
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# flake8: noqa
|
|
2
1
|
import importlib.metadata
|
|
2
|
+
from typing import Optional
|
|
3
3
|
|
|
4
4
|
import setoptconf as soc
|
|
5
5
|
|
|
@@ -12,7 +12,7 @@ __all__ = ("build_manager",)
|
|
|
12
12
|
_VERSION = importlib.metadata.version("prospector")
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def build_manager():
|
|
15
|
+
def build_manager() -> soc.ConfigurationManager:
|
|
16
16
|
manager = soc.ConfigurationManager("prospector")
|
|
17
17
|
|
|
18
18
|
manager.add(soc.BooleanSetting("zero_exit", default=False))
|
|
@@ -77,7 +77,7 @@ def build_manager():
|
|
|
77
77
|
return manager
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def build_default_sources():
|
|
80
|
+
def build_default_sources() -> list[soc.Source]:
|
|
81
81
|
sources = [
|
|
82
82
|
build_command_line_source(),
|
|
83
83
|
soc.EnvironmentVariableSource(),
|
|
@@ -99,7 +99,9 @@ def build_default_sources():
|
|
|
99
99
|
return sources
|
|
100
100
|
|
|
101
101
|
|
|
102
|
-
def build_command_line_source(
|
|
102
|
+
def build_command_line_source(
|
|
103
|
+
prog: Optional[str] = None, description: Optional[str] = "Performs static analysis of Python code"
|
|
104
|
+
) -> soc.CommandLineSource:
|
|
103
105
|
parser_options = {}
|
|
104
106
|
if prog is not None:
|
|
105
107
|
parser_options["prog"] = prog
|
|
@@ -184,9 +186,10 @@ def build_command_line_source(prog=None, description="Performs static analysis o
|
|
|
184
186
|
},
|
|
185
187
|
"output_format": {
|
|
186
188
|
"flags": ["-o", "--output-format"],
|
|
187
|
-
"help": "The output format. Valid values are:
|
|
188
|
-
"however a target file can be used instead by adding :path-to-output-file, eg, -o json:output.json"
|
|
189
|
-
|
|
189
|
+
"help": "The output format. Valid values are: {}. This will output to stdout by default, "
|
|
190
|
+
"however a target file can be used instead by adding :path-to-output-file, eg, -o json:output.json".format(
|
|
191
|
+
", ".join(sorted(FORMATTERS.keys()))
|
|
192
|
+
),
|
|
190
193
|
},
|
|
191
194
|
"absolute_paths": {
|
|
192
195
|
"help": "Whether to output absolute paths when referencing files "
|
|
@@ -197,9 +200,8 @@ def build_command_line_source(prog=None, description="Performs static analysis o
|
|
|
197
200
|
"flags": ["-t", "--tool"],
|
|
198
201
|
"help": "A list of tools to run. This lets you set exactly which "
|
|
199
202
|
"tools to run. To add extra tools to the defaults, see "
|
|
200
|
-
"--with-tool. Possible values are:
|
|
201
|
-
"default, the following tools will be run:
|
|
202
|
-
% (
|
|
203
|
+
"--with-tool. Possible values are: {}. By "
|
|
204
|
+
"default, the following tools will be run: {}".format(
|
|
203
205
|
", ".join(sorted(TOOLS.keys())),
|
|
204
206
|
", ".join(sorted(DEFAULT_TOOLS)),
|
|
205
207
|
),
|
|
@@ -208,14 +210,14 @@ def build_command_line_source(prog=None, description="Performs static analysis o
|
|
|
208
210
|
"flags": ["-w", "--with-tool"],
|
|
209
211
|
"help": "A list of tools to run in addition to the default tools. "
|
|
210
212
|
"To specify all tools explicitly, use the --tool argument. "
|
|
211
|
-
"Possible values are
|
|
213
|
+
"Possible values are {}.".format(", ".join(sorted(TOOLS.keys()))),
|
|
212
214
|
},
|
|
213
215
|
"without_tools": {
|
|
214
216
|
"flags": ["-W", "--without-tool"],
|
|
215
217
|
"help": "A list of tools that should not be run. Useful to turn off "
|
|
216
218
|
"only a single tool from the defaults. "
|
|
217
219
|
"To specify all tools explicitly, use the --tool argument. "
|
|
218
|
-
"Possible values are
|
|
220
|
+
"Possible values are {}.".format(", ".join(sorted(TOOLS.keys()))),
|
|
219
221
|
},
|
|
220
222
|
"profiles": {
|
|
221
223
|
"flags": ["-P", "--profile"],
|
prospector/config/datatype.py
CHANGED
|
@@ -6,7 +6,7 @@ from setoptconf.datatype import Choice
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class OutputChoice(Choice):
|
|
9
|
-
def sanitize(self, value):
|
|
9
|
+
def sanitize(self, value: str) -> tuple[str, list[str]]:
|
|
10
10
|
parsed = re.split(r"[;:]", value)
|
|
11
11
|
output_format, output_targets = parsed[0], parsed[1:]
|
|
12
12
|
checked_targets = []
|
prospector/encoding.py
CHANGED
|
@@ -7,8 +7,8 @@ from prospector.exceptions import CouldNotHandleEncoding, PermissionMissing
|
|
|
7
7
|
# mypy complains with 'Incompatible return value type (got "str", expected "bytes")'
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def read_py_file(filepath: Path):
|
|
11
|
-
#
|
|
10
|
+
def read_py_file(filepath: Path) -> str:
|
|
11
|
+
# See https://docs.python.org/3/library/tokenize.html#tokenize.detect_encoding
|
|
12
12
|
# first just see if the file is properly encoded
|
|
13
13
|
try:
|
|
14
14
|
with open(filepath, "rb") as bfile_:
|
prospector/exceptions.py
CHANGED
|
@@ -8,7 +8,6 @@ class FatalProspectorException(Exception):
|
|
|
8
8
|
Problems in prospector itself should raise this to notify
|
|
9
9
|
the user directly. Errors in dependent tools should be
|
|
10
10
|
caught and the user notified elegantly.
|
|
11
|
-
|
|
12
11
|
"""
|
|
13
12
|
|
|
14
13
|
# (see also the --die-on-tool-error flag)
|
|
@@ -27,10 +26,7 @@ class CouldNotHandleEncoding(Exception):
|
|
|
27
26
|
class PermissionMissing(Exception):
|
|
28
27
|
def __init__(self, path: Path):
|
|
29
28
|
docs_url = "https://prospector.landscape.io/en/master/profiles.html#ignoring-paths-and-patterns"
|
|
30
|
-
if os.path.isdir(path)
|
|
31
|
-
what = f"directory {path}"
|
|
32
|
-
else:
|
|
33
|
-
what = f"the file {path}"
|
|
29
|
+
what = f"directory {path}" if os.path.isdir(path) else f"the file {path}"
|
|
34
30
|
error_msg = (
|
|
35
31
|
f"The current user {os.getlogin()} does not have permission to open "
|
|
36
32
|
f"{what}. Either fix permissions or tell prospector to skip it "
|
prospector/finder.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from collections.abc import Iterable, Iterator
|
|
1
2
|
from pathlib import Path
|
|
2
|
-
from typing import Callable,
|
|
3
|
+
from typing import Callable, Optional
|
|
3
4
|
|
|
4
5
|
from prospector.exceptions import PermissionMissing
|
|
5
6
|
from prospector.pathutils import is_python_module, is_python_package, is_virtualenv
|
|
@@ -17,7 +18,7 @@ class FileFinder:
|
|
|
17
18
|
is basically to know which files to pass to which tools to be inspected.
|
|
18
19
|
"""
|
|
19
20
|
|
|
20
|
-
def __init__(self, *provided_paths: Path, exclusion_filters: Iterable[Callable] = None):
|
|
21
|
+
def __init__(self, *provided_paths: Path, exclusion_filters: Optional[Iterable[Callable[[Path], bool]]] = None):
|
|
21
22
|
"""
|
|
22
23
|
:param provided_paths:
|
|
23
24
|
A list of Path objects to search for files and modules - can be either directories or files
|
|
@@ -44,7 +45,7 @@ class FileFinder:
|
|
|
44
45
|
if path.is_dir():
|
|
45
46
|
self._provided_dirs.append(path)
|
|
46
47
|
|
|
47
|
-
def make_syspath(self) ->
|
|
48
|
+
def make_syspath(self) -> list[Path]:
|
|
48
49
|
paths = set()
|
|
49
50
|
for path in self._provided_dirs:
|
|
50
51
|
paths.add(path)
|
|
@@ -55,7 +56,7 @@ class FileFinder:
|
|
|
55
56
|
def is_excluded(self, path: Path) -> bool:
|
|
56
57
|
return any(filt(path) for filt in self._exclusion_filters)
|
|
57
58
|
|
|
58
|
-
def _filter(self, paths: Iterable[Path]) ->
|
|
59
|
+
def _filter(self, paths: Iterable[Path]) -> list[Path]:
|
|
59
60
|
return [path for path in paths if not self.is_excluded(path)]
|
|
60
61
|
|
|
61
62
|
def _walk(self, directory: Path) -> Iterator[Path]:
|
|
@@ -71,7 +72,7 @@ class FileFinder:
|
|
|
71
72
|
yield path
|
|
72
73
|
|
|
73
74
|
@property
|
|
74
|
-
def files(self) ->
|
|
75
|
+
def files(self) -> list[Path]:
|
|
75
76
|
"""
|
|
76
77
|
List every individual file found from the given configuration.
|
|
77
78
|
|
|
@@ -89,7 +90,7 @@ class FileFinder:
|
|
|
89
90
|
return self._filter(files)
|
|
90
91
|
|
|
91
92
|
@property
|
|
92
|
-
def python_packages(self) ->
|
|
93
|
+
def python_packages(self) -> list[Path]:
|
|
93
94
|
"""
|
|
94
95
|
Lists every directory found in the given configuration which is a python module (that is,
|
|
95
96
|
contains an `__init__.py` file).
|
|
@@ -99,7 +100,7 @@ class FileFinder:
|
|
|
99
100
|
return self._filter(d for d in self.directories if is_python_package(d))
|
|
100
101
|
|
|
101
102
|
@property
|
|
102
|
-
def python_modules(self) ->
|
|
103
|
+
def python_modules(self) -> list[Path]:
|
|
103
104
|
"""
|
|
104
105
|
Lists every directory found in the given configuration which is a python module (that is,
|
|
105
106
|
contains an `__init__.py` file).
|
|
@@ -109,7 +110,7 @@ class FileFinder:
|
|
|
109
110
|
return self._filter(f for f in self.files if is_python_module(f))
|
|
110
111
|
|
|
111
112
|
@property
|
|
112
|
-
def directories(self) ->
|
|
113
|
+
def directories(self) -> list[Path]:
|
|
113
114
|
"""
|
|
114
115
|
Lists every directory found from the given configuration, regardless of its contents.
|
|
115
116
|
|