prospector 1.13.3__tar.gz → 1.14.1__tar.gz

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.
Files changed (72) hide show
  1. {prospector-1.13.3 → prospector-1.14.1}/PKG-INFO +1 -1
  2. {prospector-1.13.3 → prospector-1.14.1}/prospector/blender_combinations.yaml +5 -0
  3. {prospector-1.13.3 → prospector-1.14.1}/prospector/finder.py +12 -9
  4. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/__init__.py +2 -1
  5. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/base.py +10 -1
  6. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/emacs.py +1 -6
  7. prospector-1.14.1/prospector/formatters/pylint.py +80 -0
  8. prospector-1.13.3/prospector/formatters/pylint.py → prospector-1.14.1/prospector/formatters/pylint_parseable.py +2 -5
  9. {prospector-1.13.3 → prospector-1.14.1}/prospector/message.py +18 -2
  10. {prospector-1.13.3 → prospector-1.14.1}/prospector/postfilter.py +7 -2
  11. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/strictness_high.yaml +7 -0
  12. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/strictness_medium.yaml +4 -0
  13. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/strictness_veryhigh.yaml +4 -0
  14. {prospector-1.13.3 → prospector-1.14.1}/prospector/suppression.py +43 -13
  15. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/bandit/__init__.py +11 -2
  16. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/mypy/__init__.py +1 -1
  17. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/pylint/collector.py +8 -2
  18. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/ruff/__init__.py +8 -3
  19. {prospector-1.13.3 → prospector-1.14.1}/pyproject.toml +4 -3
  20. {prospector-1.13.3 → prospector-1.14.1}/setup.py +1 -1
  21. {prospector-1.13.3 → prospector-1.14.1}/LICENSE +0 -0
  22. {prospector-1.13.3 → prospector-1.14.1}/README.rst +0 -0
  23. {prospector-1.13.3 → prospector-1.14.1}/prospector/__init__.py +0 -0
  24. {prospector-1.13.3 → prospector-1.14.1}/prospector/__main__.py +0 -0
  25. {prospector-1.13.3 → prospector-1.14.1}/prospector/autodetect.py +0 -0
  26. {prospector-1.13.3 → prospector-1.14.1}/prospector/blender.py +0 -0
  27. {prospector-1.13.3 → prospector-1.14.1}/prospector/compat.py +0 -0
  28. {prospector-1.13.3 → prospector-1.14.1}/prospector/config/__init__.py +0 -0
  29. {prospector-1.13.3 → prospector-1.14.1}/prospector/config/configuration.py +0 -0
  30. {prospector-1.13.3 → prospector-1.14.1}/prospector/config/datatype.py +0 -0
  31. {prospector-1.13.3 → prospector-1.14.1}/prospector/encoding.py +0 -0
  32. {prospector-1.13.3 → prospector-1.14.1}/prospector/exceptions.py +0 -0
  33. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/base_summary.py +0 -0
  34. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/grouped.py +0 -0
  35. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/json.py +0 -0
  36. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/text.py +0 -0
  37. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/vscode.py +0 -0
  38. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/xunit.py +0 -0
  39. {prospector-1.13.3 → prospector-1.14.1}/prospector/formatters/yaml.py +0 -0
  40. {prospector-1.13.3 → prospector-1.14.1}/prospector/pathutils.py +0 -0
  41. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/__init__.py +0 -0
  42. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/exceptions.py +0 -0
  43. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profile.py +0 -0
  44. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/default.yaml +0 -0
  45. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/doc_warnings.yaml +0 -0
  46. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/flake8.yaml +0 -0
  47. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/full_pep8.yaml +0 -0
  48. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/member_warnings.yaml +0 -0
  49. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/no_doc_warnings.yaml +0 -0
  50. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/no_member_warnings.yaml +0 -0
  51. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/no_pep8.yaml +0 -0
  52. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/no_test_warnings.yaml +0 -0
  53. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/strictness_low.yaml +0 -0
  54. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/strictness_none.yaml +0 -0
  55. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/strictness_verylow.yaml +0 -0
  56. {prospector-1.13.3 → prospector-1.14.1}/prospector/profiles/profiles/test_warnings.yaml +0 -0
  57. {prospector-1.13.3 → prospector-1.14.1}/prospector/run.py +0 -0
  58. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/__init__.py +0 -0
  59. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/base.py +0 -0
  60. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/dodgy/__init__.py +0 -0
  61. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/exceptions.py +0 -0
  62. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/mccabe/__init__.py +0 -0
  63. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/profile_validator/__init__.py +0 -0
  64. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/pycodestyle/__init__.py +0 -0
  65. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/pydocstyle/__init__.py +0 -0
  66. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/pyflakes/__init__.py +0 -0
  67. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/pylint/__init__.py +0 -0
  68. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/pylint/linter.py +0 -0
  69. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/pyright/__init__.py +0 -0
  70. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/pyroma/__init__.py +0 -0
  71. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/utils.py +0 -0
  72. {prospector-1.13.3 → prospector-1.14.1}/prospector/tools/vulture/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prospector
3
- Version: 1.13.3
3
+ Version: 1.14.1
4
4
  Summary: Prospector is a tool to analyse Python code by aggregating the result of other tools.
5
5
  Home-page: http://prospector.readthedocs.io
6
6
  License: GPLv2+
@@ -277,6 +277,11 @@ combinations:
277
277
  - - pep257: D103
278
278
  - pydocstyle: D103
279
279
  - pylint: missing-docstring
280
+
280
281
  - - pylint: singleton-comparison
281
282
  - pep8: E711
282
283
  - pycodestyle: E711
284
+
285
+ - - pylint: subprocess-run-check
286
+ - bandit: B603
287
+ - ruff: S603
@@ -56,8 +56,8 @@ class FileFinder:
56
56
  def is_excluded(self, path: Path) -> bool:
57
57
  return any(filt(path) for filt in self._exclusion_filters)
58
58
 
59
- def _filter(self, paths: Iterable[Path]) -> list[Path]:
60
- return [path for path in paths if not self.is_excluded(path)]
59
+ def _filter(self, paths: Iterable[Path]) -> set[Path]:
60
+ return {path for path in paths if not self.is_excluded(path)}
61
61
 
62
62
  def _walk(self, directory: Path) -> Iterator[Path]:
63
63
  if not self.is_excluded(directory):
@@ -72,22 +72,25 @@ class FileFinder:
72
72
  yield path
73
73
 
74
74
  @property
75
- def files(self) -> list[Path]:
75
+ def files(self) -> set[Path]:
76
76
  """
77
77
  List every individual file found from the given configuration.
78
78
 
79
79
  This method is useful for tools which require an explicit list of files to check.
80
80
  """
81
81
  files = set()
82
- for path in self._provided_files:
83
- files.add(path)
84
82
 
85
83
  for directory in self.directories:
86
84
  for path in self._walk(directory):
87
85
  if path.is_file():
88
86
  files.add(path)
89
87
 
90
- return self._filter(files)
88
+ files = self._filter(files)
89
+
90
+ for path in self._provided_files:
91
+ files.add(path)
92
+
93
+ return files
91
94
 
92
95
  @property
93
96
  def python_packages(self) -> list[Path]:
@@ -97,7 +100,7 @@ class FileFinder:
97
100
 
98
101
  This method is useful for passing to tools which will do their own discovery of python files.
99
102
  """
100
- return self._filter(d for d in self.directories if is_python_package(d))
103
+ return [d for d in self.directories if is_python_package(d)]
101
104
 
102
105
  @property
103
106
  def python_modules(self) -> list[Path]:
@@ -107,10 +110,10 @@ class FileFinder:
107
110
 
108
111
  This method is useful for passing to tools which will do their own discovery of python files.
109
112
  """
110
- return self._filter(f for f in self.files if is_python_module(f))
113
+ return [f for f in self.files if is_python_module(f)]
111
114
 
112
115
  @property
113
- def directories(self) -> list[Path]:
116
+ def directories(self) -> set[Path]:
114
117
  """
115
118
  Lists every directory found from the given configuration, regardless of its contents.
116
119
 
@@ -1,4 +1,4 @@
1
- from . import emacs, grouped, json, pylint, text, vscode, xunit, yaml
1
+ from . import emacs, grouped, json, pylint, pylint_parseable, text, vscode, xunit, yaml
2
2
  from .base import Formatter
3
3
 
4
4
  __all__ = ("FORMATTERS", "Formatter")
@@ -11,6 +11,7 @@ FORMATTERS: dict[str, type[Formatter]] = {
11
11
  "emacs": emacs.EmacsFormatter,
12
12
  "yaml": yaml.YamlFormatter,
13
13
  "pylint": pylint.PylintFormatter,
14
+ "pylint_parseable": pylint_parseable.PylintParseableFormatter,
14
15
  "xunit": xunit.XunitFormatter,
15
16
  "vscode": vscode.VSCodeFormatter,
16
17
  }
@@ -39,9 +39,18 @@ class Formatter(ABC):
39
39
  "line": message.location.line,
40
40
  "character": message.location.character,
41
41
  }
42
- return {
42
+ if message.location.line_end is not None and message.location.line_end != -1:
43
+ loc["lineEnd"] = message.location.line_end
44
+ if message.location.character_end is not None and message.location.character_end != -1:
45
+ loc["characterEnd"] = message.location.character_end
46
+ result = {
43
47
  "source": message.source,
44
48
  "code": message.code,
45
49
  "location": loc,
46
50
  "message": message.message,
51
+ "isFixable": message.is_fixable,
47
52
  }
53
+ if message.doc_url:
54
+ result["docUrl"] = message.doc_url
55
+
56
+ return result
@@ -7,12 +7,7 @@ __all__ = ("EmacsFormatter",)
7
7
  class EmacsFormatter(TextFormatter):
8
8
  def render_message(self, message: Message) -> str:
9
9
  output = [
10
- "%s:%s:%d:"
11
- % (
12
- self._make_path(message.location),
13
- message.location.line,
14
- (message.location.character or 0) + 1,
15
- ),
10
+ f"{self._make_path(message.location)}:{message.location.line}:{(message.location.character or 0) + 1}:",
16
11
  " L{}:{} {}: {} - {}".format(
17
12
  message.location.line or "-",
18
13
  message.location.character if message.location.line else "-",
@@ -0,0 +1,80 @@
1
+ import os
2
+ import re
3
+
4
+ from prospector.formatters.base_summary import SummaryFormatter
5
+
6
+
7
+ class PylintFormatter(SummaryFormatter):
8
+ """
9
+ This formatter outputs messages in the same way as pylint -f parseable , which is used by several
10
+ tools to parse pylint output. This formatter is therefore a compatibility shim between tools built
11
+ on top of pylint and prospector itself.
12
+ """
13
+
14
+ def render_messages(self) -> list[str]:
15
+ cur_loc = None
16
+ output = []
17
+ for message in sorted(self.messages):
18
+ if cur_loc != message.location.path:
19
+ cur_loc = message.location.path
20
+ module_name = str(self._make_path(message.location)).replace(os.path.sep, ".")
21
+ module_name = re.sub(r"(\.__init__)?\.py$", "", module_name)
22
+
23
+ header = f"************* Module {module_name}"
24
+ output.append(header)
25
+
26
+ # ={path}:{line}:{character}: [{msg_id}({symbol}), {obj}] {msg}
27
+ # prospector/configuration.py:65:1: [missing-docstring(missing-docstring), build_default_sources] \
28
+ # Missing function docstring
29
+
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
+
52
+ message_str = message.message.strip()
53
+ if message.doc_url:
54
+ message_str += f" (See: {message.doc_url})"
55
+ output.append(
56
+ template
57
+ % {
58
+ "path": self._make_path(message.location),
59
+ "line": message.location.line,
60
+ "character": message.location.character,
61
+ "source": message.source,
62
+ "code": message.code,
63
+ "function": message.location.function,
64
+ "message": message.message.strip(),
65
+ }
66
+ )
67
+ return output
68
+
69
+ def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str:
70
+ output: list[str] = []
71
+ if messages:
72
+ output.extend(self.render_messages())
73
+ if profile:
74
+ output.append("")
75
+ output.append(self.render_profile())
76
+ if summary:
77
+ output.append("")
78
+ output.append(self.render_summary())
79
+
80
+ return "\n".join(output)
@@ -4,9 +4,9 @@ import re
4
4
  from prospector.formatters.base_summary import SummaryFormatter
5
5
 
6
6
 
7
- class PylintFormatter(SummaryFormatter):
7
+ class PylintParseableFormatter(SummaryFormatter):
8
8
  """
9
- This formatter outputs messages in the same way as pylint -f parseable , which is used by several
9
+ This formatter outputs messages in the same way as `pylint --output-format=parseable`, which is used by several
10
10
  tools to parse pylint output. This formatter is therefore a compatibility shim between tools built
11
11
  on top of pylint and prospector itself.
12
12
  """
@@ -33,8 +33,6 @@ class PylintFormatter(SummaryFormatter):
33
33
  else "%(path)s"
34
34
  if message.location.line is None
35
35
  else "%(path)s:%(line)s"
36
- if message.location.character is None
37
- else "%(path)s:%(line)s:%(character)s"
38
36
  )
39
37
  template_code = (
40
38
  "(%(source)s)"
@@ -54,7 +52,6 @@ class PylintFormatter(SummaryFormatter):
54
52
  % {
55
53
  "path": self._make_path(message.location),
56
54
  "line": message.location.line,
57
- "character": message.location.character,
58
55
  "source": message.source,
59
56
  "code": message.code,
60
57
  "function": message.location.function,
@@ -12,6 +12,8 @@ class Location:
12
12
  function: Optional[str],
13
13
  line: Optional[int],
14
14
  character: Optional[int],
15
+ line_end: Optional[int] = None,
16
+ character_end: Optional[int] = None,
15
17
  ):
16
18
  if isinstance(path, Path):
17
19
  self._path = path.absolute()
@@ -25,6 +27,8 @@ class Location:
25
27
  self.function = function or None
26
28
  self.line = None if line == -1 else line
27
29
  self.character = None if character == -1 else character
30
+ self.line_end = line_end
31
+ self.character_end = character_end
28
32
 
29
33
  @property
30
34
  def path(self) -> Optional[Path]:
@@ -36,7 +40,9 @@ class Location:
36
40
  def relative_path(self, root: Optional[Path]) -> Optional[Path]:
37
41
  if self._path is None:
38
42
  return None
39
- return self._path.relative_to(root) if root else self._path
43
+ if root is None:
44
+ return self._path
45
+ return self._path.relative_to(root) if self._path.is_relative_to(root) else self._path
40
46
 
41
47
  def __repr__(self) -> str:
42
48
  return f"{self._path}:L{self.line}:{self.character}"
@@ -67,11 +73,21 @@ class Location:
67
73
 
68
74
 
69
75
  class Message:
70
- def __init__(self, source: str, code: str, location: Location, message: str):
76
+ def __init__(
77
+ self,
78
+ source: str,
79
+ code: str,
80
+ location: Location,
81
+ message: str,
82
+ doc_url: Optional[str] = None,
83
+ is_fixable: bool = False,
84
+ ):
71
85
  self.source = source
72
86
  self.code = code
73
87
  self.location = location
74
88
  self.message = message
89
+ self.doc_url = doc_url
90
+ self.is_fixable = is_fixable
75
91
 
76
92
  def __repr__(self) -> str:
77
93
  return f"{self.source}-{self.code}"
@@ -48,9 +48,14 @@ def filter_messages(filepaths: list[Path], messages: list[Message]) -> list[Mess
48
48
  if (
49
49
  relative_message_path in messages_to_ignore
50
50
  and message.location.line in messages_to_ignore[relative_message_path]
51
- and message.code in messages_to_ignore[relative_message_path][message.location.line]
52
51
  ):
53
- continue
52
+ matched = False
53
+ for ignore in messages_to_ignore[relative_message_path][message.location.line]:
54
+ if (ignore.source is None or message.source == ignore.source) and message.code in ignore.code:
55
+ matched = True
56
+ continue
57
+ if matched:
58
+ continue
54
59
 
55
60
  # otherwise this message was not filtered
56
61
  filtered.append(message)
@@ -54,3 +54,10 @@ pydocstyle:
54
54
  disable:
55
55
  - D400
56
56
  - D401
57
+
58
+ mypy:
59
+ options:
60
+ ignore-missing-imports: true
61
+ follow-imports: skip
62
+ disallow-untyped-defs: true
63
+ strict: false
@@ -125,3 +125,7 @@ pydocstyle:
125
125
  - D101
126
126
  - D102
127
127
  - D103
128
+
129
+ mypy:
130
+ options:
131
+ disallow-untyped-defs: false
@@ -38,3 +38,7 @@ pyroma:
38
38
  pydocstyle:
39
39
  disable:
40
40
  - D000
41
+
42
+ mypy:
43
+ options:
44
+ strict: true
@@ -31,11 +31,28 @@ from prospector.exceptions import FatalProspectorException
31
31
  from prospector.message import Message
32
32
 
33
33
  _FLAKE8_IGNORE_FILE = re.compile(r"flake8[:=]\s*noqa", re.IGNORECASE)
34
- _PEP8_IGNORE_LINE = re.compile(r"#\s+noqa", re.IGNORECASE)
34
+ _PEP8_IGNORE_LINE = re.compile(r"#\s*noqa(\s*#.*)?$", re.IGNORECASE)
35
+ _PEP8_IGNORE_LINE_CODE = re.compile(r"#\s*noqa:([^#]*[^# ])(\s*#.*)?$", re.IGNORECASE)
35
36
  _PYLINT_SUPPRESSED_MESSAGE = re.compile(r"^Suppressed \'([a-z0-9-]+)\' \(from line \d+\)$")
36
37
 
37
38
 
38
- def get_noqa_suppressions(file_contents: list[str]) -> tuple[bool, set[int]]:
39
+ class Ignore:
40
+ source: Optional[str]
41
+ code: str
42
+
43
+ def __init__(
44
+ self,
45
+ source: Optional[str],
46
+ code: str,
47
+ ) -> None:
48
+ self.source = source
49
+ self.code = code
50
+
51
+ def __str__(self) -> str:
52
+ return self.code if self.source is None else f"{self.source}.{self.code}"
53
+
54
+
55
+ def get_noqa_suppressions(file_contents: list[str]) -> tuple[bool, set[int], dict[int, set[Ignore]]]:
39
56
  """
40
57
  Finds all pep8/flake8 suppression messages
41
58
 
@@ -47,12 +64,21 @@ def get_noqa_suppressions(file_contents: list[str]) -> tuple[bool, set[int]]:
47
64
  """
48
65
  ignore_whole_file = False
49
66
  ignore_lines = set()
67
+ messages_to_ignore: dict[int, set[Ignore]] = defaultdict(set)
50
68
  for line_number, line in enumerate(file_contents):
51
69
  if _FLAKE8_IGNORE_FILE.search(line):
52
70
  ignore_whole_file = True
53
71
  if _PEP8_IGNORE_LINE.search(line):
54
72
  ignore_lines.add(line_number + 1)
55
- return ignore_whole_file, ignore_lines
73
+ else:
74
+ noqa_match = _PEP8_IGNORE_LINE_CODE.search(line)
75
+ if noqa_match:
76
+ prospector_ignore = noqa_match.group(1).strip().split(",")
77
+ prospector_ignore = [elem.strip() for elem in prospector_ignore]
78
+ for code in prospector_ignore:
79
+ messages_to_ignore[line_number + 1].add(Ignore(None, code))
80
+
81
+ return ignore_whole_file, ignore_lines, messages_to_ignore
56
82
 
57
83
 
58
84
  _PYLINT_EQUIVALENTS = {
@@ -89,7 +115,7 @@ def _parse_pylint_informational(
89
115
 
90
116
  def get_suppressions(
91
117
  filepaths: list[Path], messages: list[Message]
92
- ) -> tuple[set[Optional[Path]], dict[Path, set[int]], dict[Optional[Path], dict[int, set[tuple[str, str]]]]]:
118
+ ) -> tuple[set[Optional[Path]], dict[Path, set[int]], dict[Optional[Path], dict[int, set[Ignore]]]]:
93
119
  """
94
120
  Given every message which was emitted by the tools, and the
95
121
  list of files to inspect, create a list of files to ignore,
@@ -97,9 +123,9 @@ def get_suppressions(
97
123
  """
98
124
  paths_to_ignore: set[Optional[Path]] = set()
99
125
  lines_to_ignore: dict[Path, set[int]] = defaultdict(set)
100
- messages_to_ignore: dict[Optional[Path], dict[int, set[tuple[str, str]]]] = defaultdict(lambda: defaultdict(set))
126
+ messages_to_ignore: dict[Optional[Path], dict[int, set[Ignore]]] = defaultdict(lambda: defaultdict(set))
101
127
 
102
- # first deal with 'noqa' style messages
128
+ # First deal with 'noqa' style messages
103
129
  for filepath in filepaths:
104
130
  try:
105
131
  file_contents = encoding.read_py_file(filepath).split("\n")
@@ -108,20 +134,24 @@ def get_suppressions(
108
134
  warnings.warn(f"{err.path}: {err.__cause__}", ImportWarning, stacklevel=2)
109
135
  continue
110
136
 
111
- ignore_file, ignore_lines = get_noqa_suppressions(file_contents)
137
+ ignore_file, ignore_lines, file_messages_to_ignore = get_noqa_suppressions(file_contents)
112
138
  if ignore_file:
113
139
  paths_to_ignore.add(filepath)
114
140
  lines_to_ignore[filepath] |= ignore_lines
141
+ for line, codes_ignore in file_messages_to_ignore.items():
142
+ messages_to_ignore[filepath][line] |= codes_ignore
115
143
 
116
- # now figure out which messages were suppressed by pylint
144
+ # Now figure out which messages were suppressed by pylint
117
145
  pylint_ignore_files, pylint_ignore_messages = _parse_pylint_informational(messages)
118
146
  paths_to_ignore |= pylint_ignore_files
119
- for pylint_filepath, line in pylint_ignore_messages.items():
120
- for line_number, codes in line.items():
147
+ for pylint_filepath, line_codes in pylint_ignore_messages.items():
148
+ for line_number, codes in line_codes.items():
121
149
  for code in codes:
122
- messages_to_ignore[pylint_filepath][line_number].add(("pylint", code))
150
+ ignore = Ignore("pylint", code)
151
+ messages_to_ignore[pylint_filepath][line_number].add(ignore)
123
152
  if code in _PYLINT_EQUIVALENTS:
124
- for equivalent in _PYLINT_EQUIVALENTS[code]:
125
- messages_to_ignore[pylint_filepath][line_number].add(equivalent)
153
+ for ignore_source, ignore_code in _PYLINT_EQUIVALENTS[code]:
154
+ ignore = Ignore(ignore_source, ignore_code)
155
+ messages_to_ignore[pylint_filepath][line_number].add(ignore)
126
156
 
127
157
  return paths_to_ignore, lines_to_ignore, messages_to_ignore
@@ -1,6 +1,7 @@
1
1
  from typing import TYPE_CHECKING, Any, Optional
2
2
 
3
3
  from bandit.cli.main import _get_profile, _init_extensions
4
+ from bandit.core import docs_utils
4
5
  from bandit.core.config import BanditConfig
5
6
  from bandit.core.constants import RANKING
6
7
  from bandit.core.manager import BanditManager
@@ -66,7 +67,15 @@ class BanditTool(ToolBase):
66
67
  results = self.manager.get_issue_list(sev_level=RANKING[self.severity], conf_level=RANKING[self.confidence])
67
68
  messages = []
68
69
  for result in results:
69
- loc = Location(result.fname, None, "", int(result.lineno), 0)
70
- msg = Message("bandit", result.test_id, loc, result.text)
70
+ loc = Location(
71
+ result.fname,
72
+ None,
73
+ "",
74
+ result.lineno,
75
+ result.col_offset,
76
+ line_end=result.linerange[-1] if result.linerange else result.lineno,
77
+ character_end=result.end_col_offset,
78
+ )
79
+ msg = Message("bandit", result.test_id, loc, result.text, doc_url=docs_utils.get_url(result.test_id))
71
80
  messages.append(msg)
72
81
  return messages
@@ -87,7 +87,7 @@ class MypyTool(ToolBase):
87
87
  self.options.append(f"--{name}-{v}")
88
88
  continue
89
89
 
90
- raise BadToolConfig("mypy", f"The option {name} has an unsupported balue type: {type(value)}")
90
+ raise BadToolConfig("mypy", f"The option {name} has an unsupported value type: {type(value)}")
91
91
 
92
92
  def run(self, found_files: FileFinder) -> list[Message]:
93
93
  paths = [str(path) for path in found_files.python_modules]
@@ -17,7 +17,7 @@ class Collector(BaseReporter):
17
17
  self._messages: list[Message] = []
18
18
 
19
19
  def handle_message(self, msg: PylintMessage) -> None:
20
- loc = Location(msg.abspath, msg.module, msg.obj, msg.line, msg.column)
20
+ loc = Location(msg.abspath, msg.module, msg.obj, msg.line, msg.column, msg.end_line, msg.end_column)
21
21
 
22
22
  # At this point pylint will give us the code but we want the
23
23
  # more user-friendly symbol
@@ -31,7 +31,13 @@ class Collector(BaseReporter):
31
31
  else:
32
32
  msg_symbol = msg_data[0].symbol
33
33
 
34
- message = Message("pylint", msg_symbol, loc, msg.msg)
34
+ message = Message(
35
+ "pylint",
36
+ msg_symbol,
37
+ loc,
38
+ msg.msg,
39
+ doc_url=f"https://pylint.readthedocs.io/en/latest/user_guide/messages/{msg.category}/{msg.symbol}.html",
40
+ )
35
41
  self._messages.append(message)
36
42
 
37
43
  def get_messages(self) -> list[Message]:
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import subprocess # nosec
3
+ from pathlib import Path
3
4
  from typing import TYPE_CHECKING, Any
4
5
 
5
6
  from ruff.__main__ import find_ruff_bin
@@ -44,7 +45,7 @@ class RuffTool(ToolBase):
44
45
 
45
46
  def run(self, found_files: FileFinder) -> list[Message]:
46
47
  messages = []
47
- completed_process = subprocess.run( # noqa: S603
48
+ completed_process = subprocess.run( # noqa
48
49
  [self.ruff_bin, *self.ruff_args, *found_files.python_modules], capture_output=True
49
50
  )
50
51
  if not completed_process.stdout:
@@ -59,14 +60,14 @@ class RuffTool(ToolBase):
59
60
  return messages
60
61
  for message in json.loads(completed_process.stdout):
61
62
  sub_message = {}
62
- if message.get("url"):
63
- sub_message["See"] = message["url"]
64
63
  if message.get("fix") and message["fix"].get("applicability"):
65
64
  sub_message["Fix applicability"] = message["fix"]["applicability"]
66
65
  message_str = message.get("message", "")
67
66
  if sub_message:
68
67
  message_str += f" [{', '.join(f'{k}: {v}' for k, v in sub_message.items())}]"
69
68
 
69
+ if message.get("filename") is None or found_files.is_excluded(Path(message.get("filename"))):
70
+ continue
70
71
  messages.append(
71
72
  Message(
72
73
  "ruff",
@@ -77,8 +78,12 @@ class RuffTool(ToolBase):
77
78
  None,
78
79
  line=message.get("location", {}).get("row"),
79
80
  character=message.get("location", {}).get("column"),
81
+ line_end=message.get("end_location", {}).get("row"),
82
+ character_end=message.get("end_location", {}).get("column"),
80
83
  ),
81
84
  message_str,
85
+ doc_url=message.get("url"),
86
+ is_fixable=bool((message.get("fix") or {}).get("applicability") in ("safe", "unsafe")),
82
87
  )
83
88
  )
84
89
  return messages
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "prospector"
3
- version = "1.13.3"
3
+ version = "1.14.1"
4
4
  description = "Prospector is a tool to analyse Python code by aggregating the result of other tools."
5
5
  authors = ["Carl Crowder <git@carlcrowder.com>"]
6
6
  maintainers = ["Carl Crowder <git@carlcrowder.com>",
@@ -25,8 +25,9 @@ classifiers = [
25
25
  "Programming Language :: Python :: 3.13",
26
26
  "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)",
27
27
  ]
28
+ # The format is a workaround aganst https://github.com/python-poetry/poetry/issues/9961
28
29
  packages = [
29
- { include = "prospector/"}
30
+ { include = "prospector/", format = ["sdist"] }
30
31
  ]
31
32
  include = [
32
33
  "prospector/blender_combinations.yaml",
@@ -70,7 +71,7 @@ with_vulture = ["vulture"]
70
71
  with_ruff = ["ruff"]
71
72
  with_everything = ["bandit", "mypy", "pyright", "pyroma", "vulture", "ruff"]
72
73
 
73
- [tool.poetry.dev-dependencies]
74
+ [tool.poetry.group.dev.dependencies]
74
75
  coveralls = "^3.3.1"
75
76
  pytest = "^7.2.0"
76
77
  pytest-benchmark = "^4.0.0"
@@ -61,7 +61,7 @@ entry_points = \
61
61
 
62
62
  setup_kwargs = {
63
63
  'name': 'prospector',
64
- 'version': '1.13.3',
64
+ 'version': '1.14.1',
65
65
  'description': 'Prospector is a tool to analyse Python code by aggregating the result of other tools.',
66
66
  'long_description': 'prospector\n==========\n\n.. image:: https://img.shields.io/pypi/v/prospector.svg\n :target: https://pypi.python.org/pypi/prospector\n :alt: Latest Version of Prospector\n.. image:: https://github.com/PyCQA/prospector/actions/workflows/tests.yml/badge.svg\n :target: https://github.com/PyCQA/prospector/actions/workflows/tests.yml\n :alt: Build Status\n.. image:: https://img.shields.io/coveralls/PyCQA/prospector.svg?style=flat\n :target: https://coveralls.io/r/PyCQA/prospector\n :alt: Test Coverage\n.. image:: https://readthedocs.org/projects/prospector/badge/?version=latest\n :target: https://prospector.readthedocs.io/\n :alt: Documentation\n\n\nAbout\n-----\n\nProspector is a tool to analyse Python code and output information about\nerrors, potential problems, convention violations and complexity.\n\nIt brings together the functionality of other Python analysis tools such as\n`Pylint <https://docs.pylint.org/>`_,\n`pycodestyle <https://pycodestyle.pycqa.org/>`_,\nand `McCabe complexity <https://pypi.python.org/pypi/mccabe>`_.\nSee the `Supported Tools <https://prospector.readthedocs.io/en/latest/supported_tools.html>`_\ndocumentation section for a complete list.\n\nThe primary aim of Prospector is to be useful \'out of the box\'. A common complaint of other\nPython analysis tools is that it takes a long time to filter through which errors are relevant\nor interesting to your own coding style. Prospector provides some default profiles, which\nhopefully will provide a good starting point and will be useful straight away, and adapts\nthe output depending on the libraries your project uses.\n\nInstallation\n------------\n\nProspector can be installed from PyPI using ``pip`` by running the following command::\n\n pip install prospector\n\nOptional dependencies for Prospector, such as ``pyroma`` can also be installed by running::\n\n pip install prospector[with_pyroma]\n\nSome shells (such as ``Zsh``, the default shell of macOS Catalina) require brackets to be escaped::\n\n pip install prospector\\[with_pyroma\\]\n\nFor a list of all of the optional dependencies, see the optional extras section on the ReadTheDocs\npage on `Supported Tools Extras <https://prospector.readthedocs.io/en/latest/supported_tools.html#optional-extras>`_.\n\nFor local development, `poetry <https://python-poetry.org/>`_ is used. Check out the code, then run::\n\n poetry install\n\nAnd for extras::\n\n poetry install -E with_everything\n\nFor more detailed information on installing the tool, see the\n`installation section <https://prospector.readthedocs.io/en/latest/#installation>`_ of the tool\'s main page\non ReadTheDocs.\n\nDocumentation\n-------------\n\nFull `documentation is available at ReadTheDocs <https://prospector.readthedocs.io>`_.\n\nUsage\n-----\n\nSimply run prospector from the root of your project::\n\n prospector\n\nThis will output a list of messages pointing out potential problems or errors, for example::\n\n prospector.tools.base (prospector/tools/base.py):\n L5:0 ToolBase: pylint - R0922\n Abstract class is only referenced 1 times\n\nOptions\n```````\n\nRun ``prospector --help`` for a full list of options and their effects.\n\nOutput Format\n~~~~~~~~~~~~~\n\nThe default output format of ``prospector`` is designed to be human readable. For parsing\n(for example, for reporting), you can use the ``--output-format json`` flag to get JSON-formatted\noutput.\n\nProfiles\n~~~~~~~~\n\nProspector is configurable using "profiles". These are composable YAML files with directives to\ndisable or enable tools or messages. For more information, read\n`the documentation about profiles <https://prospector.readthedocs.io/en/latest/profiles.html>`_.\n\nIf your code uses frameworks and libraries\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nOften tools such as pylint find errors in code which is not an error, for example due to attributes of classes being\ncreated at run time by a library or framework used by your project.\nFor example, by default, pylint will generate an error for Django models when accessing ``objects``, as the\n``objects`` attribute is not part of the ``Model`` class definition.\n\nProspector mitigates this by providing an understanding of these frameworks to the underlying tools.\n\nProspector will try to intuit which libraries your project uses by\n`detecting dependencies <https://github.com/landscapeio/requirements-detector>`_ and automatically turning on\nsupport for the requisite libraries. You can see which adaptors were run in the metadata section of the report.\n\nIf Prospector does not correctly detect your project\'s dependencies, you can specify them manually from the commandline::\n\n prospector --uses django celery\n\nAdditionally, if Prospector is automatically detecting a library that you do not in fact use, you can turn\noff autodetection completely::\n\n prospector --no-autodetect\n\nNote that as far as possible, these adaptors have been written as plugins or augmentations for the underlying\ntools so that they can be used without requiring Prospector. For example, the Django support is available as a pylint plugin.\n\nStrictness\n~~~~~~~~~~\n\nProspector has a configurable \'strictness\' level which will determine how harshly it searches for errors::\n\n prospector --strictness high\n\nPossible values are ``verylow``, ``low``, ``medium``, ``high``, ``veryhigh``.\n\nProspector does not include documentation warnings by default, but you can turn\nthis on using the ``--doc-warnings`` flag.\n\npre-commit\n----------\n\nIf you\'d like Prospector to be run automatically when making changes to files in your Git\nrepository, you can install `pre-commit <https://pre-commit.com/>`_ and add the following\ntext to your repositories\' ``.pre-commit-config.yaml``::\n\n repos:\n - repo: https://github.com/PyCQA/prospector\n rev: 1.10.0 # The version of Prospector to use, if not \'master\' for latest\n hooks:\n - id: prospector\n\nThis only installs base prospector - if you also use optional tools, for example bandit and/or mypy, then you can add\nthem to the hook configuration like so::\n\n repos:\n - repo: https://github.com/PyCQA/prospector\n rev: 1.10.0\n hooks:\n - id: prospector\n additional_dependencies:\n - ".[with_mypy,with_bandit]"\n - args: [\n \'--with-tool=mypy\',\n \'--with-tool=bandit\',\n ]\n\nAdditional dependencies can be `individually configured <https://prospector.landscape.io/en/master/profiles.html#individual-configuration-options>`_ in your `prospector.yml` file ::\n\n # https://bandit.readthedocs.io/en/latest/config.html\n bandit:\n options:\n skips:\n - B201\n - B601\n - B610\n - B611\n - B703\n\n # https://mypy.readthedocs.io/en/stable/command_line.html\n mypy:\n options:\n ignore-missing-imports: true\n\nFor prospector options which affect display only - those which are not configurable using a profile - these can be\nadded as command line arguments to the hook. For example::\n\n repos:\n - repo: https://github.com/PyCQA/prospector\n rev: 1.10.0\n hooks:\n - id: prospector\n additional_dependencies:\n - ".[with_mypy,with_bandit]"\n args:\n - --with-tool=mypy\n - --with-tool=bandit\n - --summary-only\n - --zero-exit\n\n\n\nLicense\n-------\n\nProspector is available under the GPLv2 License.\n',
67
67
  'author': 'Carl Crowder',
File without changes
File without changes