prospector 1.10.0__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.0.dist-info → prospector-1.13.2.dist-info}/METADATA +13 -14
- prospector-1.13.2.dist-info/RECORD +71 -0
- README.rst +0 -208
- prospector-1.10.0.dist-info/LICENSE +0 -339
- prospector-1.10.0.dist-info/RECORD +0 -71
- /LICENSE → /prospector-1.13.2.dist-info/LICENSE +0 -0
- {prospector-1.10.0.dist-info → prospector-1.13.2.dist-info}/WHEEL +0 -0
- {prospector-1.10.0.dist-info → prospector-1.13.2.dist-info}/entry_points.txt +0 -0
prospector/formatters/base.py
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
|
|
3
|
+
from prospector.profiles.profile import ProspectorProfile
|
|
4
|
+
|
|
3
5
|
__all__ = ("Formatter",)
|
|
4
6
|
|
|
5
7
|
from pathlib import Path
|
|
8
|
+
from typing import Any, Optional
|
|
6
9
|
|
|
7
|
-
from prospector.message import Message
|
|
10
|
+
from prospector.message import Location, Message
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
class Formatter(ABC):
|
|
11
|
-
def __init__(
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
summary: dict[str, Any],
|
|
17
|
+
messages: list[Message],
|
|
18
|
+
profile: ProspectorProfile,
|
|
19
|
+
paths_relative_to: Optional[Path] = None,
|
|
20
|
+
) -> None:
|
|
12
21
|
self.summary = summary
|
|
13
22
|
self.messages = messages
|
|
14
23
|
self.profile = profile
|
|
15
24
|
self.paths_relative_to = paths_relative_to
|
|
16
25
|
|
|
17
26
|
@abstractmethod
|
|
18
|
-
def render(self, summary=True, messages=True, profile=False):
|
|
27
|
+
def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str:
|
|
19
28
|
raise NotImplementedError
|
|
20
29
|
|
|
21
|
-
def _make_path(self,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
elif path.is_absolute():
|
|
25
|
-
path = path.relative_to(self.paths_relative_to)
|
|
26
|
-
return str(path)
|
|
30
|
+
def _make_path(self, location: Location) -> Path:
|
|
31
|
+
path_ = location.relative_path(self.paths_relative_to)
|
|
32
|
+
return Path() if path_ is None else path_
|
|
27
33
|
|
|
28
|
-
def _message_to_dict(self, message: Message) -> dict:
|
|
34
|
+
def _message_to_dict(self, message: Message) -> dict[str, Any]:
|
|
29
35
|
loc = {
|
|
30
|
-
"path": self._make_path(message.location
|
|
36
|
+
"path": str(self._make_path(message.location)),
|
|
31
37
|
"module": message.location.module,
|
|
32
38
|
"function": message.location.function,
|
|
33
39
|
"line": message.location.line,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from prospector.formatters.base import Formatter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SummaryFormatter(Formatter):
|
|
5
|
+
"""
|
|
6
|
+
This abstract formatter is used to output a summary of the prospector run.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
summary_labels = (
|
|
10
|
+
("started", "Started", None),
|
|
11
|
+
("completed", "Finished", None),
|
|
12
|
+
("time_taken", "Time Taken", lambda x: f"{x} seconds"),
|
|
13
|
+
("formatter", "Formatter", None),
|
|
14
|
+
("profiles", "Profiles", None),
|
|
15
|
+
("strictness", "Strictness", None),
|
|
16
|
+
("libraries", "Libraries Used", ", ".join),
|
|
17
|
+
("tools", "Tools Run", ", ".join),
|
|
18
|
+
("adaptors", "Adaptors", ", ".join),
|
|
19
|
+
("message_count", "Messages Found", None),
|
|
20
|
+
("external_config", "External Config", None),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def render_summary(self) -> str:
|
|
24
|
+
output = [
|
|
25
|
+
"Check Information",
|
|
26
|
+
"=================",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
label_width = max(len(label[1]) for label in self.summary_labels)
|
|
30
|
+
|
|
31
|
+
for key, label, formatter in self.summary_labels:
|
|
32
|
+
if key in self.summary:
|
|
33
|
+
value = self.summary[key]
|
|
34
|
+
if formatter is not None:
|
|
35
|
+
value = formatter(value)
|
|
36
|
+
output.append(f" {label.rjust(label_width)}: {value}")
|
|
37
|
+
|
|
38
|
+
return "\n".join(output)
|
|
39
|
+
|
|
40
|
+
def render_profile(self) -> str:
|
|
41
|
+
output = ["Profile", "=======", "", self.profile.as_yaml().strip()]
|
|
42
|
+
|
|
43
|
+
return "\n".join(output)
|
prospector/formatters/emacs.py
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
from prospector.formatters.text import TextFormatter
|
|
2
|
+
from prospector.message import Message
|
|
2
3
|
|
|
3
4
|
__all__ = ("EmacsFormatter",)
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class EmacsFormatter(TextFormatter):
|
|
7
|
-
def render_message(self, message):
|
|
8
|
+
def render_message(self, message: Message) -> str:
|
|
8
9
|
output = [
|
|
9
10
|
"%s:%s:%d:"
|
|
10
11
|
% (
|
|
11
|
-
self._make_path(message.location
|
|
12
|
+
self._make_path(message.location),
|
|
12
13
|
message.location.line,
|
|
13
14
|
(message.location.character or 0) + 1,
|
|
14
15
|
),
|
|
15
|
-
" L
|
|
16
|
-
% (
|
|
16
|
+
" L{}:{} {}: {} - {}".format(
|
|
17
17
|
message.location.line or "-",
|
|
18
18
|
message.location.character if message.location.line else "-",
|
|
19
19
|
message.location.function,
|
|
20
20
|
message.source,
|
|
21
21
|
message.code,
|
|
22
22
|
),
|
|
23
|
-
"
|
|
23
|
+
f" {message.message}",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
return "\n".join(output)
|
prospector/formatters/grouped.py
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
2
4
|
|
|
3
5
|
from prospector.formatters.text import TextFormatter
|
|
6
|
+
from prospector.message import Message
|
|
4
7
|
|
|
5
8
|
__all__ = ("GroupedFormatter",)
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class GroupedFormatter(TextFormatter):
|
|
9
|
-
def render_messages(self):
|
|
12
|
+
def render_messages(self) -> str:
|
|
10
13
|
output = [
|
|
11
14
|
"Messages",
|
|
12
15
|
"========",
|
|
13
16
|
"",
|
|
14
17
|
]
|
|
15
18
|
|
|
16
|
-
groups = defaultdict(lambda: defaultdict(list))
|
|
19
|
+
groups: dict[Path, dict[Optional[int], list[Message]]] = defaultdict(lambda: defaultdict(list))
|
|
17
20
|
|
|
18
21
|
for message in self.messages:
|
|
19
|
-
groups[self._make_path(message.location
|
|
22
|
+
groups[self._make_path(message.location)][message.location.line].append(message)
|
|
20
23
|
|
|
21
24
|
for filename in sorted(groups.keys()):
|
|
22
25
|
output.append(str(filename))
|
|
23
26
|
|
|
24
27
|
for line in sorted(groups[filename].keys(), key=lambda x: 0 if x is None else int(x)):
|
|
25
|
-
output.append(" Line:
|
|
28
|
+
output.append(f" Line: {line}")
|
|
26
29
|
|
|
27
30
|
for message in groups[filename][line]:
|
|
28
31
|
output.append(
|
|
29
|
-
"
|
|
30
|
-
% (
|
|
32
|
+
" {}: {} / {}{}".format(
|
|
31
33
|
message.source,
|
|
32
34
|
message.code,
|
|
33
35
|
message.message,
|
|
34
|
-
(" (col
|
|
36
|
+
(f" (col {message.location.character})") if message.location.character else "",
|
|
35
37
|
)
|
|
36
38
|
)
|
|
37
39
|
|
prospector/formatters/json.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from datetime import datetime
|
|
3
|
+
from typing import Any
|
|
3
4
|
|
|
4
5
|
from prospector.formatters.base import Formatter
|
|
5
6
|
|
|
@@ -7,8 +8,8 @@ __all__ = ("JsonFormatter",)
|
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class JsonFormatter(Formatter):
|
|
10
|
-
def render(self, summary=True, messages=True, profile=False):
|
|
11
|
-
output = {}
|
|
11
|
+
def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str:
|
|
12
|
+
output: dict[str, Any] = {}
|
|
12
13
|
|
|
13
14
|
if summary:
|
|
14
15
|
# we need to slightly change the types and format
|
prospector/formatters/pylint.py
CHANGED
|
@@ -1,45 +1,77 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
3
|
|
|
4
|
-
from prospector.formatters.
|
|
4
|
+
from prospector.formatters.base_summary import SummaryFormatter
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class PylintFormatter(
|
|
8
|
-
|
|
7
|
+
class PylintFormatter(SummaryFormatter):
|
|
9
8
|
"""
|
|
10
9
|
This formatter outputs messages in the same way as pylint -f parseable , which is used by several
|
|
11
10
|
tools to parse pylint output. This formatter is therefore a compatibility shim between tools built
|
|
12
11
|
on top of pylint and prospector itself.
|
|
13
12
|
"""
|
|
14
13
|
|
|
15
|
-
def
|
|
16
|
-
# this formatter will always ignore the summary and profile
|
|
14
|
+
def render_messages(self) -> list[str]:
|
|
17
15
|
cur_loc = None
|
|
18
16
|
output = []
|
|
19
17
|
for message in sorted(self.messages):
|
|
20
18
|
if cur_loc != message.location.path:
|
|
21
19
|
cur_loc = message.location.path
|
|
22
|
-
module_name = self._make_path(message.location
|
|
20
|
+
module_name = str(self._make_path(message.location)).replace(os.path.sep, ".")
|
|
23
21
|
module_name = re.sub(r"(\.__init__)?\.py$", "", module_name)
|
|
24
22
|
|
|
25
|
-
header = "************* Module
|
|
23
|
+
header = f"************* Module {module_name}"
|
|
26
24
|
output.append(header)
|
|
27
25
|
|
|
28
26
|
# ={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
|
|
29
27
|
# prospector/configuration.py:65: [missing-docstring(missing-docstring), build_default_sources] \
|
|
30
28
|
# Missing function docstring
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
template_location = (
|
|
31
|
+
""
|
|
32
|
+
if message.location.path is None
|
|
33
|
+
else "%(path)s"
|
|
34
|
+
if message.location.line is None
|
|
35
|
+
else "%(path)s:%(line)s"
|
|
36
|
+
if message.location.character is None
|
|
37
|
+
else "%(path)s:%(line)s:%(character)s"
|
|
38
|
+
)
|
|
39
|
+
template_code = (
|
|
40
|
+
"(%(source)s)"
|
|
41
|
+
if message.code is None
|
|
42
|
+
else "%(code)s(%(source)s)"
|
|
43
|
+
if message.location.function is None
|
|
44
|
+
else "[%(code)s(%(source)s), %(function)s]"
|
|
45
|
+
)
|
|
46
|
+
template = (
|
|
47
|
+
f"{template_location}: {template_code}: %(message)s"
|
|
48
|
+
if template_location
|
|
49
|
+
else f"{template_code}: %(message)s"
|
|
50
|
+
)
|
|
51
|
+
|
|
33
52
|
output.append(
|
|
34
53
|
template
|
|
35
54
|
% {
|
|
36
|
-
"path": self._make_path(message.location
|
|
55
|
+
"path": self._make_path(message.location),
|
|
37
56
|
"line": message.location.line,
|
|
57
|
+
"character": message.location.character,
|
|
38
58
|
"source": message.source,
|
|
39
59
|
"code": message.code,
|
|
40
60
|
"function": message.location.function,
|
|
41
61
|
"message": message.message.strip(),
|
|
42
62
|
}
|
|
43
63
|
)
|
|
64
|
+
return output
|
|
65
|
+
|
|
66
|
+
def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str:
|
|
67
|
+
output: list[str] = []
|
|
68
|
+
if messages:
|
|
69
|
+
output.extend(self.render_messages())
|
|
70
|
+
if profile:
|
|
71
|
+
output.append("")
|
|
72
|
+
output.append(self.render_profile())
|
|
73
|
+
if summary:
|
|
74
|
+
output.append("")
|
|
75
|
+
output.append(self.render_summary())
|
|
44
76
|
|
|
45
77
|
return "\n".join(output)
|
prospector/formatters/text.py
CHANGED
|
@@ -1,63 +1,20 @@
|
|
|
1
|
-
from prospector.formatters.
|
|
1
|
+
from prospector.formatters.base_summary import SummaryFormatter
|
|
2
|
+
from prospector.message import Message
|
|
2
3
|
|
|
3
4
|
__all__ = ("TextFormatter",)
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TextFormatter(Formatter):
|
|
10
|
-
summary_labels = (
|
|
11
|
-
("started", "Started"),
|
|
12
|
-
("completed", "Finished"),
|
|
13
|
-
("time_taken", "Time Taken", lambda x: "%s seconds" % x),
|
|
14
|
-
("formatter", "Formatter"),
|
|
15
|
-
("profiles", "Profiles"),
|
|
16
|
-
("strictness", "Strictness"),
|
|
17
|
-
("libraries", "Libraries Used", lambda x: ", ".join(x)),
|
|
18
|
-
("tools", "Tools Run", lambda x: ", ".join(x)),
|
|
19
|
-
("adaptors", "Adaptors", lambda x: ", ".join(x)),
|
|
20
|
-
("message_count", "Messages Found"),
|
|
21
|
-
("external_config", "External Config"),
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
def render_summary(self):
|
|
25
|
-
output = [
|
|
26
|
-
"Check Information",
|
|
27
|
-
"=================",
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
label_width = max(len(label[1]) for label in self.summary_labels)
|
|
31
|
-
|
|
32
|
-
for summary_label in self.summary_labels:
|
|
33
|
-
key = summary_label[0]
|
|
34
|
-
if key in self.summary:
|
|
35
|
-
label = summary_label[1]
|
|
36
|
-
if len(summary_label) > 2:
|
|
37
|
-
value = summary_label[2](self.summary[key])
|
|
38
|
-
else:
|
|
39
|
-
value = self.summary[key]
|
|
40
|
-
output.append(
|
|
41
|
-
" %s: %s"
|
|
42
|
-
% (
|
|
43
|
-
label.rjust(label_width),
|
|
44
|
-
value,
|
|
45
|
-
)
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
return "\n".join(output)
|
|
49
|
-
|
|
50
|
-
def render_message(self, message):
|
|
7
|
+
class TextFormatter(SummaryFormatter):
|
|
8
|
+
def render_message(self, message: Message) -> str:
|
|
51
9
|
output = []
|
|
52
10
|
|
|
53
11
|
if message.location.module:
|
|
54
|
-
output.append(f"{message.location.module} ({self._make_path(message.location
|
|
12
|
+
output.append(f"{message.location.module} ({self._make_path(message.location)}):")
|
|
55
13
|
else:
|
|
56
|
-
output.append("
|
|
14
|
+
output.append(f"{self._make_path(message.location)}:")
|
|
57
15
|
|
|
58
16
|
output.append(
|
|
59
|
-
" L
|
|
60
|
-
% (
|
|
17
|
+
" L{}:{} {}: {} - {}".format(
|
|
61
18
|
message.location.line or "-",
|
|
62
19
|
message.location.character if message.location.character else "-",
|
|
63
20
|
message.location.function,
|
|
@@ -66,11 +23,11 @@ class TextFormatter(Formatter):
|
|
|
66
23
|
)
|
|
67
24
|
)
|
|
68
25
|
|
|
69
|
-
output.append("
|
|
26
|
+
output.append(f" {message.message}")
|
|
70
27
|
|
|
71
28
|
return "\n".join(output)
|
|
72
29
|
|
|
73
|
-
def render_messages(self):
|
|
30
|
+
def render_messages(self) -> str:
|
|
74
31
|
output = [
|
|
75
32
|
"Messages",
|
|
76
33
|
"========",
|
|
@@ -83,12 +40,7 @@ class TextFormatter(Formatter):
|
|
|
83
40
|
|
|
84
41
|
return "\n".join(output)
|
|
85
42
|
|
|
86
|
-
def
|
|
87
|
-
output = ["Profile", "=======", "", self.profile.as_yaml().strip()]
|
|
88
|
-
|
|
89
|
-
return "\n".join(output)
|
|
90
|
-
|
|
91
|
-
def render(self, summary=True, messages=True, profile=False):
|
|
43
|
+
def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str:
|
|
92
44
|
output = []
|
|
93
45
|
if messages and self.messages: # if there are no messages, don't render an empty header
|
|
94
46
|
output.append(self.render_messages())
|
prospector/formatters/vscode.py
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
3
|
|
|
4
|
-
from prospector.formatters.
|
|
4
|
+
from prospector.formatters.base_summary import SummaryFormatter
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class VSCodeFormatter(
|
|
8
|
-
|
|
7
|
+
class VSCodeFormatter(SummaryFormatter):
|
|
9
8
|
"""
|
|
10
9
|
This formatter outputs messages in the same way as vscode prospector linter expects.
|
|
11
10
|
"""
|
|
12
11
|
|
|
13
|
-
def
|
|
14
|
-
# this formatter will always ignore the summary and profile
|
|
12
|
+
def render_messages(self) -> list[str]:
|
|
15
13
|
cur_loc = None
|
|
16
14
|
output = []
|
|
17
15
|
|
|
18
16
|
for message in sorted(self.messages):
|
|
19
17
|
if cur_loc != message.location.path:
|
|
20
18
|
cur_loc = message.location.path
|
|
21
|
-
module_name = self._make_path(message.location
|
|
19
|
+
module_name = str(self._make_path(message.location)).replace(os.path.sep, ".")
|
|
22
20
|
module_name = re.sub(r"(\.__init__)?\.py$", "", module_name)
|
|
23
21
|
|
|
24
|
-
header = "************* Module
|
|
22
|
+
header = f"************* Module {module_name}"
|
|
25
23
|
output.append(header)
|
|
26
24
|
|
|
27
25
|
template = "%(line)s,%(character)s,%(code)s,%(code)s:%(source)s %(message)s"
|
|
@@ -35,5 +33,17 @@ class VSCodeFormatter(Formatter):
|
|
|
35
33
|
"message": message.message.strip(),
|
|
36
34
|
}
|
|
37
35
|
)
|
|
36
|
+
return output
|
|
37
|
+
|
|
38
|
+
def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str:
|
|
39
|
+
output: list[str] = []
|
|
40
|
+
if messages:
|
|
41
|
+
output.extend(self.render_messages())
|
|
42
|
+
if profile:
|
|
43
|
+
output.append("")
|
|
44
|
+
output.append(self.render_profile())
|
|
45
|
+
if summary:
|
|
46
|
+
output.append("")
|
|
47
|
+
output.append(self.render_summary())
|
|
38
48
|
|
|
39
49
|
return "\n".join(output)
|
prospector/formatters/xunit.py
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
from xml.dom.minidom import Document
|
|
1
|
+
from xml.dom.minidom import Document # nosec
|
|
2
2
|
|
|
3
3
|
from prospector.formatters.base import Formatter
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class XunitFormatter(Formatter):
|
|
7
|
-
|
|
8
7
|
"""
|
|
9
8
|
This formatter outputs messages in the Xunit xml format, which is used by several
|
|
10
9
|
CI tools to parse output. This formatter is therefore a compatibility shim between tools built
|
|
11
10
|
to use Xunit and prospector itself.
|
|
12
11
|
"""
|
|
13
12
|
|
|
14
|
-
def render(self, summary=True, messages=True, profile=False):
|
|
13
|
+
def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str:
|
|
15
14
|
xml_doc = Document()
|
|
16
15
|
|
|
17
16
|
testsuite_el = xml_doc.createElement("testsuite")
|
|
18
17
|
testsuite_el.setAttribute("errors", str(self.summary["message_count"]))
|
|
19
18
|
testsuite_el.setAttribute("failures", "0")
|
|
20
|
-
testsuite_el.setAttribute("name", "prospector
|
|
19
|
+
testsuite_el.setAttribute("name", "prospector-{}".format("-".join(self.summary["tools"])))
|
|
21
20
|
testsuite_el.setAttribute("tests", str(self.summary["message_count"]))
|
|
22
21
|
testsuite_el.setAttribute("time", str(self.summary["time_taken"]))
|
|
23
22
|
xml_doc.appendChild(testsuite_el)
|
|
@@ -35,14 +34,14 @@ class XunitFormatter(Formatter):
|
|
|
35
34
|
|
|
36
35
|
for message in sorted(self.messages):
|
|
37
36
|
testcase_el = xml_doc.createElement("testcase")
|
|
38
|
-
testcase_el.setAttribute("name", f"{self._make_path(message.location
|
|
37
|
+
testcase_el.setAttribute("name", f"{self._make_path(message.location)}-{message.location.line}")
|
|
39
38
|
|
|
40
39
|
failure_el = xml_doc.createElement("error")
|
|
41
40
|
failure_el.setAttribute("message", message.message.strip())
|
|
42
|
-
failure_el.setAttribute("type", "
|
|
41
|
+
failure_el.setAttribute("type", f"{message.source} Error")
|
|
43
42
|
template = "%(path)s:%(line)s: [%(code)s(%(source)s), %(function)s] %(message)s"
|
|
44
43
|
cdata = template % {
|
|
45
|
-
"path": self._make_path(message.location
|
|
44
|
+
"path": self._make_path(message.location),
|
|
46
45
|
"line": message.location.line,
|
|
47
46
|
"source": message.source,
|
|
48
47
|
"code": message.code,
|
prospector/formatters/yaml.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
import yaml
|
|
2
4
|
|
|
3
5
|
from prospector.formatters.base import Formatter
|
|
@@ -6,8 +8,8 @@ __all__ = ("YamlFormatter",)
|
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class YamlFormatter(Formatter):
|
|
9
|
-
def render(self, summary=True, messages=True, profile=False):
|
|
10
|
-
output = {}
|
|
11
|
+
def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str:
|
|
12
|
+
output: dict[str, Any] = {}
|
|
11
13
|
|
|
12
14
|
if summary:
|
|
13
15
|
output["summary"] = self.summary
|
prospector/message.py
CHANGED
|
@@ -3,13 +3,22 @@ from typing import Optional, Union
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Location:
|
|
6
|
+
_path: Optional[Path]
|
|
7
|
+
|
|
6
8
|
def __init__(
|
|
7
|
-
self,
|
|
9
|
+
self,
|
|
10
|
+
path: Optional[Union[Path, str]],
|
|
11
|
+
module: Optional[str],
|
|
12
|
+
function: Optional[str],
|
|
13
|
+
line: Optional[int],
|
|
14
|
+
character: Optional[int],
|
|
8
15
|
):
|
|
9
16
|
if isinstance(path, Path):
|
|
10
|
-
self._path = path
|
|
17
|
+
self._path = path.absolute()
|
|
11
18
|
elif isinstance(path, str):
|
|
12
|
-
self._path = Path(path)
|
|
19
|
+
self._path = Path(path).absolute()
|
|
20
|
+
elif path is None:
|
|
21
|
+
self._path = None
|
|
13
22
|
else:
|
|
14
23
|
raise ValueError
|
|
15
24
|
self.module = module or None
|
|
@@ -18,14 +27,16 @@ class Location:
|
|
|
18
27
|
self.character = None if character == -1 else character
|
|
19
28
|
|
|
20
29
|
@property
|
|
21
|
-
def path(self):
|
|
30
|
+
def path(self) -> Optional[Path]:
|
|
22
31
|
return self._path
|
|
23
32
|
|
|
24
|
-
def absolute_path(self) -> Path:
|
|
25
|
-
return self._path
|
|
33
|
+
def absolute_path(self) -> Optional[Path]:
|
|
34
|
+
return self._path
|
|
26
35
|
|
|
27
|
-
def relative_path(self, root: Path) -> Path:
|
|
28
|
-
|
|
36
|
+
def relative_path(self, root: Optional[Path]) -> Optional[Path]:
|
|
37
|
+
if self._path is None:
|
|
38
|
+
return None
|
|
39
|
+
return self._path.relative_to(root) if root else self._path
|
|
29
40
|
|
|
30
41
|
def __repr__(self) -> str:
|
|
31
42
|
return f"{self._path}:L{self.line}:{self.character}"
|
|
@@ -38,9 +49,16 @@ class Location:
|
|
|
38
49
|
return False
|
|
39
50
|
return self._path == other._path and self.line == other.line and self.character == other.character
|
|
40
51
|
|
|
41
|
-
def __lt__(self, other:
|
|
52
|
+
def __lt__(self, other: "Location") -> bool:
|
|
42
53
|
if not isinstance(other, Location):
|
|
43
54
|
raise ValueError
|
|
55
|
+
|
|
56
|
+
if self._path is None and other._path is None:
|
|
57
|
+
return False
|
|
58
|
+
if self._path is None:
|
|
59
|
+
return True
|
|
60
|
+
if other._path is None:
|
|
61
|
+
return False
|
|
44
62
|
if self._path == other._path:
|
|
45
63
|
if self.line == other.line:
|
|
46
64
|
return (self.character or -1) < (other.character or -1)
|
|
@@ -65,7 +83,7 @@ class Message:
|
|
|
65
83
|
return self.code == other.code
|
|
66
84
|
return False
|
|
67
85
|
|
|
68
|
-
def __lt__(self, other) -> bool:
|
|
86
|
+
def __lt__(self, other: "Message") -> bool:
|
|
69
87
|
if self.location == other.location:
|
|
70
88
|
return self.code < other.code
|
|
71
89
|
return self.location < other.location
|
|
@@ -76,10 +94,10 @@ def make_tool_error_message(
|
|
|
76
94
|
source: str,
|
|
77
95
|
code: str,
|
|
78
96
|
message: str,
|
|
79
|
-
line: int =
|
|
80
|
-
character: int =
|
|
81
|
-
module: str = None,
|
|
82
|
-
function: str = None,
|
|
97
|
+
line: Optional[int] = None,
|
|
98
|
+
character: Optional[int] = None,
|
|
99
|
+
module: Optional[str] = None,
|
|
100
|
+
function: Optional[str] = None,
|
|
83
101
|
) -> Message:
|
|
84
102
|
location = Location(path=filepath, module=module, function=function, line=line, character=character)
|
|
85
103
|
return Message(source=source, code=code, location=location, message=message)
|
prospector/pathutils.py
CHANGED
|
@@ -12,11 +12,7 @@ def is_python_module(path: Path) -> bool:
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def is_virtualenv(path: Path) -> bool:
|
|
15
|
-
if os.name == "nt"
|
|
16
|
-
# Windows!
|
|
17
|
-
clues = ("Scripts", "lib", "include")
|
|
18
|
-
else:
|
|
19
|
-
clues = ("bin", "lib", "include")
|
|
15
|
+
clues = ("Scripts", "lib", "include") if os.name == "nt" else ("bin", "lib", "include")
|
|
20
16
|
|
|
21
17
|
try:
|
|
22
18
|
# just get the name, iterdir returns absolute paths by default
|
|
@@ -37,8 +33,5 @@ def is_virtualenv(path: Path) -> bool:
|
|
|
37
33
|
# if we do have all three directories, make sure that it's not
|
|
38
34
|
# just a coincidence by doing some heuristics on the rest of
|
|
39
35
|
# the directory
|
|
40
|
-
if
|
|
41
|
-
|
|
42
|
-
return False
|
|
43
|
-
|
|
44
|
-
return True
|
|
36
|
+
# if there are more than 7 things it's probably not a virtualenvironment
|
|
37
|
+
return len(dircontents) <= 7
|
prospector/postfilter.py
CHANGED
|
@@ -29,7 +29,7 @@ def filter_messages(filepaths: List[Path], messages: List[Message]) -> List[Mess
|
|
|
29
29
|
filtered = []
|
|
30
30
|
for message in messages:
|
|
31
31
|
# first get rid of the pylint informational messages
|
|
32
|
-
relative_message_path =
|
|
32
|
+
relative_message_path = message.location.path
|
|
33
33
|
|
|
34
34
|
if message.source == "pylint" and message.code in (
|
|
35
35
|
"suppressed-message",
|
|
@@ -42,15 +42,16 @@ def filter_messages(filepaths: List[Path], messages: List[Message]) -> List[Mess
|
|
|
42
42
|
continue
|
|
43
43
|
|
|
44
44
|
# some lines are skipped entirely by messages
|
|
45
|
-
if relative_message_path in lines_to_ignore:
|
|
46
|
-
|
|
47
|
-
continue
|
|
45
|
+
if relative_message_path in lines_to_ignore and message.location.line in lines_to_ignore[relative_message_path]:
|
|
46
|
+
continue
|
|
48
47
|
|
|
49
48
|
# and some lines have only certain messages explicitly ignored
|
|
50
|
-
if
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
if (
|
|
50
|
+
relative_message_path in messages_to_ignore
|
|
51
|
+
and message.location.line in messages_to_ignore[relative_message_path]
|
|
52
|
+
and message.code in messages_to_ignore[relative_message_path][message.location.line]
|
|
53
|
+
):
|
|
54
|
+
continue
|
|
54
55
|
|
|
55
56
|
# otherwise this message was not filtered
|
|
56
57
|
filtered.append(message)
|