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

pyta-uoft / pyta / 15168562525

21 May 2025 05:21PM UTC coverage: 93.523% (+0.07%) from 93.449%
15168562525

Pull #1178

github

web-flow
Merge 5be71cb85 into 14869e4ad
Pull Request #1178: Refactor render pep8 errors mapping

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

26 existing lines in 1 file now uncovered.

3393 of 3628 relevant lines covered (93.52%)

17.7 hits per line

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

86.39
/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

138
    # Check if any error codes from the ERROR_TO_ERROR_FUNCTION_MAPPING dict are present in the message
139
    for error in ERROR_TO_ERROR_FUNCTION_MAPPING:
20✔
140
        # If an error code is present, yield and return
141
        if error in msg.msg:
20✔
142
            yield from ERROR_TO_ERROR_FUNCTION_MAPPING[error](msg, _node, source_lines)
20✔
143
            return
20✔
144

145
    # If none of the error codes were present, render a generic message
146
    yield from render_generic(msg, _node, source_lines)
20✔
147

148

149
def render_blank_line(line):
20✔
150
    """Render a blank line for a PEP8 error message."""
151
    yield (line + 1, slice(None, None), LineType.ERROR, " " * 28)
20✔
152

153

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

163

164
def render_pep8_errors_e115(msg, _node, source_lines=None):
20✔
165
    """Render a PEP8 expected an indented block (comment) message."""
166
    line = msg.line
20✔
167

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

177

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

191

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

209

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

219

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

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

235

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

242
    yield from render_context(line - 2, line, source_lines)
×
243
    yield (line, slice(0, col if col != 0 else None), LineType.ERROR, source_lines[line - 1])
×
244
    yield from render_context(line + 1, line + 3, source_lines)
×
245

246

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

257
    yield from render_context(line - 2, line, source_lines)
20✔
258
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
259
    yield from render_context(line + 1, line + 3, source_lines)
20✔
260

261

262
def render_pep8_errors_e221(msg, _node, source_lines=None):
20✔
263
    """Render a PEP8 multiple spaces before operator message."""
UNCOV
264
    line = msg.line
×
UNCOV
265
    res = re.search(r"column (\d+)", msg.msg)
×
UNCOV
266
    col = int(res.group().split()[-1])
×
UNCOV
267
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
×
268

269
    yield from render_context(line - 2, line, source_lines)
×
270
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
×
271
    yield from render_context(line + 1, line + 3, source_lines)
×
272

273

274
def render_pep8_errors_e222(msg, _node, source_lines=None):
20✔
275
    """Render a PEP8 multiple spaces after operator message"""
276
    line = msg.line
20✔
277
    res = re.search(r"column (\d+)", msg.msg)
20✔
278
    col = int(res.group().split()[-1])
20✔
279

280
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
20✔
281
    yield from render_context(line - 2, line, source_lines)
20✔
282
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
20✔
283
    yield from render_context(line + 1, line + 3, source_lines)
20✔
284

285

286
def render_pep8_errors_e223(msg, _node, source_lines=None):
20✔
287
    """Render a PEP8 tab before operator message."""
288
    line = msg.line
20✔
289
    res = re.search(r"column (\d+)", msg.msg)
20✔
290
    col = int(res.group().split()[-1])
20✔
291
    curr_idx = (
20✔
292
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("\t"))
293
    )
294

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

299

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

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

313

314
def render_pep8_errors_e226(msg, _node, source_lines):
20✔
315
    """Render a PEP8 missing whitespace around arithmetic operator message"""
316
    line = msg.line
20✔
317
    res = re.search(r"column (\d+)", msg.msg)
20✔
318
    col = int(res.group().split()[-1])
20✔
319
    end_idx = col + 1
20✔
320

321
    yield from render_context(line - 2, line, source_lines)
20✔
322
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
20✔
323
    yield from render_context(line + 1, line + 3, source_lines)
20✔
324

325

326
def render_pep8_errors_e227(msg, _node, source_lines=None):
20✔
327
    """Render a PEP8 missing whitespace around bitwise or shift operator message."""
328
    line = msg.line
20✔
329
    res = re.search(r"column (\d+)", msg.msg)
20✔
330
    col = int(res.group().split()[-1])
20✔
331
    # Check which operator to get the correct range of the line to highlight.
332
    # Default highlight is one character, but may be updated to two.
333
    # Note that only binary bitwise operators that are more than one character are included.
334
    operators = {">>", "<<"}
20✔
335
    end_idx = col + 1
20✔
336
    end_idx = end_idx + 1 if source_lines[line - 1][col : col + 2] in operators else end_idx
20✔
337

338
    yield from render_context(line - 2, line, source_lines)
20✔
339
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
20✔
340
    yield from render_context(line + 1, line + 3, source_lines)
20✔
341

342

343
def render_pep8_errors_e228(msg, _node, source_lines=None):
20✔
344
    """Render a PEP8 missing whitespace around modulo operator message."""
345
    line = msg.line
20✔
346
    res = re.search(r"column (\d+)", msg.msg)
20✔
347
    col = int(res.group().split()[-1])
20✔
348

349
    yield from render_context(line - 2, line, source_lines)
20✔
350
    yield (
20✔
351
        line,
352
        slice(col, col + 1),
353
        LineType.ERROR,
354
        source_lines[line - 1] + "  # INSERT A SPACE BEFORE AND AFTER THE % OPERATOR",
355
    )
356
    yield from render_context(line + 1, line + 3, source_lines)
20✔
357

358

359
def render_pep8_errors_e251(msg, _node, source_lines=None):
20✔
360
    """Render a PEP8 unexpected spaces around keyword / parameter equals message."""
UNCOV
361
    line = msg.line
×
UNCOV
362
    res = re.search(r"column (\d+)", msg.msg)
×
UNCOV
363
    col = int(res.group().split()[-1])
×
UNCOV
364
    equals_sign_idx = source_lines[line - 1][col:].find("=")
×
365
    code = source_lines[line - 1][col : col + equals_sign_idx if equals_sign_idx != -1 else None]
×
366
    end_idx = col + len(code) - len(code.lstrip())
×
367

368
    yield from render_context(line - 2, line, source_lines)
×
369
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
×
370
    yield from render_context(line + 1, line + 3, source_lines)
×
371

372

373
def render_pep8_errors_e261(msg, _node, source_lines=None):
20✔
374
    """Render a PEP8 at least two spaces before inline comment message."""
375
    line = msg.line
20✔
376
    res = re.search(r"column (\d+)", msg.msg)
20✔
377
    col = int(res.group().split()[-1])
20✔
378

379
    yield from render_context(line - 2, line, source_lines)
20✔
380
    yield (
20✔
381
        line,
382
        slice(col, len(source_lines[line - 1])),
383
        LineType.ERROR,
384
        source_lines[line - 1] + "  # INSERT TWO SPACES BEFORE THE '#'",
385
    )
386
    yield from render_context(line + 1, line + 3, source_lines)
20✔
387

388

389
def render_pep8_errors_e262(msg, _node, source_lines=None):
20✔
390
    """Render a PEP8 inline comment should start with '# ' message"""
391
    line = msg.line
20✔
392
    res = re.search(r"column (\d+)", msg.msg)
20✔
393
    col = int(res.group().split()[-1])
20✔
394

395
    source_line = source_lines[line - 1]
20✔
396
    keyword_idx = len(source_line) - len(source_line[col:].lstrip("# \t"))
20✔
397

398
    yield from render_context(line - 2, line, source_lines)
20✔
399
    yield (line, slice(col, keyword_idx), LineType.ERROR, source_line)
20✔
400
    yield from render_context(line + 1, line + 3, source_lines)
20✔
401

402

403
def render_pep8_errors_e265(msg, _node, source_lines=None):
20✔
404
    """Render a PEP8 block comment should start with '# ' message."""
405
    line = msg.line
20✔
406
    yield from render_context(line - 2, line, source_lines)
20✔
407
    yield (
20✔
408
        line,
409
        slice(0, len(source_lines[line - 1])),
410
        LineType.ERROR,
411
        source_lines[line - 1] + "  # INSERT SPACE AFTER THE '#'",
412
    )
413
    yield from render_context(line + 1, line + 3, source_lines)
20✔
414

415

416
def render_pep8_errors_e266(msg, _node, source_lines=None):
20✔
417
    """Render a PEP8 too many leading ‘#’ for block 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
    curr_idx = (
20✔
422
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("#"))
423
    )
424

425
    yield from render_context(line - 2, line, source_lines)
20✔
426
    yield (
20✔
427
        line,
428
        slice(col, curr_idx),
429
        LineType.ERROR,
430
        source_lines[line - 1] + "  # THERE SHOULD ONLY BE ONE '#'",
431
    )
432
    yield from render_context(line + 1, line + 3, source_lines)
20✔
433

434

435
def render_pep8_errors_e272(msg, _node, source_lines=None):
20✔
436
    """Render a PEP8 multiple spaces before keyword message."""
UNCOV
437
    line = msg.line
×
UNCOV
438
    res = re.search(r"column (\d+)", msg.msg)
×
UNCOV
439
    col = int(res.group().split()[-1])
×
UNCOV
440
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
×
441

442
    yield from render_context(line - 2, line, source_lines)
×
443
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
×
444
    yield from render_context(line + 1, line + 3, source_lines)
×
445

446

447
def render_pep8_errors_e275(msg, _node, source_lines=None):
20✔
448
    """Render a PEP8 missing whitespace after keyword message."""
449
    line = msg.line
20✔
450
    res = re.search(r"column (\d+)", msg.msg)
20✔
451
    col = int(res.group().split()[-1])
20✔
452

453
    # Get the range for highlighting the corresponding keyword.
454
    keyword = source_lines[line - 1][:col].split()[-1]
20✔
455
    keyword_idx = source_lines[line - 1].index(keyword)
20✔
456

457
    yield from render_context(line - 2, line, source_lines)
20✔
458
    yield (
20✔
459
        line,
460
        slice(keyword_idx, col),
461
        LineType.ERROR,
462
        source_lines[line - 1] + "  # INSERT SPACE AFTER KEYWORD",
463
    )
464
    yield from render_context(line + 1, line + 3, source_lines)
20✔
465

466

467
def render_pep8_errors_e301(msg, _node, source_lines=None):
20✔
468
    """Render a PEP8 expected 1 blank line message."""
469
    line = msg.line - 1
20✔
470
    yield from render_context(line - 1, line + 1, source_lines)
20✔
471
    body = source_lines[line]
20✔
472
    indentation = len(body) - len(body.lstrip())
20✔
473
    yield (
20✔
474
        None,
475
        slice(None, None),
476
        LineType.ERROR,
477
        body[:indentation] + NEW_BLANK_LINE_MESSAGE,
478
    )
479
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
480

481

482
def render_pep8_errors_e302(msg, _node, source_lines=None):
20✔
483
    """Render a PEP8 expected 2 blank lines message."""
484
    line = msg.line - 1
20✔
485
    if "found 0" in msg.msg:
20✔
486
        yield from render_context(line - 1, line + 1, source_lines)
20✔
487
        yield from (
20✔
488
            (
489
                None,
490
                slice(None, None),
491
                LineType.ERROR,
492
                NEW_BLANK_LINE_MESSAGE,
493
            )
494
            for _ in range(0, 2)
495
        )
496
    else:
497
        line -= 1
20✔
498
        yield from render_context(line - 1, line + 1, source_lines)
20✔
499
        yield from render_blank_line(line)
20✔
500
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
20✔
501
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
502

503

504
def render_pep8_errors_e303(msg, _node, source_lines=None):
20✔
505
    """Render a PEP8 too many blank lines message."""
506
    line = msg.line - 1
20✔
507
    while source_lines[line - 1].strip() == "":
20✔
508
        line -= 1
20✔
509
    yield from render_context(line - 1, line + 1, source_lines)
20✔
510
    body = source_lines[msg.line - 1]
20✔
511
    indentation = len(body) - len(body.lstrip())
20✔
512
    yield from (
20✔
513
        (curr_line, slice(None, None), LineType.ERROR, " " * (indentation + 28))
514
        for curr_line in range(line + 1, msg.line)
515
    )
516
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
517

518

519
def render_pep8_errors_e304(msg, _node, source_lines=None):
20✔
520
    """Render a PEP8 blank lines found after function decorator message."""
521
    line = msg.line - 1
20✔
522
    while source_lines[line - 1].strip() == "":
20✔
523
        line -= 1
20✔
524
    yield from render_context(line - 1, line + 1, source_lines)
20✔
525
    yield from (
20✔
526
        (curr_line, slice(None, None), LineType.ERROR, " " * 28)
527
        for curr_line in range(line + 1, msg.line)
528
    )
529
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
530

531

532
def render_pep8_errors_e305(msg, _node, source_lines=None):
20✔
533
    """Render a PEP8 expected 2 blank lines after class or function definition message."""
534
    line = msg.line - 1
20✔
535
    if "found 0" in msg.msg:
20✔
UNCOV
536
        yield from render_context(line - 1, line + 1, source_lines)
×
UNCOV
537
        yield from (
×
538
            (
539
                None,
540
                slice(None, None),
541
                LineType.ERROR,
542
                NEW_BLANK_LINE_MESSAGE,
543
            )
544
            for _ in range(0, 2)
545
        )
546
    else:
547
        line -= 1
20✔
548
        yield from render_context(line - 1, line + 1, source_lines)
20✔
549
        yield from render_blank_line(line)
20✔
550
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
20✔
551
    yield from render_context(msg.line, msg.line + 2, source_lines)
20✔
552

553

554
def render_pep8_errors_e306(msg, _node, source_lines=None):
20✔
555
    """Render a PEP8 expected 1 blank line before a nested definition message."""
UNCOV
556
    line = msg.line - 1
×
UNCOV
557
    yield from render_context(line - 1, line + 1, source_lines)
×
UNCOV
558
    body = source_lines[line]
×
UNCOV
559
    indentation = len(body) - len(body.lstrip())
×
560
    yield (
×
561
        None,
562
        slice(None, None),
563
        LineType.ERROR,
564
        body[:indentation] + NEW_BLANK_LINE_MESSAGE,
565
    )
UNCOV
566
    yield from render_context(msg.line, msg.line + 2, source_lines)
×
567

568

569
def render_missing_return_statement(msg, node, source_lines=None):
20✔
570
    """
571
    Render a missing return statements message
572
    """
573
    yield from render_context(msg.line, msg.end_line + 1, source_lines)
20✔
574

575
    # calculate indentation for the insertion point
576
    body = source_lines[msg.end_line - 1]
20✔
577
    indentation = len(source_lines[msg.line - 1]) - len(source_lines[msg.line - 1].lstrip())
20✔
578

579
    # determine whether reaching the end of function
580
    first_statement_line = node.end_lineno if len(node.body) == 0 else node.body[0].lineno
20✔
581
    function_indentation = len(source_lines[first_statement_line - 1]) - len(
20✔
582
        source_lines[first_statement_line - 1].lstrip()
583
    )
584

585
    if msg.end_line == node.end_lineno and indentation == function_indentation:
20✔
586
        insertion_text = body[:indentation] + "# INSERT RETURN STATEMENT HERE"
20✔
587
    else:
588
        insertion_text = body[:indentation] + "# INSERT RETURN STATEMENT HERE (OR BELOW)"
20✔
589

590
    # insert the message
591
    yield (
20✔
592
        None,
593
        slice(indentation, None),
594
        LineType.ERROR,
595
        insertion_text,
596
    )
597

598
    yield from render_context(msg.end_line + 1, msg.end_line + 3, source_lines)
20✔
599

600

601
def render_static_type_checker_errors(msg, _node=None, source_lines=None):
20✔
602
    """Render a message for incompatible argument types."""
603
    start_line = msg.line
20✔
604
    start_col = msg.column
20✔
605
    end_line = msg.end_line
20✔
606
    end_col = msg.end_column
20✔
607
    yield from render_context(start_line - 2, start_line, source_lines)
20✔
608

609
    if start_line == end_line:
20✔
610
        yield (
20✔
611
            start_line,
612
            slice(start_col - 1, end_col),
613
            LineType.ERROR,
614
            source_lines[start_line - 1],
615
        )
616
    else:
617
        yield (start_line, slice(start_col - 1, None), LineType.ERROR, source_lines[start_line - 1])
20✔
618
        yield from (
20✔
619
            (line, slice(None, None), LineType.ERROR, source_lines[line - 1])
620
            for line in range(start_line + 1, end_line)
621
        )
622
        yield (end_line, slice(None, end_col), LineType.ERROR, source_lines[end_line - 1])
20✔
623
    yield from render_context(end_line + 1, end_line + 3, source_lines)
20✔
624

625

626
CUSTOM_MESSAGES = {
20✔
627
    "missing-module-docstring": render_missing_docstring,
628
    "missing-class-docstring": render_missing_docstring,
629
    "missing-function-docstring": render_missing_docstring,
630
    "trailing-newlines": render_trailing_newlines,
631
    "trailing-whitespace": render_trailing_whitespace,
632
    "missing-return-type": render_missing_return_type,
633
    "too-many-arguments": render_too_many_arguments,
634
    "missing-space-in-doctest": render_missing_space_in_doctest,
635
    "pep8-errors": render_pep8_errors,
636
    "missing-return-statement": render_missing_return_statement,
637
    "incompatible-argument-type": render_static_type_checker_errors,
638
    "incompatible-assignment": render_static_type_checker_errors,
639
    "list-item-type-mismatch": render_static_type_checker_errors,
640
    "unsupported-operand-types": render_static_type_checker_errors,
641
    "union-attr-error": render_static_type_checker_errors,
642
    "dict-item-type-mismatch": render_static_type_checker_errors,
643
}
644

645
ERROR_TO_ERROR_FUNCTION_MAPPING = {
20✔
646
    "E101": render_pep8_errors_e101_and_e123,
647
    "E123": render_pep8_errors_e101_and_e123,
648
    "E115": render_pep8_errors_e115,
649
    "E116": render_pep8_errors_e116,
650
    "E122": render_pep8_errors_e122_and_e127_and_e131,
651
    "E127": render_pep8_errors_e122_and_e127_and_e131,
652
    "E131": render_pep8_errors_e122_and_e127_and_e131,
653
    "E124": render_pep8_errors_e124,
654
    "E125": render_pep8_errors_e125_and_e129,
655
    "E129": render_pep8_errors_e125_and_e129,
656
    "E128": render_pep8_errors_e128,
657
    "E201": render_pep8_errors_e201_e202_e203_e211,
658
    "E202": render_pep8_errors_e201_e202_e203_e211,
659
    "E203": render_pep8_errors_e201_e202_e203_e211,
660
    "E221": render_pep8_errors_e221,
661
    "E222": render_pep8_errors_e222,
662
    "E223": render_pep8_errors_e223,
663
    "E224": render_pep8_errors_e224_and_e273,
664
    "E273": render_pep8_errors_e224_and_e273,
665
    "E226": render_pep8_errors_e226,
666
    "E227": render_pep8_errors_e227,
667
    "E228": render_pep8_errors_e228,
668
    "E251": render_pep8_errors_e251,
669
    "E261": render_pep8_errors_e261,
670
    "E262": render_pep8_errors_e262,
671
    "E265": render_pep8_errors_e265,
672
    "E266": render_pep8_errors_e266,
673
    "E272": render_pep8_errors_e272,
674
    "E275": render_pep8_errors_e275,
675
    "E301": render_pep8_errors_e301,
676
    "E302": render_pep8_errors_e302,
677
    "E303": render_pep8_errors_e303,
678
    "E304": render_pep8_errors_e304,
679
    "E305": render_pep8_errors_e305,
680
    "E306": render_pep8_errors_e306,
681
}
682

683

684
class LineType(Enum):
20✔
685
    """An enumeration for _add_line method line types."""
686

687
    ERROR = 1  # line with error
20✔
688
    CONTEXT = 2  # non-error/other line added for context
20✔
689
    OTHER = 3  # line included in source but not error
20✔
690
    ELLIPSIS = 5  # code replaced with ellipsis
20✔
691
    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