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

pyta-uoft / pyta / 36646375051

29 Sep 2026 11:39PM UTC coverage: 90.867% (-0.06%) from 90.925%
36646375051

push

github

web-flow
Added type annotations and fixed mypy errors in python-ta package (#1397)

256 of 269 new or added lines in 33 files covered. (95.17%)

2 existing lines in 2 files now uncovered.

3761 of 4139 relevant lines covered (90.87%)

17.67 hits per line

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

99.22
/packages/python-ta/src/python_ta/reporters/node_printers.py
1
"""Specify how errors should be rendered."""
2

3
from __future__ import annotations
20 ✔
4

5
import re
20 ✔
6
from enum import Enum
20 ✔
7
from typing import TYPE_CHECKING, Any, Callable, Generator
20 ✔
8

9
from astroid import nodes
20 ✔
10

11
if TYPE_CHECKING:
12
    from python_ta.reporters.core import NewMessage
13

14
NEW_BLANK_LINE_MESSAGE = "# INSERT NEW BLANK LINE HERE"
20 ✔
15
MAX_SNIPPET_LINES = 10
20 ✔
16

17

18
def render_message(
20 ✔
19
    msg: NewMessage,
20
    node: nodes.NodeNG | None,
21
    source_lines: list[str],
22
    config: Any | None = None,
23
) -> Generator[RenderResult, None, None]:
24
    """Render a message based on type."""
25
    renderer = CUSTOM_MESSAGES.get(msg.symbol, render_generic)
20 ✔
26
    yield from renderer(msg, node, source_lines, config)
20 ✔
27

28

29
def render_generic(
20 ✔
30
    msg: NewMessage,
31
    node: nodes.NodeNG | None,
32
    source_lines: list[str],
33
    config: Any | None = None,
34
) -> Generator[RenderResult, None, None]:
35
    """Default rendering for a message."""
36
    if node is not None:
20 ✔
37
        start_line, start_col = node.fromlineno, node.col_offset
20 ✔
38

39
        if isinstance(node, (nodes.FunctionDef, nodes.ClassDef)):
20 ✔
40
            end_line, end_col = start_line, None
20 ✔
41
        else:
42
            end_line, end_col = node.end_lineno, node.end_col_offset
20 ✔
43

44
        # Display up to 2 lines before node for context:
45
        yield from render_context(start_line - 2, start_line, source_lines)
20 ✔
46

47
        if start_line == end_line:
20 ✔
48
            yield (
20 ✔
49
                start_line,
50
                slice(start_col, end_col),
51
                LineType.ERROR,
52
                source_lines[start_line - 1],
53
            )
54
        else:
55
            num_lines = end_line - start_line
20 ✔
56
            half_threshold = MAX_SNIPPET_LINES // 2
20 ✔
57
            yield (
20 ✔
58
                start_line,
59
                slice(start_col, None),
60
                LineType.ERROR,
61
                source_lines[start_line - 1],
62
            )
63
            for line in range(start_line + 1, start_line + min(half_threshold, num_lines)):
20 ✔
64
                yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20 ✔
65

66
            if end_line - start_line > MAX_SNIPPET_LINES:
20 ✔
67
                yield ("", slice(None, None), LineType.OTHER, "...")
20 ✔
68

69
            for line in range(
20 ✔
70
                end_line - min(half_threshold, num_lines - half_threshold) + 1, end_line
71
            ):
72
                yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20 ✔
73

74
            yield (
20 ✔
75
                end_line,
76
                slice(None, end_col),
77
                LineType.ERROR,
78
                source_lines[end_line - 1],
79
            )
80

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

84
    else:
85
        line = msg.line
20 ✔
86
        yield from render_context(line - 2, line, source_lines)
20 ✔
87
        yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20 ✔
88
        yield from render_context(line + 1, line + 3, source_lines)
20 ✔
89

90

91
def render_missing_docstring(
20 ✔
92
    _msg: NewMessage,
93
    node: nodes.NodeNG | None,
94
    source_lines: list[str],
95
    config: Any | None = None,
96
) -> Generator[RenderResult, None, None]:
97
    """Render a missing docstring message."""
98
    if isinstance(node, nodes.Module):
20 ✔
99
        yield (None, slice(None, None), LineType.DOCSTRING, '"""YOUR DOCSTRING HERE"""')
20 ✔
100
        yield from render_context(1, 3, source_lines)
20 ✔
101
    elif isinstance(node, nodes.ClassDef) or isinstance(node, nodes.FunctionDef):
20 ✔
102
        start = node.fromlineno
20 ✔
103
        end = node.body[0].fromlineno
20 ✔
104
        yield from render_context(start, end, source_lines)
20 ✔
105
        # Calculate indentation
106
        body = source_lines[end - 1]
20 ✔
107
        indentation = len(body) - len(body.lstrip())
20 ✔
108
        yield (
20 ✔
109
            None,
110
            slice(None, None),
111
            LineType.DOCSTRING,
112
            body[:indentation] + '"""YOUR DOCSTRING HERE"""',
113
        )
114
        yield from render_context(end, end + 2, source_lines)
20 ✔
115

116

117
def render_line_too_long(
20 ✔
118
    msg: NewMessage,
119
    node: nodes.NodeNG | None,
120
    source_lines: list[str],
121
    config: Any | None = None,
122
) -> Generator[RenderResult, None, None]:
123
    """Render a line too long message."""
124
    line = msg.line
20 ✔
125

126
    # Set start_index to configured max-line-length and end_index to length of line
127
    if isinstance(config, dict):
20 ✔
NEW
128
        max_line_length = config.get("max_line_length", 79)
×
129
    else:
130
        max_line_length = getattr(config, "max_line_length", 79)
20 ✔
131
    start_index, end_index = max_line_length, len(source_lines[line - 1])
20 ✔
132

133
    yield from render_context(line - 2, line, source_lines)
20 ✔
134
    yield (line, slice(start_index, end_index), LineType.ERROR, source_lines[line - 1])
20 ✔
135
    yield from render_context(line + 1, line + 2, source_lines)
20 ✔
136

137

138
def render_trailing_newlines(
20 ✔
139
    msg: NewMessage,
140
    _node: nodes.NodeNG | None,
141
    source_lines: list[str],
142
    config: Any | None = None,
143
) -> Generator[RenderResult, None, None]:
144
    """Render a trailing newlines message."""
145
    # Get start of trailing newlines
146
    half_threshold = MAX_SNIPPET_LINES // 2
20 ✔
147
    total_lines = len(source_lines) + 1  # Accommodating for last blank line
20 ✔
148

149
    start_line = len(source_lines)
20 ✔
150
    while start_line > 0 and source_lines[start_line - 2].strip() == "":
20 ✔
151
        start_line -= 1
20 ✔
152

153
    num_trailing_newlines = total_lines - start_line
20 ✔
154

155
    yield from render_context(start_line - 2, start_line + 1, source_lines)
20 ✔
156

157
    for line in range(start_line, start_line + min(half_threshold, num_trailing_newlines - 1)):
20 ✔
158
        yield (
20 ✔
159
            line + 1,
160
            slice(None, None),
161
            LineType.ERROR,
162
            source_lines[line] + "# DELETE THIS LINE",
163
        )
164

165
    if num_trailing_newlines > MAX_SNIPPET_LINES:
20 ✔
166
        yield ("", slice(None, None), LineType.OTHER, "...")
20 ✔
167

168
    for line in range(
20 ✔
169
        total_lines - min(half_threshold, num_trailing_newlines - half_threshold),
170
        len(source_lines),
171
    ):
172
        yield (
20 ✔
173
            line + 1,
174
            slice(None, None),
175
            LineType.ERROR,
176
            source_lines[line] + "# DELETE THIS LINE",
177
        )
178

179
    # Accommodate for last blank line
180
    yield (total_lines, slice(None, None), LineType.ERROR, "# DELETE THIS LINE")
20 ✔
181

182

183
def render_trailing_whitespace(
20 ✔
184
    msg: NewMessage,
185
    _node: nodes.NodeNG | None,
186
    source_lines: list[str],
187
    config: Any | None = None,
188
) -> Generator[RenderResult, None, None]:
189
    """Render a trailing whitespace message."""
190
    line = msg.line
20 ✔
191
    start_index, end_index = len(source_lines[line - 1].rstrip()), len(source_lines[line - 1])
20 ✔
192
    yield from render_context(line - 1, line, source_lines)
20 ✔
193
    yield (line, slice(start_index, end_index), LineType.ERROR, source_lines[line - 1])
20 ✔
194
    yield from render_context(line + 1, line + 2, source_lines)
20 ✔
195

196

197
def render_context(
20 ✔
198
    start: int,
199
    stop: int,
200
    source_lines: list[str],
201
) -> Generator[tuple[int, slice[int | None, int | None, int | None], LineType, str], None, None]:
202
    """Helper for rendering context lines."""
203
    start, stop = max(start, 1), min(stop, len(source_lines))
20 ✔
204
    yield from (
20 ✔
205
        (line, slice(None, None), LineType.CONTEXT, source_lines[line - 1])
206
        for line in range(start, stop)
207
    )
208

209

210
def render_missing_return_type(
20 ✔
211
    _msg: NewMessage,
212
    node: nodes.FunctionDef,
213
    source_lines: list[str],
214
    config: Any | None = None,
215
) -> Generator[tuple[int, slice[int | None, int | None, int | None], LineType, str], None, None]:
216
    """Render a type annotation return message."""
217
    start_line, start_col = node.fromlineno, node.parent.col_offset
20 ✔
218
    end_line, end_col = node.end_lineno, node.end_col_offset
20 ✔
219

220
    # Display up to 2 lines before node for context:
221
    yield from render_context(start_line - 2, start_line, source_lines)
20 ✔
222
    yield from (
20 ✔
223
        (line, slice(None, end_col + 1), LineType.ERROR, source_lines[line - 1])
224
        for line in range(start_line, end_line + 1)
225
    )
226
    # Display up to 2 lines after node for context:
227
    yield from render_context(end_line + 1, end_line + 3, source_lines)
20 ✔
228

229

230
def render_too_many_arguments(
20 ✔
231
    msg: NewMessage,
232
    node: nodes.FunctionDef,
233
    source_lines: list[str],
234
    config: Any | None = None,
235
) -> Generator[RenderResult, None, None]:
236
    """Render a too many arguments message."""
237
    # node is a FunctionDef node so replace it with its Arguments child
238
    yield from render_generic(msg, node.args, source_lines, config)
20 ✔
239

240

241
def render_missing_space_in_doctest(
20 ✔
242
    msg: NewMessage,
243
    _node: nodes.NodeNG,
244
    source_lines: list[str],
245
    config: Any | None = None,
246
) -> Generator[RenderResult, None, None]:
247
    """Render a missing space in doctest message"""
248
    line = msg.line
20 ✔
249

250
    # Display 2 lines before and after the erroneous line
251
    yield from render_context(line - 2, line, source_lines)
20 ✔
252
    yield (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
20 ✔
253
    yield from render_context(line + 1, line + 3, source_lines)
20 ✔
254

255

256
def render_pep8_errors(
20 ✔
257
    msg: NewMessage,
258
    _node: nodes.NodeNG,
259
    source_lines: list[str],
260
    config: Any | None = None,
261
) -> Generator[RenderResult, None, None]:
262
    """Render a PEP8 error message."""
263
    # Extract the raw error message
264
    raw_msg = getattr(msg, "msg", "")
20 ✔
265

266
    # Search for the first appearance of the error code in the extracted error text
267
    matched_error = re.search(r"(E\d{3})", raw_msg)
20 ✔
268
    if matched_error:
20 ✔
269
        error_code = matched_error.group(1)
20 ✔
270
        # Render the appropriate error through the RENDERERS dict
271
        if error_code in RENDERERS:
20 ✔
272
            line = msg.line
20 ✔
273
            require_source_lines_renderers = {"E302", "E303", "E304", "E305"}
20 ✔
274
            if error_code in require_source_lines_renderers:
20 ✔
275
                yield from RENDERERS[error_code](msg, line, source_lines)
20 ✔
276
            else:
277
                col = msg.column
20 ✔
278
                yield from render_context(line - 3, line, source_lines)
20 ✔
279
                yield from RENDERERS[error_code](line, col, source_lines[line - 1])
20 ✔
280
                yield from render_context(line + 1, line + 3, source_lines)
20 ✔
281
            return
20 ✔
282

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

286

287
def render_blank_line(line: int) -> Generator[tuple[int, slice, LineType, str], None, None]:
20 ✔
288
    """Render a blank line for a PEP8 error message."""
289
    yield (line + 1, slice(None, None), LineType.ERROR, " " * 28)
20 ✔
290

291

292
def render_pep8_errors_e101_and_e123_and_e116(
20 ✔
293
    line: int,
294
    col: int,
295
    source_line: str,
296
) -> Generator[tuple[int, slice, LineType, str], None, None]:
297
    """Render a PEP8 indentation contains mixed spaces and tabs message
298
    AND a PEP8 closing bracket does not match indentation of opening bracket's line message.
299
    """
300
    curr_idx = len(source_line) - len(source_line.lstrip())
20 ✔
301
    yield (line, slice(0, curr_idx), LineType.ERROR, source_line)
20 ✔
302

303

304
def render_pep8_errors_e115(
20 ✔
305
    line: int,
306
    col: int,
307
    source_line: str,
308
) -> Generator[tuple[int, slice, LineType, str], None, None]:
309
    """Render a PEP8 expected an indented block (comment) message."""
310
    yield (
20 ✔
311
        line,
312
        slice(0, len(source_line)),
313
        LineType.ERROR,
314
        source_line + "  # INDENT THIS LINE",
315
    )
316

317

318
def render_pep8_errors_e122_and_e127_and_e131(
20 ✔
319
    line: int,
320
    col: int,
321
    source_line: str,
322
) -> Generator[tuple[int, slice, LineType, str], None, None]:
323
    """
324
    Render a PEP8 continuation line missing indentation or outdented message, a line over-indented for visual indent
325
    message, and a continuation line unaligned for hanging indent message.
326
    """
327
    curr_line_start_index = len(source_line) - len(source_line.lstrip())
20 ✔
328
    end_index = curr_line_start_index if curr_line_start_index > 0 else len(source_line)
20 ✔
329
    yield (
20 ✔
330
        line,
331
        slice(0, end_index),
332
        LineType.ERROR,
333
        source_line,
334
    )
335

336

337
def render_pep8_errors_e124(
20 ✔
338
    line: int,
339
    col: int,
340
    source_line: str,
341
) -> Generator[tuple[int, slice, LineType, str], None, None]:
342
    """Render a PEP8 closing bracket does not match visual indentation message."""
343
    yield (line, slice(col, col + 1), LineType.ERROR, source_line)
20 ✔
344

345

346
def render_pep8_errors_e125_and_e129(
20 ✔
347
    line: int,
348
    col: int,
349
    source_line: str,
350
) -> Generator[tuple[int, slice, LineType, str]]:
351
    """Render a PEP8 continuation line with same indent as next logical line message
352
    AND a PEP8 visually indented line with same indent as next logical line messsage"""
353
    curr_idx = len(source_line) - len(source_line.lstrip())
20 ✔
354
    yield (
20 ✔
355
        line,
356
        slice(curr_idx, len(source_line)),
357
        LineType.ERROR,
358
        source_line + " " * 2 + "# INDENT THIS LINE",
359
    )
360

361

362
def render_pep8_errors_e128(
20 ✔
363
    line: int,
364
    col: int,
365
    source_line: str,
366
) -> Generator[tuple[int, slice, LineType, str], None, None]:
367
    """Render a PEP8 continuation line under-indented for visual indent message."""
368
    yield (line, slice(0, col if col != 0 else None), LineType.ERROR, source_line)
20 ✔
369

370

371
def render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272(
20 ✔
372
    line: int,
373
    col: int,
374
    source_line: str,
375
) -> Generator[tuple[int, slice, LineType, str], None, None]:
376
    """Render a PEP8 whitespace after '(' message,
377
    a PEP8 whitespace before ')' message,
378
    a PEP8 whitespace before ‘,’, ‘;’, or ‘:’ message,
379
    a PEP8 whitespace before '(' message,
380
    a PEP8 multiple spaces before operator message,
381
    a PEP8 multiple spaces after keyword message,
382
    a PEP8 multiple spaces before keyword message
383
    and a PEP8 multiple spaces after operator message."""
384
    curr_idx = col + len(source_line[col:]) - len(source_line[col:].lstrip())
20 ✔
385

386
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20 ✔
387

388

389
def render_pep8_errors_e204(
20 ✔
390
    line: int,
391
    col: int,
392
    source_line: str,
393
) -> Generator[tuple[int, slice, LineType, str], None, None]:
394
    """Render a PEP8 whitespace after decorator '@' message"""
395
    # 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
396
    curr_idx = col + len(source_line[col:]) - len(source_line[col + 1 :].lstrip())
20 ✔
397

398
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20 ✔
399

400

401
def render_pep8_errors_e223_and_e274(
20 ✔
402
    line: int,
403
    col: int,
404
    source_line: str,
405
) -> Generator[tuple[int, slice, LineType, str], None, None]:
406
    """Render a PEP8 tab before operator message and a PEP8 tab before keyword message."""
407
    curr_idx = col + len(source_line[col:]) - len(source_line[col:].lstrip("\t"))
20 ✔
408

409
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20 ✔
410

411

412
def render_pep8_errors_e224_and_e273(
20 ✔
413
    line: int,
414
    col: int,
415
    source_line: str,
416
) -> Generator[tuple[int, slice, LineType, str], None, None]:
417
    """Render a PEP8 tab after operator message and a PEP8 tab after keyword message."""
418
    curr_idx = col + len(source_line[col:]) - len(source_line[col:].lstrip("\t"))
20 ✔
419

420
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20 ✔
421

422

423
def render_pep8_errors_e225(
20 ✔
424
    line: int,
425
    col: int,
426
    source_line: str,
427
) -> Generator[tuple[int, slice, LineType, str], None, None]:
428
    """Render a PEP8 missing whitespace around operator message"""
429
    curr_idx = col + 1
20 ✔
430

431
    two_char_operators = {
20 ✔
432
        "==",
433
        ">=",
434
        "<=",
435
        "!=",
436
        ":=",
437
        "&=",
438
        "->",
439
        "%=",
440
        "/=",
441
        "+=",
442
        "-=",
443
        "*=",
444
        "|=",
445
        "^=",
446
        "@=",
447
    }
448
    three_char_operators = {"//=", ">>=", "<<=", "**="}
20 ✔
449
    # highlight multiple characters for operators that are longer than one character
450
    if source_line[col : col + 2] in two_char_operators:
20 ✔
451
        curr_idx += 1
20 ✔
452
    elif source_line[col : col + 3] in three_char_operators:
20 ✔
453
        curr_idx += 2
20 ✔
454

455
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20 ✔
456

457

458
def render_pep8_errors_e226(
20 ✔
459
    line: int,
460
    col: int,
461
    source_line: str,
462
) -> Generator[tuple[int, slice, LineType, str], None, None]:
463
    """Render a PEP8 missing whitespace around arithmetic operator message"""
464
    end_idx = col + 1
20 ✔
465

466
    multi_char_operators = {"//"}
20 ✔
467
    # highlight multiple characters for arithmetic operators that are longer than one character
468
    if source_line[col : col + 2] in multi_char_operators:
20 ✔
469
        end_idx += 1
20 ✔
470

471
    yield (line, slice(col, end_idx), LineType.ERROR, source_line)
20 ✔
472

473

474
def render_pep8_errors_e227(
20 ✔
475
    line: int,
476
    col: int,
477
    source_line: str,
478
) -> Generator[tuple[int, slice, LineType, str], None, None]:
479
    """Render a PEP8 missing whitespace around bitwise or shift operator message."""
480
    # Check which operator to get the correct range of the line to highlight.
481
    # Default highlight is one character, but may be updated to two.
482
    # Note that only binary bitwise operators that are more than one character are included.
483
    operators = {">>", "<<"}
20 ✔
484
    end_idx = col + 1
20 ✔
485
    end_idx = end_idx + 1 if source_line[col : col + 2] in operators else end_idx
20 ✔
486

487
    yield (line, slice(col, end_idx), LineType.ERROR, source_line)
20 ✔
488

489

490
def render_pep8_errors_e228(
20 ✔
491
    line: int,
492
    col: int,
493
    source_line: str,
494
) -> Generator[tuple[int, slice, LineType, str], None, None]:
495
    """Render a PEP8 missing whitespace around modulo operator message."""
496
    yield (
20 ✔
497
        line,
498
        slice(col, col + 1),
499
        LineType.ERROR,
500
        source_line + "  # INSERT A SPACE BEFORE AND AFTER THE % OPERATOR",
501
    )
502

503

504
def render_pep8_errors_e231(
20 ✔
505
    line: int,
506
    col: int,
507
    source_line: str,
508
) -> Generator[tuple[int, slice, LineType, str], None, None]:
509
    curr_idx = col + 1
20 ✔
510

511
    yield (line, slice(col, curr_idx), LineType.ERROR, source_line)
20 ✔
512

513

514
def render_pep8_errors_e251(
20 ✔
515
    line: int,
516
    col: int,
517
    source_line: str,
518
) -> Generator[tuple[int, slice, LineType, str], None, None]:
519
    """Render a PEP8 unexpected spaces around keyword / parameter equals message."""
520
    equals_sign_idx = source_line[col:].find("=")
20 ✔
521
    code = source_line[col : col + equals_sign_idx if equals_sign_idx != -1 else None]
20 ✔
522
    end_idx = col + len(code) - len(code.lstrip())
20 ✔
523

524
    yield (line, slice(col, end_idx), LineType.ERROR, source_line)
20 ✔
525

526

527
def render_pep8_errors_e261(
20 ✔
528
    line: int,
529
    col: int,
530
    source_line: str,
531
) -> Generator[tuple[int, slice, LineType, str], None, None]:
532
    """Render a PEP8 at least two spaces before inline comment message."""
533
    yield (
20 ✔
534
        line,
535
        slice(col, len(source_line)),
536
        LineType.ERROR,
537
        source_line + "  # INSERT TWO SPACES BEFORE THE '#'",
538
    )
539

540

541
def render_pep8_errors_e262(
20 ✔
542
    line: int,
543
    col: int,
544
    source_line: str,
545
) -> Generator[tuple[int, slice, LineType, str], None, None]:
546
    """Render a PEP8 inline comment should start with '# ' message"""
547
    keyword_idx = len(source_line) - len(source_line[col:].lstrip("# \t"))
20 ✔
548

549
    yield (line, slice(col, keyword_idx), LineType.ERROR, source_line)
20 ✔
550

551

552
def render_pep8_errors_e265(
20 ✔
553
    line: int,
554
    col: int,
555
    source_line: str,
556
) -> Generator[tuple[int, slice, LineType, str], None, None]:
557
    """Render a PEP8 block comment should start with '# ' message."""
558
    yield (
20 ✔
559
        line,
560
        slice(0, len(source_line)),
561
        LineType.ERROR,
562
        source_line + "  # INSERT SPACE AFTER THE '#'",
563
    )
564

565

566
def render_pep8_errors_e266(
20 ✔
567
    line: int,
568
    col: int,
569
    source_line: str,
570
) -> Generator[tuple[int, slice, LineType, str], None, None]:
571
    """Render a PEP8 too many leading ‘#’ for block comment message."""
572
    curr_idx = col + len(source_line[col:]) - len(source_line[col:].lstrip("#"))
20 ✔
573

574
    yield (
20 ✔
575
        line,
576
        slice(col, curr_idx),
577
        LineType.ERROR,
578
        source_line + "  # THERE SHOULD ONLY BE ONE '#'",
579
    )
580

581

582
def render_pep8_errors_e275(
20 ✔
583
    line: int,
584
    col: int,
585
    source_line: str,
586
) -> Generator[tuple[int, slice, LineType, str], None, None]:
587
    """Render a PEP8 missing whitespace after keyword message."""
588
    # Get the range for highlighting the corresponding keyword.
589
    keyword = source_line[:col].split()[-1]
20 ✔
590
    keyword_idx = source_line.index(keyword)
20 ✔
591

592
    yield (
20 ✔
593
        line,
594
        slice(keyword_idx, col),
595
        LineType.ERROR,
596
        source_line + "  # INSERT SPACE AFTER KEYWORD",
597
    )
598

599

600
def render_pep8_errors_e301(
20 ✔
601
    line: int,
602
    col: int,
603
    source_line: str,
604
) -> Generator[tuple[int | None, slice, LineType, str], None, None]:
605
    """Render a PEP8 expected 1 blank line message."""
606
    indentation = len(source_line) - len(source_line.lstrip())
20 ✔
607
    yield (
20 ✔
608
        None,
609
        slice(None, None),
610
        LineType.ERROR,
611
        source_line[:indentation] + NEW_BLANK_LINE_MESSAGE,
612
    )
613

614

615
def render_pep8_errors_e302(
20 ✔
616
    msg: NewMessage,
617
    line: int,
618
    source_lines: list[str],
619
) -> Generator[tuple[int | None, slice, LineType, str], None, None]:
620
    """Render a PEP8 expected 2 blank lines message."""
621
    if "found 0" in msg.msg:
20 ✔
622
        yield from render_context(line - 3, line, source_lines)
20 ✔
623
        yield from (
20 ✔
624
            (
625
                None,
626
                slice(None, None),
627
                LineType.ERROR,
628
                NEW_BLANK_LINE_MESSAGE,
629
            )
630
            for _ in range(0, 2)
631
        )
632
    else:
633
        yield from render_context(line - 3, line - 1, source_lines)
20 ✔
634
        yield from render_blank_line(line)
20 ✔
635
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
20 ✔
636
    yield from render_context(line, line + 3, source_lines)
20 ✔
637

638

639
def render_pep8_errors_e303_and_e304(
20 ✔
640
    msg: NewMessage,
641
    line: int,
642
    source_lines: list[str],
643
) -> Generator[RenderResult, None, None]:
644
    """Render a PEP8 too many blank lines message
645
    and a PEP8 blank lines found after function decorator message
646
    """
647
    half_threshold = MAX_SNIPPET_LINES // 2
20 ✔
648

649
    dline = line
20 ✔
650
    while source_lines[dline - 2].strip() == "":
20 ✔
651
        dline -= 1
20 ✔
652

653
    end_line = line - 1
20 ✔
654
    # Determine which PEP8 error we are rendering; adjust first offending blank line and last context line indices accordingly
655
    if "@" in source_lines[dline - 2]:
20 ✔
656
        # E304 Case: First blank line should be included in ERROR lines, and excluded from Context lines
657
        num_blank_lines = end_line - dline + 1
20 ✔
658
        end_context = dline
20 ✔
659
    else:
660
        # E304 Case: First blank line should be excluded from ERROR lines, and included as a Context line
661
        num_blank_lines = end_line - dline
20 ✔
662
        end_context = dline + 1
20 ✔
663

664
    yield from render_context(dline - 3, end_context, source_lines)
20 ✔
665

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

669
    if num_blank_lines > MAX_SNIPPET_LINES:
20 ✔
670
        yield ("", slice(None, None), LineType.OTHER, "...")
20 ✔
671

672
    for curr_line in range(
20 ✔
673
        end_line - min(half_threshold, num_blank_lines - half_threshold),
674
        end_line,
675
    ):
676
        yield (curr_line + 1, slice(None, None), LineType.ERROR, "# DELETE THIS LINE")
20 ✔
677

678
    yield from render_context(line, line + 3, source_lines)
20 ✔
679

680

681
def render_pep8_errors_e305(
20 ✔
682
    msg: NewMessage,
683
    line: int,
684
    source_lines: list[str],
685
) -> Generator[tuple[int | None, slice, LineType, str], None, None]:
686
    """Render a PEP8 expected 2 blank lines after class or function definition message."""
687
    if "found 0" in msg.msg:
20 ✔
688
        yield from render_context(line - 3, line, source_lines)
20 ✔
689
        yield from (
20 ✔
690
            (
691
                None,
692
                slice(None, None),
693
                LineType.ERROR,
694
                NEW_BLANK_LINE_MESSAGE,
695
            )
696
            for _ in range(0, 2)
697
        )
698
    else:
699
        yield from render_context(line - 3, line - 1, source_lines)
20 ✔
700
        yield from render_blank_line(line)
20 ✔
701
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
20 ✔
702
    yield from render_context(line, line + 3, source_lines)
20 ✔
703

704

705
def render_pep8_errors_e306(
20 ✔
706
    line: int,
707
    col: int,
708
    source_line: str,
709
) -> Generator[tuple[int | None, slice, LineType, str], None, None]:
710
    """Render a PEP8 expected 1 blank line before a nested definition message."""
711
    indentation = len(source_line) - len(source_line.lstrip())
20 ✔
712
    yield (
20 ✔
713
        None,
714
        slice(None, None),
715
        LineType.ERROR,
716
        source_line[:indentation] + NEW_BLANK_LINE_MESSAGE,
717
    )
718

719

720
def render_pep8_errors_e502(
20 ✔
721
    line: int,
722
    col: int,
723
    source_line: str,
724
) -> Generator[tuple[int, slice, LineType, str], None, None]:
725
    """Render a PEP8 the backslash is redundant between brackets."""
726
    yield (line, slice(col, col + 1), LineType.ERROR, source_line)
20 ✔
727

728

729
def render_missing_return_statement(
20 ✔
730
    msg: NewMessage,
731
    node: nodes.NodeNG,
732
    source_lines: list[str],
733
    config: dict[str, Any] | None = None,
734
) -> Generator[tuple[int | None, slice, LineType, str], None, None]:
735
    """
736
    Render a missing return statements message
737
    """
738
    yield from render_context(msg.line, msg.end_line + 1, source_lines)
20 ✔
739

740
    # calculate indentation for the insertion point
741
    body = source_lines[msg.end_line - 1]
20 ✔
742
    indentation = len(source_lines[msg.line - 1]) - len(source_lines[msg.line - 1].lstrip())
20 ✔
743

744
    # determine whether reaching the end of function
745
    first_statement_line = node.end_lineno if len(node.body) == 0 else node.body[0].lineno
20 ✔
746
    function_indentation = len(source_lines[first_statement_line - 1]) - len(
20 ✔
747
        source_lines[first_statement_line - 1].lstrip()
748
    )
749

750
    if msg.end_line == node.end_lineno and indentation == function_indentation:
20 ✔
751
        insertion_text = body[:indentation] + "# INSERT RETURN STATEMENT HERE"
20 ✔
752
    else:
753
        insertion_text = body[:indentation] + "# INSERT RETURN STATEMENT HERE (OR BELOW)"
×
754

755
    # insert the message
756
    yield (
20 ✔
757
        None,
758
        slice(indentation, None),
759
        LineType.ERROR,
760
        insertion_text,
761
    )
762

763
    yield from render_context(msg.end_line + 1, msg.end_line + 3, source_lines)
20 ✔
764

765

766
def render_static_type_checker_errors(
20 ✔
767
    msg: NewMessage,
768
    _node: nodes.NodeNG | None = None,
769
    source_lines: list[str] = [],
770
    config: dict[str, Any] | None = None,
771
) -> Generator[tuple[int | None, slice, LineType, str], None, None]:
772
    """Render a message for incompatible argument types."""
773
    start_line = msg.line
20 ✔
774
    start_col = msg.column
20 ✔
775
    end_line = msg.end_line
20 ✔
776
    end_col = msg.end_column
20 ✔
777
    yield from render_context(start_line - 2, start_line, source_lines)
20 ✔
778

779
    if start_line == end_line:
20 ✔
780
        yield (
20 ✔
781
            start_line,
782
            slice(start_col - 1, end_col),
783
            LineType.ERROR,
784
            source_lines[start_line - 1],
785
        )
786
    else:
787
        yield (
20 ✔
788
            start_line,
789
            slice(start_col - 1, None),
790
            LineType.ERROR,
791
            source_lines[start_line - 1],
792
        )
793
        yield from (
20 ✔
794
            (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
795
            for line in range(start_line + 1, end_line)
796
        )
797
        yield (
20 ✔
798
            end_line,
799
            slice(None, end_col),
800
            LineType.ERROR,
801
            source_lines[end_line - 1],
802
        )
803
    yield from render_context(end_line + 1, end_line + 3, source_lines)
20 ✔
804

805

806
CUSTOM_MESSAGES: dict[str, Callable[..., Generator[RenderResult, None, None]]] = {
20 ✔
807
    "missing-module-docstring": render_missing_docstring,
808
    "missing-class-docstring": render_missing_docstring,
809
    "missing-function-docstring": render_missing_docstring,
810
    "line-too-long": render_line_too_long,
811
    "trailing-newlines": render_trailing_newlines,
812
    "trailing-whitespace": render_trailing_whitespace,
813
    "missing-return-type": render_missing_return_type,
814
    "too-many-arguments": render_too_many_arguments,
815
    "missing-space-in-doctest": render_missing_space_in_doctest,
816
    "pep8-errors": render_pep8_errors,
817
    "missing-return-statement": render_missing_return_statement,
818
    "incompatible-argument-type": render_static_type_checker_errors,
819
    "incompatible-assignment": render_static_type_checker_errors,
820
    "list-item-type-mismatch": render_static_type_checker_errors,
821
    "unsupported-operand-types": render_static_type_checker_errors,
822
    "union-attr-error": render_static_type_checker_errors,
823
    "dict-item-type-mismatch": render_static_type_checker_errors,
824
}
825

826
RENDERERS: dict[str, Callable[..., Generator[RenderResult, None, None]]] = {
20 ✔
827
    "E101": render_pep8_errors_e101_and_e123_and_e116,
828
    "E123": render_pep8_errors_e101_and_e123_and_e116,
829
    "E115": render_pep8_errors_e115,
830
    "E116": render_pep8_errors_e101_and_e123_and_e116,
831
    "E122": render_pep8_errors_e122_and_e127_and_e131,
832
    "E127": render_pep8_errors_e122_and_e127_and_e131,
833
    "E131": render_pep8_errors_e122_and_e127_and_e131,
834
    "E124": render_pep8_errors_e124,
835
    "E125": render_pep8_errors_e125_and_e129,
836
    "E129": render_pep8_errors_e125_and_e129,
837
    "E128": render_pep8_errors_e128,
838
    "E201": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
839
    "E202": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
840
    "E203": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
841
    "E204": render_pep8_errors_e204,
842
    "E211": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
843
    "E221": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
844
    "E222": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
845
    "E223": render_pep8_errors_e223_and_e274,
846
    "E224": render_pep8_errors_e224_and_e273,
847
    "E225": render_pep8_errors_e225,
848
    "E231": render_pep8_errors_e231,
849
    "E273": render_pep8_errors_e224_and_e273,
850
    "E274": render_pep8_errors_e223_and_e274,
851
    "E226": render_pep8_errors_e226,
852
    "E227": render_pep8_errors_e227,
853
    "E228": render_pep8_errors_e228,
854
    "E251": render_pep8_errors_e251,
855
    "E261": render_pep8_errors_e261,
856
    "E262": render_pep8_errors_e262,
857
    "E265": render_pep8_errors_e265,
858
    "E266": render_pep8_errors_e266,
859
    "E271": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
860
    "E272": render_pep8_errors_e201_e202_e203_e211_e221_e222_e271_e272,
861
    "E275": render_pep8_errors_e275,
862
    "E301": render_pep8_errors_e301,
863
    "E302": render_pep8_errors_e302,
864
    "E303": render_pep8_errors_e303_and_e304,
865
    "E304": render_pep8_errors_e303_and_e304,
866
    "E305": render_pep8_errors_e305,
867
    "E306": render_pep8_errors_e306,
868
    "E502": render_pep8_errors_e502,
869
}
870

871

872
class LineType(Enum):
20 ✔
873
    """An enumeration for _add_line method line types."""
874

875
    ERROR = 1  # line with error
20 ✔
876
    CONTEXT = 2  # non-error/other line added for context
20 ✔
877
    OTHER = 3  # line included in source but not error
20 ✔
878
    ELLIPSIS = 5  # code replaced with ellipsis
20 ✔
879
    DOCSTRING = 6  # docstring needed warning
20 ✔
880

881

882
RenderResult = tuple[int | str | None, slice, LineType, str]
20 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc