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

pyta-uoft / pyta / 7943416989

17 Feb 2024 06:08PM UTC coverage: 95.693% (-1.2%) from 96.904%
7943416989

Pull #1011

github

web-flow
Merge 7cab9e3f7 into d0773772b
Pull Request #1011: New PEP8 custom renderers for error E203 and E226

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

43 existing lines in 1 file now uncovered.

3244 of 3390 relevant lines covered (95.69%)

7.3 hits per line

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

85.67
/python_ta/reporters/node_printers.py
1
"""Specify how errors should be rendered."""
5✔
2
import re
5✔
3
from enum import Enum
5✔
4

5
from astroid import nodes
5✔
6

7
NEW_BLANK_LINE_MESSAGE = "# INSERT NEW BLANK LINE HERE"
5✔
8

9

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

15

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

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

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

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

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

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

53

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

74

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

84

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

93

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

102

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

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

117

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

123

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

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

133

134
def render_pep8_errors(msg, _node, source_lines=None):
10✔
135
    """Render a PEP8 error message."""
136
    if "E101" in msg.msg:
5✔
137
        yield from render_pep8_errors_e101(msg, _node, source_lines)
138
    elif "E115" in msg.msg:
5✔
139
        yield from render_pep8_errors_e115(msg, _node, source_lines)
140
    elif "E116" in msg.msg:
5✔
141
        yield from render_pep8_errors_e116(msg, _node, source_lines)
5✔
142
    elif "E122" in msg.msg or "E127" in msg.msg or "E131" in msg.msg:
5✔
143
        yield from render_pep8_errors_e122_and_e127_and_e131(msg, _node, source_lines)
144
    elif "E124" in msg.msg:
5✔
145
        yield from render_pep8_errors_e124(msg, _node, source_lines)
5✔
146
    elif "E125" in msg.msg or "E129" in msg.msg:
5✔
147
        yield from render_pep8_errors_e125_and_e129(msg, _node, source_lines)
148
    elif "E128" in msg.msg:
5✔
149
        yield from render_pep8_errors_e128(msg, _node, source_lines)
5✔
150
    elif "E201" in msg.msg:
5✔
151
        yield from render_pep8_errors_e201(msg, _node, source_lines)
5✔
152
    elif "E202" in msg.msg:
5✔
153
        yield from render_pep8_errors_e202(msg, _node, source_lines)
5✔
154
    elif "E203" in msg.msg:
5✔
155
        yield from render_pep8_errors_e203(msg, _node, source_lines)
5✔
156
    elif "E211" in msg.msg:
5✔
157
        yield from render_pep8_errors_e211(msg, _node, source_lines)
5✔
158
    elif "E221" in msg.msg:
5✔
159
        yield from render_pep8_errors_e221(msg, _node, source_lines)
5✔
160
    elif "E223" in msg.msg:
5✔
161
        yield from render_pep8_errors_e223(msg, _node, source_lines)
162
    elif "E224" in msg.msg or "E273" in msg.msg:
5✔
163
        yield from render_pep8_errors_e224_and_e273(msg, _node, source_lines)
164
    elif "E226" in msg.msg:
5✔
165
        yield from render_pep8_errors_e226(msg, _node, source_lines)
5✔
166
    elif "E227" in msg.msg:
5✔
167
        yield from render_pep8_errors_e227(msg, _node, source_lines)
168
    elif "E228" in msg.msg:
5✔
169
        yield from render_pep8_errors_e228(msg, _node, source_lines)
170
    elif "E251" in msg.msg:
5✔
171
        yield from render_pep8_errors_e251(msg, _node, source_lines)
5✔
172
    elif "E261" in msg.msg:
5✔
173
        yield from render_pep8_errors_e261(msg, _node, source_lines)
5✔
174
    elif "E265" in msg.msg:
5✔
175
        yield from render_pep8_errors_e265(msg, _node, source_lines)
176
    elif "E266" in msg.msg:
5✔
177
        yield from render_pep8_errors_e266(msg, _node, source_lines)
178
    elif "E272" in msg.msg:
5✔
179
        yield from render_pep8_errors_e272(msg, _node, source_lines)
5✔
180
    elif "E275" in msg.msg:
5✔
181
        yield from render_pep8_errors_e275(msg, _node, source_lines)
182
    elif "E301" in msg.msg:
5✔
183
        yield from render_pep8_errors_e301(msg, _node, source_lines)
184
    elif "E302" in msg.msg:
5✔
185
        yield from render_pep8_errors_e302(msg, _node, source_lines)
5✔
186
    elif "E303" in msg.msg:
5✔
187
        yield from render_pep8_errors_e303(msg, _node, source_lines)
188
    elif "E304" in msg.msg:
5✔
189
        yield from render_pep8_errors_e304(msg, _node, source_lines)
190
    elif "E305" in msg.msg:
5✔
191
        yield from render_pep8_errors_e305(msg, _node, source_lines)
5✔
192
    elif "E306" in msg.msg:
5✔
193
        yield from render_pep8_errors_e306(msg, _node, source_lines)
5✔
194
    else:
195
        yield from render_generic(msg, _node, source_lines)
5✔
196

197

198
def render_blank_line(line):
5✔
199
    """Render a blank line for a PEP8 error message."""
200
    yield (line + 1, slice(None, None), LineType.ERROR, " " * 28)
10✔
201

202

203
def render_pep8_errors_e101(msg, _node, source_lines=None):
10✔
204
    """Render a PEP8 indentation contains mixed spaces and tabs message."""
205
    line = msg.line
206
    curr_idx = len(source_lines[line - 1]) - len(source_lines[line - 1].lstrip())
207
    yield from render_context(line - 2, line, source_lines)
208
    yield (line, slice(0, curr_idx), LineType.ERROR, source_lines[line - 1])
209
    yield from render_context(line + 1, line + 3, source_lines)
210

211

212
def render_pep8_errors_e115(msg, _node, source_lines=None):
5✔
213
    """Render a PEP8 expected an indented block (comment) message."""
UNCOV
214
    line = msg.line
×
215

UNCOV
216
    yield from render_context(line - 2, line, source_lines)
×
217
    yield (
218
        line,
219
        slice(0, len(source_lines[line - 1])),
220
        LineType.ERROR,
221
        source_lines[line - 1] + "  # INDENT THIS LINE",
222
    )
UNCOV
223
    yield from render_context(line + 1, line + 3, source_lines)
×
224

225

226
def render_pep8_errors_e116(msg, _node, source_lines=None):
10✔
227
    """Render a PEP8 unexpected indentation (comment) message"""
228
    line = msg.line
5✔
229
    curr_idx = len(source_lines[line - 1]) - len(source_lines[line - 1].lstrip())
5✔
230
    yield from render_context(line - 2, line, source_lines)
5✔
231
    yield (
5✔
232
        line,
233
        slice(0, curr_idx),
234
        LineType.ERROR,
235
        source_lines[line - 1],
236
    )
237
    yield from render_context(line + 1, line + 3, source_lines)
5✔
238

239

240
def render_pep8_errors_e122_and_e127_and_e131(msg, _node, source_lines=None):
5✔
241
    """
242
    Render a PEP8 continuation line missing indentation or outdented message, a line over-indented for visual indent
243
    message, and a continuation line unaligned for hanging indent message.
244
    """
UNCOV
245
    line = msg.line
×
UNCOV
246
    curr_line_start_index = len(source_lines[line - 1]) - len(source_lines[line - 1].lstrip())
×
UNCOV
247
    end_index = curr_line_start_index if curr_line_start_index > 0 else len(source_lines[line - 1])
×
UNCOV
248
    yield from render_context(line - 2, line, source_lines)
×
249
    yield (
250
        line,
251
        slice(0, end_index),
252
        LineType.ERROR,
253
        source_lines[line - 1],
254
    )
UNCOV
255
    yield from render_context(line + 1, line + 3, source_lines)
×
256

257

258
def render_pep8_errors_e124(msg, _node, source_lines=None):
10✔
259
    """Render a PEP8 closing bracket does not match visual indentation message."""
260
    line = msg.line
5✔
261
    res = re.search(r"column (\d+)", msg.msg)
5✔
262
    col = int(res.group().split()[-1])
5✔
263
    yield from render_context(line - 2, line, source_lines)
5✔
264
    yield (line, slice(col, col + 1), LineType.ERROR, source_lines[line - 1])
5✔
265
    yield from render_context(line + 1, line + 3, source_lines)
5✔
266

267

268
def render_pep8_errors_e125_and_e129(msg, _node, source_lines=None):
5✔
269
    """Render a PEP8 continuation line with same indent as next logical line message
270
    AND a PEP8 visually indented line with same indent as next logical line messsage"""
UNCOV
271
    line = msg.line
×
UNCOV
272
    curr_idx = len(source_lines[line - 1]) - len(source_lines[line - 1].lstrip())
×
273

UNCOV
274
    yield from render_context(line - 2, line, source_lines)
×
275
    yield (
276
        line,
277
        slice(curr_idx, len(source_lines[line - 1])),
278
        LineType.ERROR,
279
        source_lines[line - 1] + " " * 2 + "# INDENT THIS LINE",
280
    )
UNCOV
281
    yield from render_context(line + 1, line + 3, source_lines)
×
282

283

284
def render_pep8_errors_e128(msg, _node, source_lines):
10✔
285
    """Render a PEP8 continuation line under-indented for visual indent message."""
286
    line = msg.line
5✔
287
    res = re.search(r"column (\d+)", msg.msg)
5✔
288
    col = int(res.group().split()[-1])
5✔
289

290
    yield from render_context(line - 2, line, source_lines)
5✔
291
    yield (line, slice(0, col if col != 0 else None), LineType.ERROR, source_lines[line - 1])
5✔
292
    yield from render_context(line + 1, line + 3, source_lines)
5✔
293

294

295
def render_pep8_errors_e201(msg, _node, source_lines=None):
5✔
296
    """Render a PEP8 whitespace after '(' message."""
297
    line = msg.line
10✔
298
    res = re.search(r"column (\d+)", msg.msg)
10✔
299
    col = int(res.group().split()[-1])
10✔
300
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
10✔
301

302
    yield from render_context(line - 2, line, source_lines)
10✔
303
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
10✔
304
    yield from render_context(line + 1, line + 3, source_lines)
10✔
305

306

307
def render_pep8_errors_e202(msg, _node, source_lines=None):
10✔
308
    """Render a PEP8 whitespace before ')' message."""
309
    line = msg.line
5✔
310
    res = re.search(r"column (\d+)", msg.msg)
5✔
311
    col = int(res.group().split()[-1])
5✔
312
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
5✔
313

314
    yield from render_context(line - 2, line, source_lines)
5✔
315
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
5✔
316
    yield from render_context(line + 1, line + 3, source_lines)
5✔
317

318

319
def render_pep8_errors_e203(msg, _node, source_lines):
5✔
320
    """Render a PEP8 whitespace before ‘,’, ‘;’, or ‘:’ message."""
321
    line = msg.line
10✔
322
    res = re.search(r"column (\d+)", msg.msg)
10✔
323
    col = int(res.group().split()[-1])
10✔
324
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
10✔
325

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

330

331
def render_pep8_errors_e211(msg, _node, source_lines=None):
10✔
332
    """Render a PEP8 whitespace before '(' message."""
333
    line = msg.line
5✔
334
    res = re.search(r"column (\d+)", msg.msg)
5✔
335
    col = int(res.group().split()[-1])
5✔
336
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
5✔
337

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

342

343
def render_pep8_errors_e221(msg, _node, source_lines=None):
5✔
344
    """Render a PEP8 multiple spaces before operator message."""
345
    line = msg.line
10✔
346
    res = re.search(r"column (\d+)", msg.msg)
10✔
347
    col = int(res.group().split()[-1])
10✔
348
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
10✔
349

350
    yield from render_context(line - 2, line, source_lines)
10✔
351
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
10✔
352
    yield from render_context(line + 1, line + 3, source_lines)
10✔
353

354

355
def render_pep8_errors_e223(msg, _node, source_lines=None):
10✔
356
    """Render a PEP8 tab before operator message."""
357
    line = msg.line
358
    res = re.search(r"column (\d+)", msg.msg)
359
    col = int(res.group().split()[-1])
360
    curr_idx = (
361
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("\t"))
362
    )
363

364
    yield from render_context(line - 2, line, source_lines)
365
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
366
    yield from render_context(line + 1, line + 3, source_lines)
367

368

369
def render_pep8_errors_e224_and_e273(msg, _node, source_lines):
5✔
370
    """Render a PEP8 tab after operator message and a PEP8 tab after keyword message."""
UNCOV
371
    line = msg.line
×
UNCOV
372
    res = re.search(r"column (\d+)", msg.msg)
×
UNCOV
373
    col = int(res.group().split()[-1])
×
374
    curr_idx = (
375
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("\t"))
376
    )
377

UNCOV
378
    yield from render_context(line - 2, line, source_lines)
×
UNCOV
379
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
×
UNCOV
380
    yield from render_context(line + 1, line + 3, source_lines)
×
381

382

383
def render_pep8_errors_e226(msg, _node, source_lines):
10✔
384
    """Render a PEP8 missing whitespace around arithmetic operator message"""
385
    line = msg.line
5✔
386
    res = re.search(r"column (\d+)", msg.msg)
5✔
387
    col = int(res.group().split()[-1])
5✔
388
    end_idx = col + 1
5✔
389

390
    yield from render_context(line - 2, line, source_lines)
5✔
391
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
5✔
392
    yield from render_context(line + 1, line + 3, source_lines)
5✔
393

394

395
def render_pep8_errors_e227(msg, _node, source_lines=None):
5✔
396
    """Render a PEP8 missing whitespace around bitwise or shift operator message."""
UNCOV
397
    line = msg.line
×
UNCOV
398
    res = re.search(r"column (\d+)", msg.msg)
×
UNCOV
399
    col = int(res.group().split()[-1])
×
400
    # Check which operator to get the correct range of the line to highlight.
401
    # Default highlight is one character, but may be updated to two.
402
    # Note that only binary bitwise operators that are more than one character are included.
UNCOV
403
    operators = {">>", "<<"}
×
UNCOV
404
    end_idx = col + 1
×
UNCOV
405
    end_idx = end_idx + 1 if source_lines[line - 1][col : col + 2] in operators else end_idx
×
406

UNCOV
407
    yield from render_context(line - 2, line, source_lines)
×
UNCOV
408
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
×
UNCOV
409
    yield from render_context(line + 1, line + 3, source_lines)
×
410

411

412
def render_pep8_errors_e228(msg, _node, source_lines=None):
10✔
413
    """Render a PEP8 missing whitespace around modulo operator message."""
414
    line = msg.line
415
    res = re.search(r"column (\d+)", msg.msg)
416
    col = int(res.group().split()[-1])
417

418
    yield from render_context(line - 2, line, source_lines)
419
    yield (
420
        line,
421
        slice(col, col + 1),
422
        LineType.ERROR,
423
        source_lines[line - 1] + "  # INSERT A SPACE BEFORE AND AFTER THE % OPERATOR",
424
    )
425
    yield from render_context(line + 1, line + 3, source_lines)
426

427

428
def render_pep8_errors_e251(msg, _node, source_lines=None):
5✔
429
    """Render a PEP8 unexpected spaces around keyword / parameter equals message."""
430
    line = msg.line
10✔
431
    res = re.search(r"column (\d+)", msg.msg)
10✔
432
    col = int(res.group().split()[-1])
10✔
433
    equals_sign_idx = source_lines[line - 1][col:].find("=")
10✔
434
    code = source_lines[line - 1][col : col + equals_sign_idx if equals_sign_idx != -1 else None]
10✔
435
    end_idx = col + len(code) - len(code.lstrip())
10✔
436

437
    yield from render_context(line - 2, line, source_lines)
10✔
438
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
10✔
439
    yield from render_context(line + 1, line + 3, source_lines)
10✔
440

441

442
def render_pep8_errors_e261(msg, _node, source_lines=None):
10✔
443
    """Render a PEP8 at least two spaces before inline comment message."""
444
    line = msg.line
5✔
445
    res = re.search(r"column (\d+)", msg.msg)
5✔
446
    col = int(res.group().split()[-1])
5✔
447

448
    yield from render_context(line - 2, line, source_lines)
5✔
449
    yield (
5✔
450
        line,
451
        slice(col, len(source_lines[line - 1])),
452
        LineType.ERROR,
453
        source_lines[line - 1] + "  # INSERT TWO SPACES BEFORE THE '#'",
454
    )
455
    yield from render_context(line + 1, line + 3, source_lines)
5✔
456

457

458
def render_pep8_errors_e265(msg, _node, source_lines=None):
5✔
459
    """Render a PEP8 block comment should start with '# ' message."""
UNCOV
460
    line = msg.line
×
UNCOV
461
    yield from render_context(line - 2, line, source_lines)
×
462
    yield (
463
        line,
464
        slice(0, len(source_lines[line - 1])),
465
        LineType.ERROR,
466
        source_lines[line - 1] + "  # INSERT SPACE AFTER THE '#'",
467
    )
UNCOV
468
    yield from render_context(line + 1, line + 3, source_lines)
×
469

470

471
def render_pep8_errors_e266(msg, _node, source_lines=None):
10✔
472
    """Render a PEP8 too many leading ‘#’ for block comment message."""
473
    line = msg.line
474
    res = re.search(r"column (\d+)", msg.msg)
475
    col = int(res.group().split()[-1])
476
    curr_idx = (
477
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("#"))
478
    )
479

480
    yield from render_context(line - 2, line, source_lines)
481
    yield (
482
        line,
483
        slice(col, curr_idx),
484
        LineType.ERROR,
485
        source_lines[line - 1] + "  # THERE SHOULD ONLY BE ONE '#'",
486
    )
487
    yield from render_context(line + 1, line + 3, source_lines)
488

489

490
def render_pep8_errors_e272(msg, _node, source_lines=None):
5✔
491
    """Render a PEP8 multiple spaces before keyword message."""
492
    line = msg.line
10✔
493
    res = re.search(r"column (\d+)", msg.msg)
10✔
494
    col = int(res.group().split()[-1])
10✔
495
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
10✔
496

497
    yield from render_context(line - 2, line, source_lines)
10✔
498
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
10✔
499
    yield from render_context(line + 1, line + 3, source_lines)
10✔
500

501

502
def render_pep8_errors_e275(msg, _node, source_lines=None):
10✔
503
    """Render a PEP8 missing whitespace after keyword message."""
504
    line = msg.line
505
    res = re.search(r"column (\d+)", msg.msg)
506
    col = int(res.group().split()[-1])
507

508
    # Get the range for highlighting the corresponding keyword.
509
    keyword = source_lines[line - 1][:col].split()[-1]
510
    keyword_idx = source_lines[line - 1].index(keyword)
511

512
    yield from render_context(line - 2, line, source_lines)
513
    yield (
514
        line,
515
        slice(keyword_idx, col),
516
        LineType.ERROR,
517
        source_lines[line - 1] + "  # INSERT SPACE AFTER KEYWORD",
518
    )
519
    yield from render_context(line + 1, line + 3, source_lines)
520

521

522
def render_pep8_errors_e301(msg, _node, source_lines=None):
5✔
523
    """Render a PEP8 expected 1 blank line message."""
UNCOV
524
    line = msg.line - 1
×
UNCOV
525
    yield from render_context(line - 1, line + 1, source_lines)
×
UNCOV
526
    body = source_lines[line]
×
UNCOV
527
    indentation = len(body) - len(body.lstrip())
×
528
    yield (
529
        None,
530
        slice(None, None),
531
        LineType.ERROR,
532
        body[:indentation] + NEW_BLANK_LINE_MESSAGE + " " * indentation,
533
    )
UNCOV
534
    yield from render_context(msg.line, msg.line + 2, source_lines)
×
535

536

537
def render_pep8_errors_e302(msg, _node, source_lines=None):
10✔
538
    """Render a PEP8 expected 2 blank lines message."""
539
    line = msg.line - 1
5✔
540
    if "found 0" in msg.msg:
5✔
541
        yield from render_context(line - 1, line + 1, source_lines)
5✔
542
        yield from (
5✔
543
            (
544
                None,
545
                slice(None, None),
546
                LineType.ERROR,
547
                NEW_BLANK_LINE_MESSAGE,
548
            )
549
            for _ in range(0, 2)
550
        )
551
    else:
552
        line -= 1
5✔
553
        yield from render_context(line - 1, line + 1, source_lines)
5✔
554
        yield from render_blank_line(line)
5✔
555
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
5✔
556
    yield from render_context(msg.line, msg.line + 2, source_lines)
5✔
557

558

559
def render_pep8_errors_e303(msg, _node, source_lines=None):
5✔
560
    """Render a PEP8 too many blank lines message."""
UNCOV
561
    line = msg.line - 1
×
UNCOV
562
    while source_lines[line - 1].strip() == "":
×
UNCOV
563
        line -= 1
×
UNCOV
564
    yield from render_context(line - 1, line + 1, source_lines)
×
UNCOV
565
    body = source_lines[msg.line - 1]
×
UNCOV
566
    indentation = len(body) - len(body.lstrip())
×
567
    yield from (
568
        (curr_line, slice(None, None), LineType.ERROR, " " * (indentation + 28))
569
        for curr_line in range(line + 1, msg.line)
570
    )
UNCOV
571
    yield from render_context(msg.line, msg.line + 2, source_lines)
×
572

573

574
def render_pep8_errors_e304(msg, _node, source_lines=None):
10✔
575
    """Render a PEP8 blank lines found after function decorator message."""
576
    line = msg.line - 1
577
    while source_lines[line - 1].strip() == "":
578
        line -= 1
579
    yield from render_context(line - 1, line + 1, source_lines)
580
    yield from (
581
        (curr_line, slice(None, None), LineType.ERROR, " " * 28)
582
        for curr_line in range(line + 1, msg.line)
583
    )
584
    yield from render_context(msg.line, msg.line + 2, source_lines)
585

586

587
def render_pep8_errors_e305(msg, _node, source_lines=None):
5✔
588
    """Render a PEP8 expected 2 blank lines after class or function definition message."""
589
    line = msg.line - 1
10✔
590
    if "found 0" in msg.msg:
10✔
UNCOV
591
        yield from render_context(line - 1, line + 1, source_lines)
×
592
        yield from (
593
            (
594
                None,
595
                slice(None, None),
596
                LineType.ERROR,
597
                NEW_BLANK_LINE_MESSAGE,
598
            )
599
            for _ in range(0, 2)
600
        )
601
    else:
602
        line -= 1
10✔
603
        yield from render_context(line - 1, line + 1, source_lines)
10✔
604
        yield from render_blank_line(line)
10✔
605
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
10✔
606
    yield from render_context(msg.line, msg.line + 2, source_lines)
10✔
607

608

609
def render_pep8_errors_e306(msg, _node, source_lines=None):
10✔
610
    """Render a PEP8 expected 1 blank line before a nested definition message."""
611
    line = msg.line - 1
5✔
612
    yield from render_context(line - 1, line + 1, source_lines)
5✔
613
    body = source_lines[line]
5✔
614
    indentation = len(body) - len(body.lstrip())
5✔
615
    yield (
5✔
616
        None,
617
        slice(None, None),
618
        LineType.ERROR,
619
        body[:indentation] + NEW_BLANK_LINE_MESSAGE + " " * indentation,
620
    )
621
    yield from render_context(msg.line, msg.line + 2, source_lines)
5✔
622

623

624
CUSTOM_MESSAGES = {
5✔
625
    "missing-module-docstring": render_missing_docstring,
626
    "missing-class-docstring": render_missing_docstring,
627
    "missing-function-docstring": render_missing_docstring,
628
    "trailing-newlines": render_trailing_newlines,
629
    "trailing-whitespace": render_trailing_whitespace,
630
    "missing-return-type": render_missing_return_type,
631
    "too-many-arguments": render_too_many_arguments,
632
    "missing-space-in-doctest": render_missing_space_in_doctest,
633
    "pep8-errors": render_pep8_errors,
634
}
635

636

637
class LineType(Enum):
5✔
638
    """An enumeration for _add_line method line types."""
5✔
639

640
    ERROR = 1  # line with error
10✔
641
    CONTEXT = 2  # non-error/other line added for context
10✔
642
    OTHER = 3  # line included in source but not error
10✔
643
    ELLIPSIS = 5  # code replaced with ellipsis
10✔
644
    DOCSTRING = 6  # docstring needed warning
10✔
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