0
# Tools
1
2
Plugin system for integrating static analysis tools with base classes and tool registry. The tools framework allows Prospector to run multiple analysis tools with a unified interface.
3
4
## Capabilities
5
6
### ToolBase Abstract Class
7
8
Base class that all analysis tools must implement.
9
10
```python { .api }
11
class ToolBase(ABC):
12
pass
13
```
14
15
Abstract base class for all Prospector tools. Tools must implement the configure and run methods.
16
17
```python { .api }
18
@abstractmethod
19
def configure(self, prospector_config: "ProspectorConfig", found_files: FileFinder) -> Optional[tuple[Optional[Union[str, Path]], Optional[Iterable[Message]]]]
20
```
21
22
Configures the tool based on the provided configuration and discovered files.
23
24
**Parameters:**
25
- `prospector_config`: ProspectorConfig - Prospector configuration object
26
- `found_files`: FileFinder - File discovery object
27
28
**Returns:**
29
- `Optional[tuple]` - Two-element tuple or None:
30
- First element: Optional[Union[str, Path]] - Configuration source (file path or description), None for defaults
31
- Second element: Optional[Iterable[Message]] - Configuration messages/warnings, None if no issues
32
33
This method allows tools to:
34
- Discover tool-specific configuration files (e.g., `.pylintrc`, `pyproject.toml`)
35
- Validate configuration settings
36
- Report configuration issues as messages
37
- Use Prospector defaults if no tool-specific config is found
38
39
```python { .api }
40
@abstractmethod
41
def run(self, found_files: FileFinder) -> list[Message]
42
```
43
44
Executes the analysis tool and returns found issues.
45
46
**Parameters:**
47
- `found_files`: FileFinder - File discovery object containing files to analyze
48
49
**Returns:**
50
- `list[Message]` - List of analysis messages found by the tool
51
52
```python { .api }
53
def get_ignored_codes(self, line: str) -> list[tuple[str, int]]
54
```
55
56
Parses a line of code for inline ignore directives (e.g., `# noqa`).
57
58
**Parameters:**
59
- `line`: str - Line of source code to parse
60
61
**Returns:**
62
- `list[tuple[str, int]]` - List of (error_code, line_offset) tuples for codes to ignore
63
64
### Tool Registry
65
66
```python { .api }
67
TOOLS: dict[str, type[ToolBase]]
68
```
69
70
Registry of all available analysis tools.
71
72
**Available Tools:**
73
- `"dodgy"`: DodgyTool - Detects potentially dodgy code (hardcoded passwords, etc.)
74
- `"mccabe"`: McCabeTool - Cyclomatic complexity analysis
75
- `"pyflakes"`: PyFlakesTool - Fast static analysis for Python
76
- `"pycodestyle"`: PycodestyleTool - PEP 8 style checker
77
- `"pylint"`: PylintTool - Comprehensive Python linter
78
- `"pydocstyle"`: PydocstyleTool - Docstring style checker
79
- `"profile-validator"`: ProfileValidationTool - Validates Prospector profiles
80
- `"vulture"`: VultureTool - Dead code finder (optional dependency)
81
- `"pyroma"`: PyromaTool - Python package quality checker (optional dependency)
82
- `"pyright"`: PyrightTool - Microsoft's Python type checker (optional dependency)
83
- `"mypy"`: MypyTool - Python type checker (optional dependency)
84
- `"bandit"`: BanditTool - Security linter (optional dependency)
85
- `"ruff"`: RuffTool - Fast Python linter and formatter (optional dependency)
86
87
```python { .api }
88
DEFAULT_TOOLS: tuple[str, ...]
89
```
90
91
Tuple of tool names that run by default.
92
93
**Default Tools:**
94
- `"dodgy"` - Always runs
95
- `"mccabe"` - Always runs
96
- `"pyflakes"` - Always runs
97
- `"pycodestyle"` - Always runs
98
- `"pylint"` - Always runs
99
- `"pydocstyle"` - Always runs
100
- `"profile-validator"` - Always runs
101
102
```python { .api }
103
DEPRECATED_TOOL_NAMES: dict[str, str]
104
```
105
106
Mapping of deprecated tool names to their current names.
107
108
**Deprecated Names:**
109
- `"pep8"` → `"pycodestyle"`
110
- `"pep257"` → `"pydocstyle"`
111
112
### Individual Tool Classes
113
114
#### Built-in Tools
115
116
```python { .api }
117
class DodgyTool(ToolBase):
118
pass
119
```
120
121
Detects potentially dodgy code patterns like hardcoded passwords, TODO comments, and other code smells.
122
123
```python { .api }
124
class McCabeTool(ToolBase):
125
pass
126
```
127
128
Measures cyclomatic complexity of functions and methods. Identifies overly complex code that may be hard to maintain.
129
130
```python { .api }
131
class PyFlakesTool(ToolBase):
132
pass
133
```
134
135
Fast static analysis tool that catches common Python errors like undefined variables, unused imports, and syntax errors.
136
137
```python { .api }
138
class PycodestyleTool(ToolBase):
139
pass
140
```
141
142
Checks Python code against PEP 8 style guidelines, including line length, indentation, whitespace, and naming conventions.
143
144
```python { .api }
145
class PylintTool(ToolBase):
146
pass
147
```
148
149
Comprehensive Python linter that checks for errors, enforces coding standards, looks for code smells, and suggests refactoring.
150
151
```python { .api }
152
class PydocstyleTool(ToolBase):
153
pass
154
```
155
156
Checks docstring style against PEP 257 conventions and other docstring standards.
157
158
```python { .api }
159
class ProfileValidationTool(ToolBase):
160
pass
161
```
162
163
Validates Prospector profile configuration files for syntax and semantic correctness.
164
165
#### Optional Tools
166
167
These tools require additional dependencies and are only available when installed:
168
169
```python { .api }
170
class VultureTool(ToolBase):
171
pass
172
```
173
174
Finds unused code (dead code) in Python programs. Install with `pip install prospector[with_vulture]`.
175
176
```python { .api }
177
class PyromaTool(ToolBase):
178
pass
179
```
180
181
Rates the quality of Python packages based on packaging best practices. Install with `pip install prospector[with_pyroma]`.
182
183
```python { .api }
184
class PyrightTool(ToolBase):
185
pass
186
```
187
188
Microsoft's fast static type checker for Python. Install with `pip install prospector[with_pyright]`.
189
190
```python { .api }
191
class MypyTool(ToolBase):
192
pass
193
```
194
195
Static type checker for Python that uses type hints. Install with `pip install prospector[with_mypy]`.
196
197
```python { .api }
198
class BanditTool(ToolBase):
199
pass
200
```
201
202
Security-focused linter that identifies common security issues in Python code. Install with `pip install prospector[with_bandit]`.
203
204
```python { .api }
205
class RuffTool(ToolBase):
206
pass
207
```
208
209
Fast Python linter and code formatter written in Rust. Install with `pip install prospector[with_ruff]`.
210
211
## Usage Examples
212
213
### Using Tool Registry
214
215
```python
216
from prospector import tools
217
218
# Get all available tools
219
print("Available tools:")
220
for name, tool_class in tools.TOOLS.items():
221
print(f" {name}: {tool_class.__name__}")
222
223
# Check default tools
224
print(f"\nDefault tools: {tools.DEFAULT_TOOLS}")
225
226
# Check for deprecated names
227
if "pep8" in tools.DEPRECATED_TOOL_NAMES:
228
new_name = tools.DEPRECATED_TOOL_NAMES["pep8"]
229
print(f"pep8 is deprecated, use {new_name} instead")
230
```
231
232
### Creating and Configuring Tools
233
234
```python
235
from prospector import tools
236
from prospector.config import ProspectorConfig
237
from prospector.finder import FileFinder
238
from pathlib import Path
239
240
# Create configuration and file finder
241
config = ProspectorConfig()
242
finder = FileFinder(Path("."))
243
244
# Create a specific tool
245
pylint_tool = tools.TOOLS["pylint"]()
246
247
# Configure the tool
248
config_result = pylint_tool.configure(config, finder)
249
if config_result:
250
config_source, config_messages = config_result
251
if config_source:
252
print(f"Pylint configured from: {config_source}")
253
if config_messages:
254
for msg in config_messages:
255
print(f"Config warning: {msg.message}")
256
257
# Run the tool
258
messages = pylint_tool.run(finder)
259
print(f"Pylint found {len(messages)} issues")
260
```
261
262
### Implementing a Custom Tool
263
264
```python
265
from prospector.tools.base import ToolBase
266
from prospector.finder import FileFinder
267
from prospector.message import Message, Location
268
from typing import TYPE_CHECKING, Optional, Iterable, Union
269
from pathlib import Path
270
271
if TYPE_CHECKING:
272
from prospector.config import ProspectorConfig
273
274
class CustomTool(ToolBase):
275
"""Example custom analysis tool"""
276
277
def configure(self, prospector_config: "ProspectorConfig", found_files: FileFinder) -> Optional[tuple[Optional[Union[str, Path]], Optional[Iterable[Message]]]]:
278
# No external configuration needed
279
return None
280
281
def run(self, found_files: FileFinder) -> list[Message]:
282
messages = []
283
284
# Analyze each Python module
285
for module_path in found_files.iter_all_modules():
286
try:
287
with open(module_path, 'r', encoding='utf-8') as f:
288
lines = f.readlines()
289
290
# Check for TODO comments
291
for line_num, line in enumerate(lines, 1):
292
if 'TODO' in line:
293
location = Location(
294
path=module_path,
295
module=None,
296
function=None,
297
line=line_num,
298
character=line.find('TODO')
299
)
300
301
message = Message(
302
source="custom-tool",
303
code="TODO",
304
location=location,
305
message="TODO comment found",
306
is_fixable=False
307
)
308
messages.append(message)
309
310
except (OSError, UnicodeDecodeError):
311
# Skip files that can't be read
312
continue
313
314
return messages
315
316
# Register custom tool (in practice, this would be done in __init__.py)
317
# tools.TOOLS["custom"] = CustomTool
318
```
319
320
### Working with Optional Tools
321
322
```python
323
from prospector import tools
324
325
def check_optional_tool(tool_name: str):
326
"""Check if an optional tool is available"""
327
try:
328
tool_class = tools.TOOLS[tool_name]
329
tool = tool_class()
330
print(f"{tool_name} is available")
331
return True
332
except Exception as e:
333
print(f"{tool_name} is not available: {e}")
334
return False
335
336
# Check availability of optional tools
337
optional_tools = ["mypy", "bandit", "vulture", "pyroma", "pyright", "ruff"]
338
for tool_name in optional_tools:
339
check_optional_tool(tool_name)
340
```
341
342
### Tool Configuration Discovery
343
344
```python
345
from prospector import tools
346
from prospector.config import ProspectorConfig
347
from prospector.finder import FileFinder
348
from pathlib import Path
349
350
config = ProspectorConfig()
351
finder = FileFinder(Path("."))
352
353
# Check how each tool gets configured
354
for tool_name in tools.DEFAULT_TOOLS:
355
tool_class = tools.TOOLS[tool_name]
356
tool = tool_class()
357
358
try:
359
result = tool.configure(config, finder)
360
if result:
361
config_source, config_messages = result
362
if config_source:
363
print(f"{tool_name}: configured from {config_source}")
364
else:
365
print(f"{tool_name}: using defaults")
366
367
if config_messages:
368
print(f" {len(list(config_messages))} config messages")
369
else:
370
print(f"{tool_name}: no configuration")
371
372
except Exception as e:
373
print(f"{tool_name}: configuration error - {e}")
374
```
375
376
### Analyzing Inline Ignore Directives
377
378
```python
379
from prospector.tools.base import ToolBase
380
381
# Create a tool instance to test ignore parsing
382
tool = ToolBase() # This would normally be a concrete tool
383
384
# Test lines with ignore directives
385
test_lines = [
386
"print('hello') # noqa",
387
"import os # noqa: F401",
388
"x = 1 # noqa: E501,W292",
389
"def func(): # pylint: disable=invalid-name",
390
"regular_line_no_ignore()"
391
]
392
393
for line in test_lines:
394
ignored_codes = tool.get_ignored_codes(line)
395
if ignored_codes:
396
print(f"Line: {line}")
397
print(f" Ignores: {ignored_codes}")
398
else:
399
print(f"Line: {line} (no ignores)")
400
```