• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pyta-uoft / pyta / 17840587224

18 Sep 2025 08:28PM UTC coverage: 94.889% (+0.01%) from 94.879%
17840587224

Pull #1235

github

web-flow
Merge 851fd61e5 into 2237d93b1
Pull Request #1235: Added custom node renderers for the PEP8 error messages E204, E225, E231

26 of 27 new or added lines in 1 file covered. (96.3%)

3602 of 3796 relevant lines covered (94.89%)

18.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.71
/python_ta/reporters/node_printers.py
1
"""Specify how errors should be rendered."""
2

3
import re
20✔
4
from enum import Enum
20✔
5

6
from astroid import nodes
20✔
7

8
NEW_BLANK_LINE_MESSAGE = "# INSERT NEW BLANK LINE HERE"
20✔
9

10

11
def render_message(msg, node, source_lines):
20✔
12
    """Render a message based on type."""
13
    renderer = CUSTOM_MESSAGES.get(msg.symbol, render_generic)
20✔
14
    yield from renderer(msg, node, source_lines)
20✔
15

16

17
def render_generic(msg, node=None, source_lines=None):
20✔
18
    """Default rendering for a message."""
19
    if node is not None:
20✔
20
        start_line, start_col = node.fromlineno, node.col_offset
20✔
21

22
        if isinstance(node, (nodes.FunctionDef, nodes.ClassDef)):
20✔
23
            end_line, end_col = start_line, None
20✔
24
        else:
25
            end_line, end_col = node.end_lineno, node.end_col_offset
20✔
26

27
        # Display up to 2 lines before node for context:
28
        yield from render_context(start_line - 2, start_line, source_lines)
20✔
29

30
        if start_line == end_line:
20✔
31
            yield (
20✔
32
                start_line,
33
                slice(start_col, end_col),
34
                LineType.ERROR,
35
                source_lines[start_line - 1],
36
            )
37
        else:
38
            yield (start_line, slice(start_col, None), LineType.ERROR, source_lines[start_line - 1])
20✔
39
            yield from (
20✔
40
                (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
41
                for line in range(start_line + 1, end_line)
42
            )
43
            yield (end_line, slice(None, end_col), LineType.ERROR, source_lines[end_line - 1])
20✔
44

45
        # Display up to 2 lines after node for context:
46
        yield from render_context(end_line + 1, end_line + 3, source_lines)
20✔
47

48
    else:
49
        line = msg.line
20✔
50
        yield from render_context(line - 2, line, source_lines)
20✔
51
        yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20✔
52
        yield from render_context(line + 1, line + 3, source_lines)
20✔
53

54

55
def render_missing_docstring(_msg, node, source_lines=None):
20✔
56
    """Render a missing docstring message."""
57
    if isinstance(node, nodes.Module):
20✔
58
        yield (None, slice(None, None), LineType.DOCSTRING, '"""YOUR DOCSTRING HERE"""')
20✔
59
        yield from render_context(1, 3, source_lines)
20✔
60
    elif isinstance(node, nodes.ClassDef) or isinstance(node, nodes.FunctionDef):
20✔
61
        start = node.fromlineno
20✔
62
        end = node.body[0].fromlineno
20✔
63
        yield from render_context(start, end, source_lines)
20✔
64
        # Calculate indentation
65
        body = source_lines[end - 1]
20✔
66
        indentation = len(body) - len(body.lstrip())
20✔
67
        yield (
20✔
68
            None,
69
            slice(None, None),
70
            LineType.DOCSTRING,
71
            body[:indentation] + '"""YOUR DOCSTRING HERE"""',
72
        )
73
        yield from render_context(end, end + 2, source_lines)
20✔
74

75

76
def render_trailing_newlines(msg, _node, source_lines=None):
20✔
77
    """Render a trailing newlines message."""
78
    start_line = msg.line - 1
20✔
79
    yield from render_context(start_line - 2, start_line, source_lines)
20✔
80
    yield from (
20✔
81
        (line, slice(None, None), LineType.OTHER, source_lines[line - 1])
82
        for line in range(start_line, len(source_lines) + 1)
83
    )
84

85

86
def render_trailing_whitespace(msg, _node, source_lines=None):
20✔
87
    """Render a trailing whitespace message."""
88
    line = msg.line
20✔
89
    start_index, end_index = len(source_lines[line - 1].rstrip()), len(source_lines[line - 1])
20✔
90
    yield from render_context(line - 1, line, source_lines)
20✔
91
    yield (line, slice(start_index, end_index), LineType.ERROR, source_lines[line - 1])
20✔
92
    yield from render_context(line + 1, line + 2, source_lines)
20✔
93

94

95
def render_context(start, stop, source_lines):
20✔
96
    """Helper for rendering context lines."""
97
    start, stop = max(start, 1), min(stop, len(source_lines))
20✔
98
    yield from (
20✔
99
        (line, slice(None, None), LineType.CONTEXT, source_lines[line - 1])
100
        for line in range(start, stop)
101
    )
102

103

104
def render_missing_return_type(_msg, node, source_lines=None):
20✔
105
    """Render a type annotation return message."""
106
    start_line, start_col = node.fromlineno, node.parent.col_offset
20✔
107
    end_line, end_col = node.end_lineno, node.end_col_offset
20✔
108

109
    # Display up to 2 lines before node for context:
110
    yield from render_context(start_line - 2, start_line, source_lines)
20✔
111
    yield from (
20✔
112
        (line, slice(None, end_col + 1), LineType.ERROR, source_lines[line - 1])
113
        for line in range(start_line, end_line + 1)
114
    )
115
    # Display up to 2 lines after node for context:
116
    yield from render_context(end_line + 1, end_line + 3, source_lines)
20✔
117

118

119
def render_too_many_arguments(msg, node, source_lines=None):
20✔
120
    """Render a too many arguments message."""
121
    # node is a FunctionDef node so replace it with its Arguments child
122
    yield from render_generic(msg, node.args, source_lines)
20✔
123

124

125
def render_missing_space_in_doctest(msg, _node, source_lines=None):
20✔
126
    """Render a missing space in doctest message"""
127
    line = msg.line
20✔
128

129
    # Display 2 lines before and after the erroneous line
130
    yield from render_context(line - 2, line, source_lines)
20✔
131
    yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20✔
132
    yield from render_context(line + 1, line + 3, source_lines)
20✔
133

134

135
def render_pep8_errors(msg, _node, source_lines=None):
20✔
136
    """Render a PEP8 error message."""
137
    # Extract the raw error message
138
    raw_msg = getattr(msg, "msg", "")
20✔
139

140
    # Search for the first appearance of the error code in the extracted error text
141
    matched_error = re.search(r"(E\d{3})", raw_msg)
20✔
142
    if matched_error:
20✔
143
        error_code = matched_error.group(1)
20✔
144
        # Render the appropriate error through the RENDERERS dict
145
        if error_code in RENDERERS:
20✔
146
            yield from RENDERERS[error_code](msg, _node, source_lines)
20✔
147
            return
20✔
148

149
    # If none of the error codes were present, render the error using the generic error renderer
150
    yield from render_generic(msg, _node, source_lines)
20✔
151

152

153
def render_blank_line(line):
20✔
154
    """Render a blank line for a PEP8 error message."""
155
    yield (line + 1, slice(None, None), LineType.ERROR, " " * 28)
20✔
156

157

158
def render_pep8_errors_e101_and_e123(msg, _node, source_lines=None):
20✔
159
    """Render a PEP8 indentation contains mixed spaces and tabs message
160
    AND a PEP8 closing bracket does not match indentation of opening bracket's line message."""
161
    line = msg.line
20✔
162
    curr_idx = len(source_lines[line - 1]) - len(source_lines[line - 1].lstrip())
20✔
163
    yield from render_context(line - 2, line, source_lines)
20✔
164
    yield (line, slice(0, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
165
    yield from render_context(line + 1, line + 3, source_lines)
20✔
166

167

168
def render_pep8_errors_e115(msg, _node, source_lines=None):
20✔
169
    """Render a PEP8 expected an indented block (comment) message."""
170
    line = msg.line
20✔
171

172
    yield from render_context(line - 2, line, source_lines)
20✔
173
    yield (
20✔
174
        line,
175
        slice(0, len(source_lines[line - 1])),
176
        LineType.ERROR,
177
        source_lines[line - 1] + "  # INDENT THIS LINE",
178
    )
179
    yield from render_context(line + 1, line + 3, source_lines)
20✔
180

181

182
def render_pep8_errors_e116(msg, _node, source_lines=None):
20✔
183
    """Render a PEP8 unexpected indentation (comment) message"""
184
    line = msg.line
20✔
185
    curr_idx = len(source_lines[line - 1]) - len(source_lines[line - 1].lstrip())
20✔
186
    yield from render_context(line - 2, line, source_lines)
20✔
187
    yield (
20✔
188
        line,
189
        slice(0, curr_idx),
190
        LineType.ERROR,
191
        source_lines[line - 1],
192
    )
193
    yield from render_context(line + 1, line + 3, source_lines)
20✔
194

195

196
def render_pep8_errors_e122_and_e127_and_e131(msg, _node, source_lines=None):
20✔
197
    """
198
    Render a PEP8 continuation line missing indentation or outdented message, a line over-indented for visual indent
199
    message, and a continuation line unaligned for hanging indent message.
200
    """
201
    line = msg.line
20✔
202
    curr_line_start_index = len(source_lines[line - 1]) - len(source_lines[line - 1].lstrip())
20✔
203
    end_index = curr_line_start_index if curr_line_start_index > 0 else len(source_lines[line - 1])
20✔
204
    yield from render_context(line - 2, line, source_lines)
20✔
205
    yield (
20✔
206
        line,
207
        slice(0, end_index),
208
        LineType.ERROR,
209
        source_lines[line - 1],
210
    )
211
    yield from render_context(line + 1, line + 3, source_lines)
20✔
212

213

214
def render_pep8_errors_e124(msg, _node, source_lines=None):
20✔
215
    """Render a PEP8 closing bracket does not match visual indentation message."""
216
    line = msg.line
20✔
217
    res = re.search(r"column (\d+)", msg.msg)
20✔
218
    col = int(res.group().split()[-1])
20✔
219
    yield from render_context(line - 2, line, source_lines)
20✔
220
    yield (line, slice(col, col + 1), LineType.ERROR, source_lines[line - 1])
20✔
221
    yield from render_context(line + 1, line + 3, source_lines)
20✔
222

223

224
def render_pep8_errors_e125_and_e129(msg, _node, source_lines=None):
20✔
225
    """Render a PEP8 continuation line with same indent as next logical line message
226
    AND a PEP8 visually indented line with same indent as next logical line messsage"""
227
    line = msg.line
20✔
228
    curr_idx = len(source_lines[line - 1]) - len(source_lines[line - 1].lstrip())
20✔
229

230
    yield from render_context(line - 2, line, source_lines)
20✔
231
    yield (
20✔
232
        line,
233
        slice(curr_idx, len(source_lines[line - 1])),
234
        LineType.ERROR,
235
        source_lines[line - 1] + " " * 2 + "# INDENT THIS LINE",
236
    )
237
    yield from render_context(line + 1, line + 3, source_lines)
20✔
238

239

240
def render_pep8_errors_e128(msg, _node, source_lines):
20✔
241
    """Render a PEP8 continuation line under-indented for visual indent message."""
242
    line = msg.line
20✔
243
    res = re.search(r"column (\d+)", msg.msg)
20✔
244
    col = int(res.group().split()[-1])
20✔
245

246
    yield from render_context(line - 2, line, source_lines)
20✔
247
    yield (line, slice(0, col if col != 0 else None), LineType.ERROR, source_lines[line - 1])
20✔
248
    yield from render_context(line + 1, line + 3, source_lines)
20✔
249

250

251
def render_pep8_errors_e201_e202_e203_e211(msg, _node, source_lines=None):
20✔
252
    """Render a PEP8 whitespace after '(' message,
253
    a PEP8 whitespace before ')' message,
254
    a PEP8 whitespace before ‘,’, ‘;’, or ‘:’ message,
255
    AND a PEP8 whitespace before '(' message.."""
256
    line = msg.line
20✔
257
    res = re.search(r"column (\d+)", msg.msg)
20✔
258
    col = int(res.group().split()[-1])
20✔
259
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
20✔
260

261
    yield from render_context(line - 2, line, source_lines)
20✔
262
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
263
    yield from render_context(line + 1, line + 3, source_lines)
20✔
264

265

266
def render_pep8_errors_e204(msg, _node, source_lines=None):
20✔
267
    line = msg.line
20✔
268
    res = re.search(r"column (\d+)", msg.msg)
20✔
269
    col = int(res.group().split()[-1])
20✔
270
    curr_idx = col + 1
20✔
271

272
    yield from render_context(line - 2, line, source_lines)
20✔
273
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
274
    yield from render_context(line + 1, line + 3, source_lines)
20✔
275

276

277
def render_pep8_errors_e221(msg, _node, source_lines=None):
20✔
278
    """Render a PEP8 multiple spaces before operator message."""
279
    line = msg.line
20✔
280
    res = re.search(r"column (\d+)", msg.msg)
20✔
281
    col = int(res.group().split()[-1])
20✔
282
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
20✔
283

284
    yield from render_context(line - 2, line, source_lines)
20✔
285
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
286
    yield from render_context(line + 1, line + 3, source_lines)
20✔
287

288

289
def render_pep8_errors_e222(msg, _node, source_lines=None):
20✔
290
    """Render a PEP8 multiple spaces after operator message"""
291
    line = msg.line
20✔
292
    res = re.search(r"column (\d+)", msg.msg)
20✔
293
    col = int(res.group().split()[-1])
20✔
294

295
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
20✔
296
    yield from render_context(line - 2, line, source_lines)
20✔
297
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
298
    yield from render_context(line + 1, line + 3, source_lines)
20✔
299

300

301
def render_pep8_errors_e223(msg, _node, source_lines=None):
20✔
302
    """Render a PEP8 tab before operator message."""
303
    line = msg.line
20✔
304
    res = re.search(r"column (\d+)", msg.msg)
20✔
305
    col = int(res.group().split()[-1])
20✔
306
    curr_idx = (
20✔
307
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("\t"))
308
    )
309

310
    yield from render_context(line - 2, line, source_lines)
20✔
311
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
312
    yield from render_context(line + 1, line + 3, source_lines)
20✔
313

314

315
def render_pep8_errors_e224_and_e273(msg, _node, source_lines):
20✔
316
    """Render a PEP8 tab after operator message and a PEP8 tab after keyword message."""
317
    line = msg.line
20✔
318
    res = re.search(r"column (\d+)", msg.msg)
20✔
319
    col = int(res.group().split()[-1])
20✔
320
    curr_idx = (
20✔
321
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("\t"))
322
    )
323

324
    yield from render_context(line - 2, line, source_lines)
20✔
325
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
326
    yield from render_context(line + 1, line + 3, source_lines)
20✔
327

328

329
def render_pep8_errors_e225(msg, _node, source_lines):
20✔
330
    """Render a PEP8 missing whitespace around operator message"""
331
    line = msg.line
20✔
332
    res = re.search(r"column (\d+)", msg.msg)
20✔
333
    col = int(res.group().split()[-1])
20✔
334
    curr_idx = col + 1
20✔
335

336
    multi_char_operators = {"==, >=, <=, !="}
20✔
337
    # highlight multiple characters for operators that are longer than one character
338
    if source_lines[line - 1][col : col + 2] in multi_char_operators:
20✔
NEW
339
        curr_idx += 1
×
340

341
    yield from render_context(line - 2, line, source_lines)
20✔
342
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
343
    yield from render_context(line + 1, line + 3, source_lines)
20✔
344

345

346
def render_pep8_errors_e226(msg, _node, source_lines):
20✔
347
    """Render a PEP8 missing whitespace around arithmetic operator message"""
348
    line = msg.line
20✔
349
    res = re.search(r"column (\d+)", msg.msg)
20✔
350
    col = int(res.group().split()[-1])
20✔
351
    end_idx = col + 1
20✔
352

353
    yield from render_context(line - 2, line, source_lines)
20✔
354
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
20✔
355
    yield from render_context(line + 1, line + 3, source_lines)
20✔
356

357

358
def render_pep8_errors_e227(msg, _node, source_lines=None):
20✔
359
    """Render a PEP8 missing whitespace around bitwise or shift operator message."""
360
    line = msg.line
20✔
361
    res = re.search(r"column (\d+)", msg.msg)
20✔
362
    col = int(res.group().split()[-1])
20✔
363
    # Check which operator to get the correct range of the line to highlight.
364
    # Default highlight is one character, but may be updated to two.
365
    # Note that only binary bitwise operators that are more than one character are included.
366
    operators = {">>", "<<"}
20✔
367
    end_idx = col + 1
20✔
368
    end_idx = end_idx + 1 if source_lines[line - 1][col : col + 2] in operators else end_idx
20✔
369

370
    yield from render_context(line - 2, line, source_lines)
20✔
371
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
20✔
372
    yield from render_context(line + 1, line + 3, source_lines)
20✔
373

374

375
def render_pep8_errors_e228(msg, _node, source_lines=None):
20✔
376
    """Render a PEP8 missing whitespace around modulo operator message."""
377
    line = msg.line
20✔
378
    res = re.search(r"column (\d+)", msg.msg)
20✔
379
    col = int(res.group().split()[-1])
20✔
380

381
    yield from render_context(line - 2, line, source_lines)
20✔
382
    yield (
20✔
383
        line,
384
        slice(col, col + 1),
385
        LineType.ERROR,
386
        source_lines[line - 1] + "  # INSERT A SPACE BEFORE AND AFTER THE % OPERATOR",
387
    )
388
    yield from render_context(line + 1, line + 3, source_lines)
20✔
389

390

391
def render_pep8_errors_e231(msg, _node, source_lines=None):
20✔
392
    line = msg.line
20✔
393
    res = re.search(r"column (\d+)", msg.msg)
20✔
394
    col = int(res.group().split()[-1])
20✔
395
    curr_idx = col + 1
20✔
396

397
    yield from render_context(line - 2, line, source_lines)
20✔
398
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
399
    yield from render_context(line + 1, line + 3, source_lines)
20✔
400

401

402
def render_pep8_errors_e251(msg, _node, source_lines=None):
20✔
403
    """Render a PEP8 unexpected spaces around keyword / parameter equals message."""
404
    line = msg.line
20✔
405
    res = re.search(r"column (\d+)", msg.msg)
20✔
406
    col = int(res.group().split()[-1])
20✔
407
    equals_sign_idx = source_lines[line - 1][col:].find("=")
20✔
408
    code = source_lines[line - 1][col : col + equals_sign_idx if equals_sign_idx != -1 else None]
20✔
409
    end_idx = col + len(code) - len(code.lstrip())
20✔
410

411
    yield from render_context(line - 2, line, source_lines)
20✔
412
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
20✔
413
    yield from render_context(line + 1, line + 3, source_lines)
20✔
414

415

416
def render_pep8_errors_e261(msg, _node, source_lines=None):
20✔
417
    """Render a PEP8 at least two spaces before inline comment message."""
418
    line = msg.line
20✔
419
    res = re.search(r"column (\d+)", msg.msg)
20✔
420
    col = int(res.group().split()[-1])
20✔
421

422
    yield from render_context(line - 2, line, source_lines)
20✔
423
    yield (
20✔
424
        line,
425
        slice(col, len(source_lines[line - 1])),
426
        LineType.ERROR,
427
        source_lines[line - 1] + "  # INSERT TWO SPACES BEFORE THE '#'",
428
    )
429
    yield from render_context(line + 1, line + 3, source_lines)
20✔
430

431

432
def render_pep8_errors_e262(msg, _node, source_lines=None):
20✔
433
    """Render a PEP8 inline comment should start with '# ' message"""
434
    line = msg.line
20✔
435
    res = re.search(r"column (\d+)", msg.msg)
20✔
436
    col = int(res.group().split()[-1])
20✔
437

438
    source_line = source_lines[line - 1]
20✔
439
    keyword_idx = len(source_line) - len(source_line[col:].lstrip("# \t"))
20✔
440

441
    yield from render_context(line - 2, line, source_lines)
20✔
442
    yield (line, slice(col, keyword_idx), LineType.ERROR, source_line)
20✔
443
    yield from render_context(line + 1, line + 3, source_lines)
20✔
444

445

446
def render_pep8_errors_e265(msg, _node, source_lines=None):
20✔
447
    """Render a PEP8 block comment should start with '# ' message."""
448
    line = msg.line
20✔
449
    yield from render_context(line - 2, line, source_lines)
20✔
450
    yield (
20✔
451
        line,
452
        slice(0, len(source_lines[line - 1])),
453
        LineType.ERROR,
454
        source_lines[line - 1] + "  # INSERT SPACE AFTER THE '#'",
455
    )
456
    yield from render_context(line + 1, line + 3, source_lines)
20✔
457

458

459
def render_pep8_errors_e266(msg, _node, source_lines=None):
20✔
460
    """Render a PEP8 too many leading ‘#’ for block comment message."""
461
    line = msg.line
20✔
462
    res = re.search(r"column (\d+)", msg.msg)
20✔
463
    col = int(res.group().split()[-1])
20✔
464
    curr_idx = (
20✔
465
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("#"))
466
    )
467

468
    yield from render_context(line - 2, line, source_lines)
20✔
469
    yield (
20✔
470
        line,
471
        slice(col, curr_idx),
472
        LineType.ERROR,
473
        source_lines[line - 1] + "  # THERE SHOULD ONLY BE ONE '#'",
474
    )
475
    yield from render_context(line + 1, line + 3, source_lines)
20✔
476

477

478
def render_pep8_errors_e272(msg, _node, source_lines=None):
20✔
479
    """Render a PEP8 multiple spaces before keyword message."""
480
    line = msg.line
20✔
481
    res = re.search(r"column (\d+)", msg.msg)
20✔
482
    col = int(res.group().split()[-1])
20✔
483
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
20✔
484

485
    yield from render_context(line - 2, line, source_lines)
20✔
486
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
487
    yield from render_context(line + 1, line + 3, source_lines)
20✔
488

489

490
def render_pep8_errors_e275(msg, _node, source_lines=None):
20✔
491
    """Render a PEP8 missing whitespace after keyword message."""
492
    line = msg.line
20✔
493
    res = re.search(r"column (\d+)", msg.msg)
20✔
494
    col = int(res.group().split()[-1])
20✔
495

496
    # Get the range for highlighting the corresponding keyword.
497
    keyword = source_lines[line - 1][:col].split()[-1]
20✔
498
    keyword_idx = source_lines[line - 1].index(keyword)
20✔
499

500
    yield from render_context(line - 2, line, source_lines)
20✔
501
    yield (
20✔
502
        line,
503
        slice(keyword_idx, col),
504
        LineType.ERROR,
505
        source_lines[line - 1] + "  # INSERT SPACE AFTER KEYWORD",
506
    )
507
    yield from render_context(line + 1, line + 3, source_lines)
20✔
508

509

510
def render_pep8_errors_e301(msg, _node, source_lines=None):
20✔
511
    """Render a PEP8 expected 1 blank line message."""
512
    line = msg.line - 1
20✔
513
    yield from render_context(line - 1, line + 1, source_lines)
20✔
514
    body = source_lines[line]
20✔
515
    indentation = len(body) - len(body.lstrip())
20✔
516
    yield (
20✔
517
        None,
518
        slice(None, None),
519
        LineType.ERROR,
520
        body[:indentation] + NEW_BLANK_LINE_MESSAGE,
521
    )
522
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
523

524

525
def render_pep8_errors_e302(msg, _node, source_lines=None):
20✔
526
    """Render a PEP8 expected 2 blank lines message."""
527
    line = msg.line - 1
20✔
528
    if "found 0" in msg.msg:
20✔
529
        yield from render_context(line - 1, line + 1, source_lines)
20✔
530
        yield from (
20✔
531
            (
532
                None,
533
                slice(None, None),
534
                LineType.ERROR,
535
                NEW_BLANK_LINE_MESSAGE,
536
            )
537
            for _ in range(0, 2)
538
        )
539
    else:
540
        line -= 1
20✔
541
        yield from render_context(line - 1, line + 1, source_lines)
20✔
542
        yield from render_blank_line(line)
20✔
543
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
20✔
544
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
545

546

547
def render_pep8_errors_e303(msg, _node, source_lines=None):
20✔
548
    """Render a PEP8 too many blank lines message."""
549
    line = msg.line - 1
20✔
550
    while source_lines[line - 1].strip() == "":
20✔
551
        line -= 1
20✔
552
    yield from render_context(line - 1, line + 1, source_lines)
20✔
553
    body = source_lines[msg.line - 1]
20✔
554
    indentation = len(body) - len(body.lstrip())
20✔
555
    yield from (
20✔
556
        (curr_line, slice(None, None), LineType.ERROR, " " * (indentation + 28))
557
        for curr_line in range(line + 1, msg.line)
558
    )
559
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
560

561

562
def render_pep8_errors_e304(msg, _node, source_lines=None):
20✔
563
    """Render a PEP8 blank lines found after function decorator message."""
564
    line = msg.line - 1
20✔
565
    while source_lines[line - 1].strip() == "":
20✔
566
        line -= 1
20✔
567
    yield from render_context(line - 1, line + 1, source_lines)
20✔
568
    yield from (
20✔
569
        (curr_line, slice(None, None), LineType.ERROR, " " * 28)
570
        for curr_line in range(line + 1, msg.line)
571
    )
572
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
573

574

575
def render_pep8_errors_e305(msg, _node, source_lines=None):
20✔
576
    """Render a PEP8 expected 2 blank lines after class or function definition message."""
577
    line = msg.line - 1
20✔
578
    if "found 0" in msg.msg:
20✔
579
        yield from render_context(line - 1, line + 1, source_lines)
20✔
580
        yield from (
20✔
581
            (
582
                None,
583
                slice(None, None),
584
                LineType.ERROR,
585
                NEW_BLANK_LINE_MESSAGE,
586
            )
587
            for _ in range(0, 2)
588
        )
589
    else:
590
        line -= 1
20✔
591
        yield from render_context(line - 1, line + 1, source_lines)
20✔
592
        yield from render_blank_line(line)
20✔
593
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
20✔
594
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
595

596

597
def render_pep8_errors_e306(msg, _node, source_lines=None):
20✔
598
    """Render a PEP8 expected 1 blank line before a nested definition message."""
599
    line = msg.line - 1
20✔
600
    yield from render_context(line - 1, line + 1, source_lines)
20✔
601
    body = source_lines[line]
20✔
602
    indentation = len(body) - len(body.lstrip())
20✔
603
    yield (
20✔
604
        None,
605
        slice(None, None),
606
        LineType.ERROR,
607
        body[:indentation] + NEW_BLANK_LINE_MESSAGE,
608
    )
609
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
610

611

612
def render_missing_return_statement(msg, node, source_lines=None):
20✔
613
    """
614
    Render a missing return statements message
615
    """
616
    yield from render_context(msg.line, msg.end_line + 1, source_lines)
20✔
617

618
    # calculate indentation for the insertion point
619
    body = source_lines[msg.end_line - 1]
20✔
620
    indentation = len(source_lines[msg.line - 1]) - len(source_lines[msg.line - 1].lstrip())
20✔
621

622
    # determine whether reaching the end of function
623
    first_statement_line = node.end_lineno if len(node.body) == 0 else node.body[0].lineno
20✔
624
    function_indentation = len(source_lines[first_statement_line - 1]) - len(
20✔
625
        source_lines[first_statement_line - 1].lstrip()
626
    )
627

628
    if msg.end_line == node.end_lineno and indentation == function_indentation:
20✔
629
        insertion_text = body[:indentation] + "# INSERT RETURN STATEMENT HERE"
20✔
630
    else:
631
        insertion_text = body[:indentation] + "# INSERT RETURN STATEMENT HERE (OR BELOW)"
20✔
632

633
    # insert the message
634
    yield (
20✔
635
        None,
636
        slice(indentation, None),
637
        LineType.ERROR,
638
        insertion_text,
639
    )
640

641
    yield from render_context(msg.end_line + 1, msg.end_line + 3, source_lines)
20✔
642

643

644
def render_static_type_checker_errors(msg, _node=None, source_lines=None):
20✔
645
    """Render a message for incompatible argument types."""
646
    start_line = msg.line
20✔
647
    start_col = msg.column
20✔
648
    end_line = msg.end_line
20✔
649
    end_col = msg.end_column
20✔
650
    yield from render_context(start_line - 2, start_line, source_lines)
20✔
651

652
    if start_line == end_line:
20✔
653
        yield (
20✔
654
            start_line,
655
            slice(start_col - 1, end_col),
656
            LineType.ERROR,
657
            source_lines[start_line - 1],
658
        )
659
    else:
660
        yield (start_line, slice(start_col - 1, None), LineType.ERROR, source_lines[start_line - 1])
20✔
661
        yield from (
20✔
662
            (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
663
            for line in range(start_line + 1, end_line)
664
        )
665
        yield (end_line, slice(None, end_col), LineType.ERROR, source_lines[end_line - 1])
20✔
666
    yield from render_context(end_line + 1, end_line + 3, source_lines)
20✔
667

668

669
CUSTOM_MESSAGES = {
20✔
670
    "missing-module-docstring": render_missing_docstring,
671
    "missing-class-docstring": render_missing_docstring,
672
    "missing-function-docstring": render_missing_docstring,
673
    "trailing-newlines": render_trailing_newlines,
674
    "trailing-whitespace": render_trailing_whitespace,
675
    "missing-return-type": render_missing_return_type,
676
    "too-many-arguments": render_too_many_arguments,
677
    "missing-space-in-doctest": render_missing_space_in_doctest,
678
    "pep8-errors": render_pep8_errors,
679
    "missing-return-statement": render_missing_return_statement,
680
    "incompatible-argument-type": render_static_type_checker_errors,
681
    "incompatible-assignment": render_static_type_checker_errors,
682
    "list-item-type-mismatch": render_static_type_checker_errors,
683
    "unsupported-operand-types": render_static_type_checker_errors,
684
    "union-attr-error": render_static_type_checker_errors,
685
    "dict-item-type-mismatch": render_static_type_checker_errors,
686
}
687

688
RENDERERS = {
20✔
689
    "E101": render_pep8_errors_e101_and_e123,
690
    "E123": render_pep8_errors_e101_and_e123,
691
    "E115": render_pep8_errors_e115,
692
    "E116": render_pep8_errors_e116,
693
    "E122": render_pep8_errors_e122_and_e127_and_e131,
694
    "E127": render_pep8_errors_e122_and_e127_and_e131,
695
    "E131": render_pep8_errors_e122_and_e127_and_e131,
696
    "E124": render_pep8_errors_e124,
697
    "E125": render_pep8_errors_e125_and_e129,
698
    "E129": render_pep8_errors_e125_and_e129,
699
    "E128": render_pep8_errors_e128,
700
    "E201": render_pep8_errors_e201_e202_e203_e211,
701
    "E202": render_pep8_errors_e201_e202_e203_e211,
702
    "E203": render_pep8_errors_e201_e202_e203_e211,
703
    "E204": render_pep8_errors_e204,
704
    "E211": render_pep8_errors_e201_e202_e203_e211,
705
    "E221": render_pep8_errors_e221,
706
    "E222": render_pep8_errors_e222,
707
    "E223": render_pep8_errors_e223,
708
    "E224": render_pep8_errors_e224_and_e273,
709
    "E225": render_pep8_errors_e225,
710
    "E231": render_pep8_errors_e231,
711
    "E273": render_pep8_errors_e224_and_e273,
712
    "E226": render_pep8_errors_e226,
713
    "E227": render_pep8_errors_e227,
714
    "E228": render_pep8_errors_e228,
715
    "E251": render_pep8_errors_e251,
716
    "E261": render_pep8_errors_e261,
717
    "E262": render_pep8_errors_e262,
718
    "E265": render_pep8_errors_e265,
719
    "E266": render_pep8_errors_e266,
720
    "E272": render_pep8_errors_e272,
721
    "E275": render_pep8_errors_e275,
722
    "E301": render_pep8_errors_e301,
723
    "E302": render_pep8_errors_e302,
724
    "E303": render_pep8_errors_e303,
725
    "E304": render_pep8_errors_e304,
726
    "E305": render_pep8_errors_e305,
727
    "E306": render_pep8_errors_e306,
728
}
729

730

731
class LineType(Enum):
20✔
732
    """An enumeration for _add_line method line types."""
733

734
    ERROR = 1  # line with error
20✔
735
    CONTEXT = 2  # non-error/other line added for context
20✔
736
    OTHER = 3  # line included in source but not error
20✔
737
    ELLIPSIS = 5  # code replaced with ellipsis
20✔
738
    DOCSTRING = 6  # docstring needed warning
20✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc