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

pyta-uoft / pyta / 7967988832

20 Feb 2024 03:15AM UTC coverage: 95.668% (-1.2%) from 96.904%
7967988832

push

github

web-flow
Add new custom renderers for pep8-errors E203 and E226 (#1011)

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

43 existing lines in 1 file now uncovered.

3224 of 3370 relevant lines covered (95.67%)

7.3 hits per line

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

84.64
/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 or "E202" in msg.msg or "E203" in msg.msg:
5✔
151
        yield from render_pep8_errors_e201_e202_e203(msg, _node, source_lines)
5✔
152
    elif "E211" in msg.msg:
5✔
153
        yield from render_pep8_errors_e211(msg, _node, source_lines)
5✔
154
    elif "E221" in msg.msg:
5✔
155
        yield from render_pep8_errors_e221(msg, _node, source_lines)
5✔
156
    elif "E223" in msg.msg:
5✔
157
        yield from render_pep8_errors_e223(msg, _node, source_lines)
158
    elif "E224" in msg.msg or "E273" in msg.msg:
5✔
159
        yield from render_pep8_errors_e224_and_e273(msg, _node, source_lines)
160
    elif "E226" in msg.msg:
5✔
161
        yield from render_pep8_errors_e226(msg, _node, source_lines)
5✔
162
    elif "E227" in msg.msg:
5✔
163
        yield from render_pep8_errors_e227(msg, _node, source_lines)
164
    elif "E228" in msg.msg:
5✔
165
        yield from render_pep8_errors_e228(msg, _node, source_lines)
166
    elif "E251" in msg.msg:
5✔
167
        yield from render_pep8_errors_e251(msg, _node, source_lines)
5✔
168
    elif "E261" in msg.msg:
5✔
169
        yield from render_pep8_errors_e261(msg, _node, source_lines)
5✔
170
    elif "E265" in msg.msg:
5✔
171
        yield from render_pep8_errors_e265(msg, _node, source_lines)
172
    elif "E266" in msg.msg:
5✔
173
        yield from render_pep8_errors_e266(msg, _node, source_lines)
174
    elif "E272" in msg.msg:
5✔
175
        yield from render_pep8_errors_e272(msg, _node, source_lines)
5✔
176
    elif "E275" in msg.msg:
5✔
177
        yield from render_pep8_errors_e275(msg, _node, source_lines)
178
    elif "E301" in msg.msg:
5✔
179
        yield from render_pep8_errors_e301(msg, _node, source_lines)
180
    elif "E302" in msg.msg:
5✔
181
        yield from render_pep8_errors_e302(msg, _node, source_lines)
5✔
182
    elif "E303" in msg.msg:
5✔
183
        yield from render_pep8_errors_e303(msg, _node, source_lines)
184
    elif "E304" in msg.msg:
5✔
185
        yield from render_pep8_errors_e304(msg, _node, source_lines)
186
    elif "E305" in msg.msg:
5✔
187
        yield from render_pep8_errors_e305(msg, _node, source_lines)
5✔
188
    elif "E306" in msg.msg:
5✔
189
        yield from render_pep8_errors_e306(msg, _node, source_lines)
5✔
190
    else:
191
        yield from render_generic(msg, _node, source_lines)
5✔
192

193

194
def render_blank_line(line):
5✔
195
    """Render a blank line for a PEP8 error message."""
196
    yield (line + 1, slice(None, None), LineType.ERROR, " " * 28)
10✔
197

198

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

207

208
def render_pep8_errors_e115(msg, _node, source_lines=None):
5✔
209
    """Render a PEP8 expected an indented block (comment) message."""
UNCOV
210
    line = msg.line
×
211

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

221

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

235

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

253

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

263

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

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

279

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

286
    yield from render_context(line - 2, line, source_lines)
5✔
287
    yield (line, slice(0, col if col != 0 else None), LineType.ERROR, source_lines[line - 1])
5✔
288
    yield from render_context(line + 1, line + 3, source_lines)
5✔
289

290

291
def render_pep8_errors_e201_e202_e203(msg, _node, source_lines=None):
5✔
292
    """Render a PEP8 whitespace after '(' message,
293
    a PEP8 whitespace before ')' message,
294
    AND a PEP8 whitespace before ‘,’, ‘;’, or ‘:’ message."""
295
    line = msg.line
10✔
296
    res = re.search(r"column (\d+)", msg.msg)
10✔
297
    col = int(res.group().split()[-1])
10✔
298
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
10✔
299

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

304

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

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

316

317
def render_pep8_errors_e221(msg, _node, source_lines=None):
5✔
318
    """Render a PEP8 multiple spaces before operator message."""
319
    line = msg.line
10✔
320
    res = re.search(r"column (\d+)", msg.msg)
10✔
321
    col = int(res.group().split()[-1])
10✔
322
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
10✔
323

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

328

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

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

342

343
def render_pep8_errors_e224_and_e273(msg, _node, source_lines):
5✔
344
    """Render a PEP8 tab after operator message and a PEP8 tab after keyword message."""
UNCOV
345
    line = msg.line
×
UNCOV
346
    res = re.search(r"column (\d+)", msg.msg)
×
UNCOV
347
    col = int(res.group().split()[-1])
×
348
    curr_idx = (
349
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("\t"))
350
    )
351

UNCOV
352
    yield from render_context(line - 2, line, source_lines)
×
UNCOV
353
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
×
UNCOV
354
    yield from render_context(line + 1, line + 3, source_lines)
×
355

356

357
def render_pep8_errors_e226(msg, _node, source_lines):
10✔
358
    """Render a PEP8 missing whitespace around arithmetic operator message"""
359
    line = msg.line
5✔
360
    res = re.search(r"column (\d+)", msg.msg)
5✔
361
    col = int(res.group().split()[-1])
5✔
362
    end_idx = col + 1
5✔
363

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

368

369
def render_pep8_errors_e227(msg, _node, source_lines=None):
5✔
370
    """Render a PEP8 missing whitespace around bitwise or shift operator 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
    # Check which operator to get the correct range of the line to highlight.
375
    # Default highlight is one character, but may be updated to two.
376
    # Note that only binary bitwise operators that are more than one character are included.
UNCOV
377
    operators = {">>", "<<"}
×
UNCOV
378
    end_idx = col + 1
×
UNCOV
379
    end_idx = end_idx + 1 if source_lines[line - 1][col : col + 2] in operators else end_idx
×
380

UNCOV
381
    yield from render_context(line - 2, line, source_lines)
×
UNCOV
382
    yield (line, slice(col, end_idx), LineType.ERROR, source_lines[line - 1])
×
UNCOV
383
    yield from render_context(line + 1, line + 3, source_lines)
×
384

385

386
def render_pep8_errors_e228(msg, _node, source_lines=None):
10✔
387
    """Render a PEP8 missing whitespace around modulo operator message."""
388
    line = msg.line
389
    res = re.search(r"column (\d+)", msg.msg)
390
    col = int(res.group().split()[-1])
391

392
    yield from render_context(line - 2, line, source_lines)
393
    yield (
394
        line,
395
        slice(col, col + 1),
396
        LineType.ERROR,
397
        source_lines[line - 1] + "  # INSERT A SPACE BEFORE AND AFTER THE % OPERATOR",
398
    )
399
    yield from render_context(line + 1, line + 3, source_lines)
400

401

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

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

415

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

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

431

432
def render_pep8_errors_e265(msg, _node, source_lines=None):
5✔
433
    """Render a PEP8 block comment should start with '# ' message."""
UNCOV
434
    line = msg.line
×
UNCOV
435
    yield from render_context(line - 2, line, source_lines)
×
436
    yield (
437
        line,
438
        slice(0, len(source_lines[line - 1])),
439
        LineType.ERROR,
440
        source_lines[line - 1] + "  # INSERT SPACE AFTER THE '#'",
441
    )
UNCOV
442
    yield from render_context(line + 1, line + 3, source_lines)
×
443

444

445
def render_pep8_errors_e266(msg, _node, source_lines=None):
10✔
446
    """Render a PEP8 too many leading ‘#’ for block comment message."""
447
    line = msg.line
448
    res = re.search(r"column (\d+)", msg.msg)
449
    col = int(res.group().split()[-1])
450
    curr_idx = (
451
        col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip("#"))
452
    )
453

454
    yield from render_context(line - 2, line, source_lines)
455
    yield (
456
        line,
457
        slice(col, curr_idx),
458
        LineType.ERROR,
459
        source_lines[line - 1] + "  # THERE SHOULD ONLY BE ONE '#'",
460
    )
461
    yield from render_context(line + 1, line + 3, source_lines)
462

463

464
def render_pep8_errors_e272(msg, _node, source_lines=None):
5✔
465
    """Render a PEP8 multiple spaces before keyword message."""
466
    line = msg.line
10✔
467
    res = re.search(r"column (\d+)", msg.msg)
10✔
468
    col = int(res.group().split()[-1])
10✔
469
    curr_idx = col + len(source_lines[line - 1][col:]) - len(source_lines[line - 1][col:].lstrip())
10✔
470

471
    yield from render_context(line - 2, line, source_lines)
10✔
472
    yield (line, slice(col, curr_idx), LineType.ERROR, source_lines[line - 1])
10✔
473
    yield from render_context(line + 1, line + 3, source_lines)
10✔
474

475

476
def render_pep8_errors_e275(msg, _node, source_lines=None):
10✔
477
    """Render a PEP8 missing whitespace after keyword message."""
478
    line = msg.line
479
    res = re.search(r"column (\d+)", msg.msg)
480
    col = int(res.group().split()[-1])
481

482
    # Get the range for highlighting the corresponding keyword.
483
    keyword = source_lines[line - 1][:col].split()[-1]
484
    keyword_idx = source_lines[line - 1].index(keyword)
485

486
    yield from render_context(line - 2, line, source_lines)
487
    yield (
488
        line,
489
        slice(keyword_idx, col),
490
        LineType.ERROR,
491
        source_lines[line - 1] + "  # INSERT SPACE AFTER KEYWORD",
492
    )
493
    yield from render_context(line + 1, line + 3, source_lines)
494

495

496
def render_pep8_errors_e301(msg, _node, source_lines=None):
5✔
497
    """Render a PEP8 expected 1 blank line message."""
UNCOV
498
    line = msg.line - 1
×
UNCOV
499
    yield from render_context(line - 1, line + 1, source_lines)
×
UNCOV
500
    body = source_lines[line]
×
UNCOV
501
    indentation = len(body) - len(body.lstrip())
×
502
    yield (
503
        None,
504
        slice(None, None),
505
        LineType.ERROR,
506
        body[:indentation] + NEW_BLANK_LINE_MESSAGE + " " * indentation,
507
    )
UNCOV
508
    yield from render_context(msg.line, msg.line + 2, source_lines)
×
509

510

511
def render_pep8_errors_e302(msg, _node, source_lines=None):
10✔
512
    """Render a PEP8 expected 2 blank lines message."""
513
    line = msg.line - 1
5✔
514
    if "found 0" in msg.msg:
5✔
515
        yield from render_context(line - 1, line + 1, source_lines)
5✔
516
        yield from (
5✔
517
            (
518
                None,
519
                slice(None, None),
520
                LineType.ERROR,
521
                NEW_BLANK_LINE_MESSAGE,
522
            )
523
            for _ in range(0, 2)
524
        )
525
    else:
526
        line -= 1
5✔
527
        yield from render_context(line - 1, line + 1, source_lines)
5✔
528
        yield from render_blank_line(line)
5✔
529
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
5✔
530
    yield from render_context(msg.line, msg.line + 2, source_lines)
5✔
531

532

533
def render_pep8_errors_e303(msg, _node, source_lines=None):
5✔
534
    """Render a PEP8 too many blank lines message."""
UNCOV
535
    line = msg.line - 1
×
UNCOV
536
    while source_lines[line - 1].strip() == "":
×
UNCOV
537
        line -= 1
×
UNCOV
538
    yield from render_context(line - 1, line + 1, source_lines)
×
UNCOV
539
    body = source_lines[msg.line - 1]
×
UNCOV
540
    indentation = len(body) - len(body.lstrip())
×
541
    yield from (
542
        (curr_line, slice(None, None), LineType.ERROR, " " * (indentation + 28))
543
        for curr_line in range(line + 1, msg.line)
544
    )
UNCOV
545
    yield from render_context(msg.line, msg.line + 2, source_lines)
×
546

547

548
def render_pep8_errors_e304(msg, _node, source_lines=None):
10✔
549
    """Render a PEP8 blank lines found after function decorator message."""
550
    line = msg.line - 1
551
    while source_lines[line - 1].strip() == "":
552
        line -= 1
553
    yield from render_context(line - 1, line + 1, source_lines)
554
    yield from (
555
        (curr_line, slice(None, None), LineType.ERROR, " " * 28)
556
        for curr_line in range(line + 1, msg.line)
557
    )
558
    yield from render_context(msg.line, msg.line + 2, source_lines)
559

560

561
def render_pep8_errors_e305(msg, _node, source_lines=None):
5✔
562
    """Render a PEP8 expected 2 blank lines after class or function definition message."""
563
    line = msg.line - 1
10✔
564
    if "found 0" in msg.msg:
10✔
UNCOV
565
        yield from render_context(line - 1, line + 1, source_lines)
×
566
        yield from (
567
            (
568
                None,
569
                slice(None, None),
570
                LineType.ERROR,
571
                NEW_BLANK_LINE_MESSAGE,
572
            )
573
            for _ in range(0, 2)
574
        )
575
    else:
576
        line -= 1
10✔
577
        yield from render_context(line - 1, line + 1, source_lines)
10✔
578
        yield from render_blank_line(line)
10✔
579
        yield (None, slice(None, None), LineType.ERROR, NEW_BLANK_LINE_MESSAGE)
10✔
580
    yield from render_context(msg.line, msg.line + 2, source_lines)
10✔
581

582

583
def render_pep8_errors_e306(msg, _node, source_lines=None):
10✔
584
    """Render a PEP8 expected 1 blank line before a nested definition message."""
585
    line = msg.line - 1
5✔
586
    yield from render_context(line - 1, line + 1, source_lines)
5✔
587
    body = source_lines[line]
5✔
588
    indentation = len(body) - len(body.lstrip())
5✔
589
    yield (
5✔
590
        None,
591
        slice(None, None),
592
        LineType.ERROR,
593
        body[:indentation] + NEW_BLANK_LINE_MESSAGE + " " * indentation,
594
    )
595
    yield from render_context(msg.line, msg.line + 2, source_lines)
5✔
596

597

598
CUSTOM_MESSAGES = {
5✔
599
    "missing-module-docstring": render_missing_docstring,
600
    "missing-class-docstring": render_missing_docstring,
601
    "missing-function-docstring": render_missing_docstring,
602
    "trailing-newlines": render_trailing_newlines,
603
    "trailing-whitespace": render_trailing_whitespace,
604
    "missing-return-type": render_missing_return_type,
605
    "too-many-arguments": render_too_many_arguments,
606
    "missing-space-in-doctest": render_missing_space_in_doctest,
607
    "pep8-errors": render_pep8_errors,
608
}
609

610

611
class LineType(Enum):
5✔
612
    """An enumeration for _add_line method line types."""
5✔
613

614
    ERROR = 1  # line with error
10✔
615
    CONTEXT = 2  # non-error/other line added for context
10✔
616
    OTHER = 3  # line included in source but not error
10✔
617
    ELLIPSIS = 5  # code replaced with ellipsis
10✔
618
    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