0
# Messages
1
2
Standardized data structures for representing analysis results and source code locations. The Message and Location classes provide a unified format for all analysis tool outputs.
3
4
## Capabilities
5
6
### Message Class
7
8
Represents a single analysis issue or finding from a static analysis tool.
9
10
```python { .api }
11
class Message:
12
def __init__(self, source: str, code: str, location: Location, message: str,
13
doc_url: Optional[str] = None, is_fixable: bool = False) -> None
14
```
15
16
Creates a new message representing an analysis finding.
17
18
**Parameters:**
19
- `source`: str - Name of the tool that generated this message (e.g., "pylint", "pyflakes")
20
- `code`: str - Error/warning code from the tool (e.g., "E501", "unused-import")
21
- `location`: Location - Source code location where the issue was found
22
- `message`: str - Human-readable description of the issue
23
- `doc_url`: Optional[str] - URL to documentation about this issue type
24
- `is_fixable`: bool - Whether this issue can be automatically fixed
25
26
**Properties:**
27
- `source`: str - Tool that generated the message
28
- `code`: str - Error/warning code
29
- `location`: Location - Location of the issue
30
- `message`: str - Human-readable message text
31
- `doc_url`: Optional[str] - Documentation URL
32
- `is_fixable`: bool - Auto-fixable flag
33
34
**Comparison Methods:**
35
Messages can be compared and sorted. Two messages are equal if they have the same location and code. Messages are sorted first by location, then by code.
36
37
```python { .api }
38
def __eq__(self, other: object) -> bool
39
```
40
41
```python { .api }
42
def __lt__(self, other: "Message") -> bool
43
```
44
45
```python { .api }
46
def __repr__(self) -> str
47
```
48
49
### Location Class
50
51
Represents a specific location in source code where an issue was found.
52
53
```python { .api }
54
class Location:
55
def __init__(self, path: Optional[Union[Path, str]], module: Optional[str],
56
function: Optional[str], line: Optional[int], character: Optional[int],
57
line_end: Optional[int] = None, character_end: Optional[int] = None) -> None
58
```
59
60
Creates a new location object.
61
62
**Parameters:**
63
- `path`: Optional[Union[Path, str]] - File path (converted to absolute Path)
64
- `module`: Optional[str] - Python module name
65
- `function`: Optional[str] - Function or method name where issue occurs
66
- `line`: Optional[int] - Line number (1-based, None for file-level issues)
67
- `character`: Optional[int] - Character position on the line (0-based)
68
- `line_end`: Optional[int] - End line number for multi-line issues
69
- `character_end`: Optional[int] - End character position for multi-line issues
70
71
**Properties:**
72
- `path`: Optional[Path] - Absolute file path
73
- `module`: Optional[str] - Module name
74
- `function`: Optional[str] - Function name
75
- `line`: Optional[int] - Line number
76
- `character`: Optional[int] - Character position
77
- `line_end`: Optional[int] - End line number
78
- `character_end`: Optional[int] - End character position
79
80
```python { .api }
81
def absolute_path(self) -> Optional[Path]
82
```
83
84
Returns the absolute path to the file.
85
86
**Returns:**
87
- `Optional[Path]` - Absolute path or None if no path was provided
88
89
```python { .api }
90
def relative_path(self, root: Optional[Path]) -> Optional[Path]
91
```
92
93
Returns the path relative to the specified root directory.
94
95
**Parameters:**
96
- `root`: Optional[Path] - Root directory for relative path calculation
97
98
**Returns:**
99
- `Optional[Path]` - Relative path if possible, otherwise absolute path
100
101
**Comparison Methods:**
102
Locations can be compared and sorted. Comparison is done by path first, then line, then character.
103
104
```python { .api }
105
def __eq__(self, other: object) -> bool
106
```
107
108
```python { .api }
109
def __lt__(self, other: "Location") -> bool
110
```
111
112
```python { .api }
113
def __hash__(self) -> int
114
```
115
116
```python { .api }
117
def __repr__(self) -> str
118
```
119
120
### Utility Functions
121
122
```python { .api }
123
def make_tool_error_message(filepath: Union[Path, str], source: str, code: str, message: str,
124
line: Optional[int] = None, character: Optional[int] = None,
125
module: Optional[str] = None, function: Optional[str] = None) -> Message
126
```
127
128
Convenience function for creating Message objects for tool errors.
129
130
**Parameters:**
131
- `filepath`: Union[Path, str] - Path to the file
132
- `source`: str - Tool name
133
- `code`: str - Error code
134
- `message`: str - Error message
135
- `line`: Optional[int] - Line number
136
- `character`: Optional[int] - Character position
137
- `module`: Optional[str] - Module name
138
- `function`: Optional[str] - Function name
139
140
**Returns:**
141
- `Message` - New Message object with the specified parameters
142
143
## Usage Examples
144
145
### Creating Messages
146
147
```python
148
from pathlib import Path
149
from prospector.message import Message, Location, make_tool_error_message
150
151
# Create a location
152
location = Location(
153
path=Path("src/main.py"),
154
module="main",
155
function="calculate",
156
line=42,
157
character=10
158
)
159
160
# Create a message
161
message = Message(
162
source="pylint",
163
code="C0103",
164
location=location,
165
message="Invalid name 'x' for variable",
166
doc_url="https://pylint.pycqa.org/en/latest/messages/convention/invalid-name.html",
167
is_fixable=False
168
)
169
170
print(f"Issue: {message.message}")
171
print(f"Location: {message.location}")
172
print(f"From: {message.source}")
173
```
174
175
### Using Convenience Function
176
177
```python
178
from prospector.message import make_tool_error_message
179
180
# Create a message using the convenience function
181
message = make_tool_error_message(
182
filepath="src/utils.py",
183
source="pyflakes",
184
code="unused-import",
185
message="'os' imported but unused",
186
line=1,
187
character=0,
188
module="utils"
189
)
190
191
print(f"{message.source}: {message.message}")
192
```
193
194
### Working with Locations
195
196
```python
197
from pathlib import Path
198
from prospector.message import Location
199
200
# Create location with minimal information
201
location = Location(
202
path="src/main.py",
203
module=None,
204
function=None,
205
line=100,
206
character=15
207
)
208
209
# Get absolute path
210
abs_path = location.absolute_path()
211
print(f"Absolute path: {abs_path}")
212
213
# Get relative path from project root
214
project_root = Path("/home/user/myproject")
215
rel_path = location.relative_path(project_root)
216
print(f"Relative path: {rel_path}")
217
218
# Location for file-level issues
219
file_location = Location(
220
path="setup.py",
221
module=None,
222
function=None,
223
line=None, # No specific line
224
character=None
225
)
226
```
227
228
### Sorting and Comparing Messages
229
230
```python
231
from prospector.message import Message, Location
232
233
# Create multiple messages
234
messages = [
235
Message("pylint", "E501", Location("file2.py", None, None, 10, 0), "Line too long"),
236
Message("pylint", "C0103", Location("file1.py", None, None, 5, 0), "Invalid name"),
237
Message("pyflakes", "unused", Location("file1.py", None, None, 5, 0), "Unused import"),
238
Message("pylint", "E501", Location("file1.py", None, None, 20, 0), "Line too long"),
239
]
240
241
# Sort messages (by location, then by code)
242
sorted_messages = sorted(messages)
243
244
for msg in sorted_messages:
245
print(f"{msg.location.path}:{msg.location.line} {msg.source}:{msg.code} {msg.message}")
246
247
# Check for duplicates
248
msg1 = Message("pylint", "E501", Location("test.py", None, None, 10, 0), "Error 1")
249
msg2 = Message("pylint", "E501", Location("test.py", None, None, 10, 0), "Error 2")
250
251
print(f"Are messages equal? {msg1 == msg2}") # True - same location and code
252
```
253
254
### Multi-line Issues
255
256
```python
257
from prospector.message import Message, Location
258
259
# Create location for multi-line issue
260
location = Location(
261
path="src/complex.py",
262
module="complex",
263
function="long_function",
264
line=50, # Start line
265
character=4, # Start character
266
line_end=55, # End line
267
character_end=8 # End character
268
)
269
270
message = Message(
271
source="pylint",
272
code="R0915",
273
location=location,
274
message="Too many statements (60/50)"
275
)
276
277
print(f"Issue spans lines {location.line}-{location.line_end}")
278
```