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

pyta-uoft / pyta / 19590712582

22 Nov 2025 05:06AM UTC coverage: 93.941% (+0.01%) from 93.928%
19590712582

Pull #1267

github

web-flow
Merge a73dfc158 into 7e38b378a
Pull Request #1267: Shorten render generic

7 of 7 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

3535 of 3763 relevant lines covered (93.94%)

17.81 hits per line

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

99.6
/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
MAX_SNIPPET_LINES = 10
20✔
10

11

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

17

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

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

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

31
        if start_line == end_line:
20✔
32
            yield (
20✔
33
                start_line,
34
                slice(start_col, end_col),
35
                LineType.ERROR,
36
                source_lines[start_line - 1],
37
            )
38
        else:
39
            if end_line - start_line <= MAX_SNIPPET_LINES:
20✔
40
                yield (
20✔
41
                    start_line,
42
                    slice(start_col, None),
43
                    LineType.ERROR,
44
                    source_lines[start_line - 1],
45
                )
46
                yield from (
20✔
47
                    (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
48
                    for line in range(start_line + 1, end_line)
49
                )
50
                yield (end_line, slice(None, end_col), LineType.ERROR, source_lines[end_line - 1])
20✔
51
            else:
52
                half_threshold = MAX_SNIPPET_LINES // 2
20✔
53

54
                for line in range(start_line, start_line + half_threshold):
20✔
55
                    yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20✔
56

57
                if end_line - start_line > MAX_SNIPPET_LINES:
20✔
58
                    yield ("", slice(None, None), LineType.OTHER, "...")
20✔
59

60
                for line in range(end_line - half_threshold + 1, end_line + 1):
20✔
61
                    yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20✔
62

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

66
    else:
67
        line = msg.line
20✔
68
        yield from render_context(line - 2, line, source_lines)
20✔
69
        yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20✔
70
        yield from render_context(line + 1, line + 3, source_lines)
20✔
71

72

73
def render_missing_docstring(_msg, node, source_lines=None, config=None):
20✔
74
    """Render a missing docstring message."""
75
    if isinstance(node, nodes.Module):
20✔
76
        yield (None, slice(None, None), LineType.DOCSTRING, '"""YOUR DOCSTRING HERE"""')
20✔
77
        yield from render_context(1, 3, source_lines)
20✔
78
    elif isinstance(node, nodes.ClassDef) or isinstance(node, nodes.FunctionDef):
20✔
79
        start = node.fromlineno
20✔
80
        end = node.body[0].fromlineno
20✔
81
        yield from render_context(start, end, source_lines)
20✔
82
        # Calculate indentation
83
        body = source_lines[end - 1]
20✔
84
        indentation = len(body) - len(body.lstrip())
20✔
85
        yield (
20✔
86
            None,
87
            slice(None, None),
88
            LineType.DOCSTRING,
89
            body[:indentation] + '"""YOUR DOCSTRING HERE"""',
90
        )
91
        yield from render_context(end, end + 2, source_lines)
20✔
92

93

94
def render_line_too_long(msg, node, source_lines=None, config=None):
20✔
95
    """Render a line too long message."""
96
    line = msg.line
20✔
97

98
    # Set start_index to configured max-line-length and end_index to length of line
99
    start_index, end_index = config.max_line_length, len(source_lines[line - 1])
20✔
100

101
    yield from render_context(line - 2, line, source_lines)
20✔
102
    yield (line, slice(start_index, end_index), LineType.ERROR, source_lines[line - 1])
20✔
103
    yield from render_context(line + 1, line + 2, source_lines)
20✔
104

105

106
def render_trailing_newlines(msg, _node, source_lines=None, config=None):
20✔
107
    """Render a trailing newlines message."""
108
    # Get start of trailing newlines
109
    half_threshold = MAX_SNIPPET_LINES // 2
20✔
110
    total_lines = len(source_lines) + 1  # Accommodating for last blank line
20✔
111

112
    start_line = len(source_lines)
20✔
113
    while start_line > 0 and source_lines[start_line - 2].strip() == "":
20✔
114
        start_line -= 1
20✔
115

116
    num_trailing_newlines = total_lines - start_line
20✔
117

118
    yield from render_context(start_line - 2, start_line + 1, source_lines)
20✔
119

120
    for line in range(start_line, start_line + min(half_threshold, num_trailing_newlines - 1)):
20✔
121
        yield (
20✔
122
            line + 1,
123
            slice(None, None),
124
            LineType.ERROR,
125
            source_lines[line] + "# DELETE THIS LINE",
126
        )
127

128
    if num_trailing_newlines > MAX_SNIPPET_LINES:
20✔
129
        yield ("", slice(None, None), LineType.OTHER, "...")
20✔
130

131
    for line in range(
20✔
132
        total_lines - min(half_threshold, num_trailing_newlines - half_threshold),
133
        len(source_lines),
134
    ):
135
        yield (
20✔
136
            line + 1,
137
            slice(None, None),
138
            LineType.ERROR,
139
            source_lines[line] + "# DELETE THIS LINE",
140
        )
141

142
    # Accommodate for last blank line
143
    yield (total_lines, slice(None, None), LineType.ERROR, "# DELETE THIS LINE")
20✔
144

145

146
def render_trailing_whitespace(msg, _node, source_lines=None, config=None):
20✔
147
    """Render a trailing whitespace message."""
148
    line = msg.line
20✔
149
    start_index, end_index = len(source_lines[line - 1].rstrip()), len(source_lines[line - 1])
20✔
150
    yield from render_context(line - 1, line, source_lines)
20✔
151
    yield (line, slice(start_index, end_index), LineType.ERROR, source_lines[line - 1])
20✔
152
    yield from render_context(line + 1, line + 2, source_lines)
20✔
153

154

155
def render_context(start, stop, source_lines):
20✔
156
    """Helper for rendering context lines."""
157
    start, stop = max(start, 1), min(stop, len(source_lines))
20✔
158
    yield from (
20✔
159
        (line, slice(None, None), LineType.CONTEXT, source_lines[line - 1])
160
        for line in range(start, stop)
161
    )
162

163

164
def render_missing_return_type(_msg, node, source_lines=None, config=None):
20✔
165
    """Render a type annotation return message."""
166
    start_line, start_col = node.fromlineno, node.parent.col_offset
20✔
167
    end_line, end_col = node.end_lineno, node.end_col_offset
20✔
168

169
    # Display up to 2 lines before node for context:
170
    yield from render_context(start_line - 2, start_line, source_lines)
20✔
171
    yield from (
20✔
172
        (line, slice(None, end_col + 1), LineType.ERROR, source_lines[line - 1])
173
        for line in range(start_line, end_line + 1)
174
    )
175
    # Display up to 2 lines after node for context:
176
    yield from render_context(end_line + 1, end_line + 3, source_lines)
20✔
177

178

179
def render_too_many_arguments(msg, node, source_lines=None, config=None):
20✔
180
    """Render a too many arguments message."""
181
    # node is a FunctionDef node so replace it with its Arguments child
182
    yield from render_generic(msg, node.args, source_lines, config)
20✔
183

184

185
def render_missing_space_in_doctest(msg, _node, source_lines=None, config=None):
20✔
186
    """Render a missing space in doctest message"""
187
    line = msg.line
20✔
188

189
    # Display 2 lines before and after the erroneous line
190
    yield from render_context(line - 2, line, source_lines)
20✔
191
    yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20✔
192
    yield from render_context(line + 1, line + 3, source_lines)
20✔
193

194

195
def render_pep8_errors(msg, _node, source_lines=None, config=None):
20✔
196
    """Render a PEP8 error message."""
197
    # Extract the raw error message
198
    raw_msg = getattr(msg, "msg", "")
20✔
199

200
    # Search for the first appearance of the error code in the extracted error text
201
    matched_error = re.search(r"(E\d{3})", raw_msg)
20✔
202
    if matched_error:
20✔
203
        error_code = matched_error.group(1)
20✔
204
        # Render the appropriate error through the RENDERERS dict
205
        if error_code in RENDERERS:
20✔
206
            line = msg.line
20✔
207
            require_source_lines_renderers = {"E302", "E303", "E304", "E305"}
20✔
208
            if error_code in require_source_lines_renderers:
20✔
209
                yield from RENDERERS[error_code](msg, line, source_lines)
20✔
210
            else:
211
                col = msg.column
20✔
212
                yield from render_context(line - 3, line, source_lines)
20✔
213
                yield from RENDERERS[error_code](line, col, source_lines[line - 1])
20✔
214
                yield from render_context(line + 1, line + 3, source_lines)
20✔
215
            return
20✔
216

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

220

221
def render_blank_line(line):
20✔
222
    """Render a blank line for a PEP8 error message."""
223
    yield (line + 1, slice(None, None), LineType.ERROR, " " * 28)
20✔
224

225

226
def render_pep8_errors_e101_and_e123_and_e116(line, col, source_line=None):
20✔
227
    """Render a PEP8 indentation contains mixed spaces and tabs message
228
    AND a PEP8 closing bracket does not match indentation of opening bracket's line message."""
229
    curr_idx = len(source_line) - len(source_line.lstrip())
20✔
230
    yield (line, slice(0, curr_idx), LineType.ERROR, source_line)
20✔
231

232

233
def render_pep8_errors_e115(line, col, source_line=None):
20✔
234
    """Render a PEP8 expected an indented block (comment) message."""
235
    yield (
20✔
236
        line,
237
        slice(0, len(source_line)),
238
        LineType.ERROR,
239
        source_line + "  # INDENT THIS LINE",
240
    )
241

242

243
def render_pep8_errors_e122_and_e127_and_e131(line, col, source_line=None):
20✔
244
    """
245
    Render a PEP8 continuation line missing indentation or outdented message, a line over-indented for visual indent
246
    message, and a continuation line unaligned for hanging indent message.
247
    """
248
    curr_line_start_index = len(source_line) - len(source_line.lstrip())
20✔
249
    end_index = curr_line_start_index if curr_line_start_index > 0 else len(source_line)
20✔
250
    yield (
20✔
251
        line,
252
        slice(0, end_index),
253
        LineType.ERROR,
254
        source_line,
255
    )
256

257

258
def render_pep8_errors_e124(line, col, source_line=None):
20✔
259
    """Render a PEP8 closing bracket does not match visual indentation message."""
260
    yield (line, slice(col, col + 1), LineType.ERROR, source_line)
20✔
261

262

263
def render_pep8_errors_e125_and_e129(line, col, source_line=None):
20✔
264
    """Render a PEP8 continuation line with same indent as next logical line message
265
    AND a PEP8 visually indented line with same indent as next logical line messsage"""
266
    curr_idx = len(source_line) - len(source_line.lstrip())
20✔
267
    yield (
20✔
268
        line,
269
        slice(curr_idx, len(source_line)),
270
        LineType.ERROR,
271
        source_line + " " * 2 + "# INDENT THIS LINE",
272
    )
273

274

275
def render_pep8_errors_e128(line, col, source_line):
20✔
276
    """Render a PEP8 continuation line under-indented for visual indent message."""
277
    yield (line, slice(0, col if col != 0 else None), LineType.ERROR, source_line)
20✔
278

279

280
def render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272(line, col, source_line=None):
20✔
281
    """Render a PEP8 whitespace after '(' message,
282
    a PEP8 whitespace before ')' message,
283
    a PEP8 whitespace before ‘,’, ‘;’, or ‘:’ message,
284
    a PEP8 whitespace before '(' message,
285
    a PEP8 multiple spaces before operator message,
286
    a PEP8 multiple spaces after keyword message,
287
    a PEP8 multiple spaces before keyword message
288
    and a PEP8 multiple spaces after operator message."""
289
    curr_idx = col + len(source_line[col:]) - len(source_line[col:].lstrip())
20✔
290

291
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20✔
292

293

294
def render_pep8_errors_e204(line, col, source_line=None):
20✔
295
    """Render a PEP8 whitespace after decorator '@' message"""
296
    # calculates the length of the leading whitespaces by subtracting the length of everything after the first character after stripping all leading whitespaces from the total line length
297
    curr_idx = col + len(source_line[col:]) - len(source_line[col + 1 :].lstrip())
20✔
298

299
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20✔
300

301

302
def render_pep8_errors_e223_and_e274(line, col, source_line=None):
20✔
303
    """Render a PEP8 tab before operator message and a PEP8 tab before keyword message."""
304
    curr_idx = col + len(source_line[col:]) - len(source_line[col:].lstrip("\t"))
20✔
305

306
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20✔
307

308

309
def render_pep8_errors_e224_and_e273(line, col, source_line):
20✔
310
    """Render a PEP8 tab after operator message and a PEP8 tab after keyword message."""
311
    curr_idx = col + len(source_line[col:]) - len(source_line[col:].lstrip("\t"))
20✔
312

313
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20✔
314

315

316
def render_pep8_errors_e225(line, col, source_line):
20✔
317
    """Render a PEP8 missing whitespace around operator message"""
318
    curr_idx = col + 1
20✔
319

320
    two_char_operators = {
20✔
321
        "==",
322
        ">=",
323
        "<=",
324
        "!=",
325
        ":=",
326
        "&=",
327
        "->",
328
        "%=",
329
        "/=",
330
        "+=",
331
        "-=",
332
        "*=",
333
        "|=",
334
        "^=",
335
        "@=",
336
    }
337
    three_char_operators = {"//=", ">>=", "<<=", "**="}
20✔
338
    # highlight multiple characters for operators that are longer than one character
339
    if source_line[col : col + 2] in two_char_operators:
20✔
340
        curr_idx += 1
20✔
341
    elif source_line[col : col + 3] in three_char_operators:
20✔
342
        curr_idx += 2
20✔
343

344
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20✔
345

346

347
def render_pep8_errors_e226(line, col, source_line):
20✔
348
    """Render a PEP8 missing whitespace around arithmetic operator message"""
349
    end_idx = col + 1
20✔
350

351
    multi_char_operators = {"//"}
20✔
352
    # highlight multiple characters for arithmetic operators that are longer than one character
353
    if source_line[col : col + 2] in multi_char_operators:
20✔
354
        end_idx += 1
20✔
355

356
    yield (line, slice(col, end_idx), LineType.ERROR, source_line)
20✔
357

358

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

368
    yield (line, slice(col, end_idx), LineType.ERROR, source_line)
20✔
369

370

371
def render_pep8_errors_e228(line, col, source_line=None):
20✔
372
    """Render a PEP8 missing whitespace around modulo operator message."""
373
    yield (
20✔
374
        line,
375
        slice(col, col + 1),
376
        LineType.ERROR,
377
        source_line + "  # INSERT A SPACE BEFORE AND AFTER THE % OPERATOR",
378
    )
379

380

381
def render_pep8_errors_e231(line, col, source_line=None):
20✔
382
    curr_idx = col + 1
20✔
383

384
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20✔
385

386

387
def render_pep8_errors_e251(line, col, source_line=None):
20✔
388
    """Render a PEP8 unexpected spaces around keyword / parameter equals message."""
389
    equals_sign_idx = source_line[col:].find("=")
20✔
390
    code = source_line[col : col + equals_sign_idx if equals_sign_idx != -1 else None]
20✔
391
    end_idx = col + len(code) - len(code.lstrip())
20✔
392

393
    yield (line, slice(col, end_idx), LineType.ERROR, source_line)
20✔
394

395

396
def render_pep8_errors_e261(line, col, source_line=None):
20✔
397
    """Render a PEP8 at least two spaces before inline comment message."""
398
    yield (
20✔
399
        line,
400
        slice(col, len(source_line)),
401
        LineType.ERROR,
402
        source_line + "  # INSERT TWO SPACES BEFORE THE '#'",
403
    )
404

405

406
def render_pep8_errors_e262(line, col, source_line=None):
20✔
407
    """Render a PEP8 inline comment should start with '# ' message"""
408
    keyword_idx = len(source_line) - len(source_line[col:].lstrip("# \t"))
20✔
409

410
    yield (line, slice(col, keyword_idx), LineType.ERROR, source_line)
20✔
411

412

413
def render_pep8_errors_e265(line, col, source_line=None):
20✔
414
    """Render a PEP8 block comment should start with '# ' message."""
415
    yield (
20✔
416
        line,
417
        slice(0, len(source_line)),
418
        LineType.ERROR,
419
        source_line + "  # INSERT SPACE AFTER THE '#'",
420
    )
421

422

423
def render_pep8_errors_e266(line, col, source_line=None):
20✔
424
    """Render a PEP8 too many leading ‘#’ for block comment message."""
425
    curr_idx = col + len(source_line[col:]) - len(source_line[col:].lstrip("#"))
20✔
426

427
    yield (
20✔
428
        line,
429
        slice(col, curr_idx),
430
        LineType.ERROR,
431
        source_line + "  # THERE SHOULD ONLY BE ONE '#'",
432
    )
433

434

435
def render_pep8_errors_e275(line, col, source_line=None):
20✔
436
    """Render a PEP8 missing whitespace after keyword message."""
437
    # Get the range for highlighting the corresponding keyword.
438
    keyword = source_line[:col].split()[-1]
20✔
439
    keyword_idx = source_line.index(keyword)
20✔
440

441
    yield (
20✔
442
        line,
443
        slice(keyword_idx, col),
444
        LineType.ERROR,
445
        source_line + "  # INSERT SPACE AFTER KEYWORD",
446
    )
447

448

449
def render_pep8_errors_e301(line, col, source_line=None):
20✔
450
    """Render a PEP8 expected 1 blank line message."""
451
    indentation = len(source_line) - len(source_line.lstrip())
20✔
452
    yield (
20✔
453
        None,
454
        slice(None, None),
455
        LineType.ERROR,
456
        source_line[:indentation] + NEW_BLANK_LINE_MESSAGE,
457
    )
458

459

460
def render_pep8_errors_e302(msg, line, source_lines=None):
20✔
461
    """Render a PEP8 expected 2 blank lines message."""
462
    if "found 0" in msg.msg:
20✔
463
        yield from render_context(line - 3, line, source_lines)
20✔
464
        yield from (
20✔
465
            (
466
                None,
467
                slice(None, None),
468
                LineType.ERROR,
469
                NEW_BLANK_LINE_MESSAGE,
470
            )
471
            for _ in range(0, 2)
472
        )
473
    else:
474
        yield from render_context(line - 3, line - 1, source_lines)
20✔
475
        yield from render_blank_line(line)
20✔
476
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
20✔
477
    yield from render_context(line, line + 3, source_lines)
20✔
478

479

480
def render_pep8_errors_e303_and_e304(msg, line, source_lines=None):
20✔
481
    """Render a PEP8 too many blank lines message
482
    and a PEP8 blank lines found after function decorator message
483
    """
484
    half_threshold = MAX_SNIPPET_LINES // 2
20✔
485

486
    dline = line
20✔
487
    while source_lines[dline - 2].strip() == "":
20✔
488
        dline -= 1
20✔
489

490
    end_line = line - 1
20✔
491
    # Determine which PEP8 error we are rendering; adjust first offending blank line and last context line indices accordingly
492
    if "@" in source_lines[dline - 2]:
20✔
493
        # E304 Case: First blank line should be included in ERROR lines, and excluded from Context lines
494
        num_blank_lines = end_line - dline + 1
20✔
495
        end_context = dline
20✔
496
    else:
497
        # E304 Case: First blank line should be excluded from ERROR lines, and included as a Context line
498
        num_blank_lines = end_line - dline
20✔
499
        end_context = dline + 1
20✔
500

501
    yield from render_context(dline - 3, end_context, source_lines)
20✔
502

503
    for curr_line in range(dline, dline + min(half_threshold, num_blank_lines)):
20✔
504
        yield (curr_line + 1, slice(None, None), LineType.ERROR, "# DELETE THIS LINE")
20✔
505

506
    if num_blank_lines > MAX_SNIPPET_LINES:
20✔
507
        yield ("", slice(None, None), LineType.OTHER, "...")
20✔
508

509
    for curr_line in range(
20✔
510
        end_line - min(half_threshold, num_blank_lines - half_threshold),
511
        end_line,
512
    ):
513
        yield (curr_line + 1, slice(None, None), LineType.ERROR, "# DELETE THIS LINE")
20✔
514

515
    yield from render_context(line, line + 3, source_lines)
20✔
516

517

518
def render_pep8_errors_e305(msg, line, source_lines=None):
20✔
519
    """Render a PEP8 expected 2 blank lines after class or function definition message."""
520
    if "found 0" in msg.msg:
20✔
521
        yield from render_context(line - 3, line, source_lines)
20✔
522
        yield from (
20✔
523
            (
524
                None,
525
                slice(None, None),
526
                LineType.ERROR,
527
                NEW_BLANK_LINE_MESSAGE,
528
            )
529
            for _ in range(0, 2)
530
        )
531
    else:
532
        yield from render_context(line - 3, line - 1, source_lines)
20✔
533
        yield from render_blank_line(line)
20✔
534
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
20✔
535
    yield from render_context(line, line + 3, source_lines)
20✔
536

537

538
def render_pep8_errors_e306(line, col, source_line=None):
20✔
539
    """Render a PEP8 expected 1 blank line before a nested definition message."""
540
    indentation = len(source_line) - len(source_line.lstrip())
20✔
541
    yield (
20✔
542
        None,
543
        slice(None, None),
544
        LineType.ERROR,
545
        source_line[:indentation] + NEW_BLANK_LINE_MESSAGE,
546
    )
547

548

549
def render_pep8_errors_e502(line, col, source_line=None):
20✔
550
    """Render a PEP8 the backslash is redundant between brackets."""
551
    yield (line, slice(col, col + 1), LineType.ERROR, source_line)
20✔
552

553

554
def render_missing_return_statement(msg, node, source_lines=None, config=None):
20✔
555
    """
556
    Render a missing return statements message
557
    """
558
    yield from render_context(msg.line, msg.end_line + 1, source_lines)
20✔
559

560
    # calculate indentation for the insertion point
561
    body = source_lines[msg.end_line - 1]
20✔
562
    indentation = len(source_lines[msg.line - 1]) - len(source_lines[msg.line - 1].lstrip())
20✔
563

564
    # determine whether reaching the end of function
565
    first_statement_line = node.end_lineno if len(node.body) == 0 else node.body[0].lineno
20✔
566
    function_indentation = len(source_lines[first_statement_line - 1]) - len(
20✔
567
        source_lines[first_statement_line - 1].lstrip()
568
    )
569

570
    if msg.end_line == node.end_lineno and indentation == function_indentation:
20✔
571
        insertion_text = body[:indentation] + "# INSERT RETURN STATEMENT HERE"
20✔
572
    else:
UNCOV
573
        insertion_text = body[:indentation] + "# INSERT RETURN STATEMENT HERE (OR BELOW)"
×
574

575
    # insert the message
576
    yield (
20✔
577
        None,
578
        slice(indentation, None),
579
        LineType.ERROR,
580
        insertion_text,
581
    )
582

583
    yield from render_context(msg.end_line + 1, msg.end_line + 3, source_lines)
20✔
584

585

586
def render_static_type_checker_errors(msg, _node=None, source_lines=None, config=None):
20✔
587
    """Render a message for incompatible argument types."""
588
    start_line = msg.line
20✔
589
    start_col = msg.column
20✔
590
    end_line = msg.end_line
20✔
591
    end_col = msg.end_column
20✔
592
    yield from render_context(start_line - 2, start_line, source_lines)
20✔
593

594
    if start_line == end_line:
20✔
595
        yield (
20✔
596
            start_line,
597
            slice(start_col - 1, end_col),
598
            LineType.ERROR,
599
            source_lines[start_line - 1],
600
        )
601
    else:
602
        yield (start_line, slice(start_col - 1, None), LineType.ERROR, source_lines[start_line - 1])
20✔
603
        yield from (
20✔
604
            (line, slice(None, None), LineType.ERROR, source_lines)
605
            for line in range(start_line + 1, end_line)
606
        )
607
        yield (end_line, slice(None, end_col), LineType.ERROR, source_lines[end_line - 1])
20✔
608
    yield from render_context(end_line + 1, end_line + 3, source_lines)
20✔
609

610

611
CUSTOM_MESSAGES = {
20✔
612
    "missing-module-docstring": render_missing_docstring,
613
    "missing-class-docstring": render_missing_docstring,
614
    "missing-function-docstring": render_missing_docstring,
615
    "line-too-long": render_line_too_long,
616
    "trailing-newlines": render_trailing_newlines,
617
    "trailing-whitespace": render_trailing_whitespace,
618
    "missing-return-type": render_missing_return_type,
619
    "too-many-arguments": render_too_many_arguments,
620
    "missing-space-in-doctest": render_missing_space_in_doctest,
621
    "pep8-errors": render_pep8_errors,
622
    "missing-return-statement": render_missing_return_statement,
623
    "incompatible-argument-type": render_static_type_checker_errors,
624
    "incompatible-assignment": render_static_type_checker_errors,
625
    "list-item-type-mismatch": render_static_type_checker_errors,
626
    "unsupported-operand-types": render_static_type_checker_errors,
627
    "union-attr-error": render_static_type_checker_errors,
628
    "dict-item-type-mismatch": render_static_type_checker_errors,
629
}
630

631
RENDERERS = {
20✔
632
    "E101": render_pep8_errors_e101_and_e123_and_e116,
633
    "E123": render_pep8_errors_e101_and_e123_and_e116,
634
    "E115": render_pep8_errors_e115,
635
    "E116": render_pep8_errors_e101_and_e123_and_e116,
636
    "E122": render_pep8_errors_e122_and_e127_and_e131,
637
    "E127": render_pep8_errors_e122_and_e127_and_e131,
638
    "E131": render_pep8_errors_e122_and_e127_and_e131,
639
    "E124": render_pep8_errors_e124,
640
    "E125": render_pep8_errors_e125_and_e129,
641
    "E129": render_pep8_errors_e125_and_e129,
642
    "E128": render_pep8_errors_e128,
643
    "E201": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
644
    "E202": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
645
    "E203": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
646
    "E204": render_pep8_errors_e204,
647
    "E211": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
648
    "E221": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
649
    "E222": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
650
    "E223": render_pep8_errors_e223_and_e274,
651
    "E224": render_pep8_errors_e224_and_e273,
652
    "E225": render_pep8_errors_e225,
653
    "E231": render_pep8_errors_e231,
654
    "E273": render_pep8_errors_e224_and_e273,
655
    "E274": render_pep8_errors_e223_and_e274,
656
    "E226": render_pep8_errors_e226,
657
    "E227": render_pep8_errors_e227,
658
    "E228": render_pep8_errors_e228,
659
    "E251": render_pep8_errors_e251,
660
    "E261": render_pep8_errors_e261,
661
    "E262": render_pep8_errors_e262,
662
    "E265": render_pep8_errors_e265,
663
    "E266": render_pep8_errors_e266,
664
    "E271": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
665
    "E272": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
666
    "E275": render_pep8_errors_e275,
667
    "E301": render_pep8_errors_e301,
668
    "E302": render_pep8_errors_e302,
669
    "E303": render_pep8_errors_e303_and_e304,
670
    "E304": render_pep8_errors_e303_and_e304,
671
    "E305": render_pep8_errors_e305,
672
    "E306": render_pep8_errors_e306,
673
    "E502": render_pep8_errors_e502,
674
}
675

676

677
class LineType(Enum):
20✔
678
    """An enumeration for _add_line method line types."""
679

680
    ERROR = 1  # line with error
20✔
681
    CONTEXT = 2  # non-error/other line added for context
20✔
682
    OTHER = 3  # line included in source but not error
20✔
683
    ELLIPSIS = 5  # code replaced with ellipsis
20✔
684
    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