Skip to content

Commit 112d65c

Browse files
committed
Add PLU003: blank lines before except
1 parent ecddaf4 commit 112d65c

File tree

10 files changed

+169
-8
lines changed

10 files changed

+169
-8
lines changed

README.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
[![black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
88

99
Flake8-plus is a plugin for [Flake8](https://github.com/PyCQA/flake8) that detects
10-
incorrect amounts of vertical whitespace before the first toplevel `import` statement
11-
and before `return` statements. The plugin can be configured to expect any number of
12-
blank lines. By default, the plugin expects no blank lines before both `import` and
13-
`return`.
10+
incorrect amounts of vertical whitespace before the first toplevel `import` statement,
11+
before `return` statements and before `except`. The plugin can be configured to expect
12+
any number of blank lines. By default, the plugin expects no blank lines before both the
13+
`import` and `return` statements, and the `except` keyword.
1414

1515
## Installation
1616

@@ -31,11 +31,11 @@ $ flake8 --version
3131
## Configuration
3232

3333
You can set the required number of blank lines before the first `import` as well as the
34-
number of blank lines required before a `return`. This can be done from the command
35-
line:
34+
number of blank lines required before a `return` and before `except`. This can be done
35+
from the command line:
3636

3737
```shell
38-
$ flake8 --blanks-before-imports 1 --blanks-before-return 1
38+
$ flake8 --blanks-before-imports 1 --blanks-before-return 1 --blanks-before-except 1
3939
```
4040

4141
Or from one of the `setup.cfg`, `tox.ini`, or `.flake8` files:
@@ -44,6 +44,7 @@ Or from one of the `setup.cfg`, `tox.ini`, or `.flake8` files:
4444
[flake8]
4545
blanks-before-imports=1
4646
blanks-before-return=1
47+
blanks-before-except=1
4748
```
4849

4950
## Why no blank lines?
@@ -86,9 +87,27 @@ October 2022).
8687
Since zero blank lines is the style used most frequently, Flake8-plus uses that as that
8788
as the default.
8889

90+
### Before `except`
91+
92+
Neither Black, Flake8 nor Pylint enforces a specific number of blank lines preceding
93+
`except`. However, they all use zero blank lines more frequently than they use any other
94+
number of blanks. The table below shows the frequency of the number of blank lines
95+
before an `except` statement in the code bases for Black, Flake8 and Pylint (as of
96+
October 2022).
97+
98+
| Package | Total `except`s | 0 blanks | 1 blank | 2 blanks | Folder |
99+
| ------- | --------------: | -------: | ------: | -------: | ------------- |
100+
| Black | 71 | 64 | 7 | 0 | `src` |
101+
| Flake8 | 26 | 26 | 0 | 0 | `src/flake8/` |
102+
| Pylint | 285 | 283 | 2 | 0 | `pylint` |
103+
104+
Since zero blank lines is the style used most frequently, Flake8-plus uses that as that
105+
as the default.
106+
89107
## Reported problems
90108

91109
| Code |  Description |
92110
| ------ | ----------------------------------------------------------- |
93111
| PLU001 | "expected {} blank lines before first import, found {}" |
94112
| PLU002 | "expected {} blank lines before return statement, found {}" |
113+
| PLU003 | "expected {} blank lines before except, found {}" |

en_US_custom.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ ast
33
BaseVisitor
44
config
55
docstring
6+
ExceptHandler
67
ParameterSet
78
PLU
89
pragma

flake8_plus/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def __init__(
1010
self,
1111
blanks_before_imports: int = defaults.BLANKS_BEFORE_IMPORTS,
1212
blanks_before_return: int = defaults.BLANKS_BEFORE_RETURN,
13+
blanks_before_except: int = defaults.BLANKS_BEFORE_EXCEPT,
1314
):
1415
"""
1516
Initialize a `Configuration` instance.
@@ -19,6 +20,8 @@ def __init__(
1920
import statements.
2021
blanks_before_return (int): Number of blanks line expected before return
2122
statement.
23+
blanks_before_except (int): Number of blanks line expected before except.
2224
"""
2325
self.blanks_before_imports = blanks_before_imports
2426
self.blanks_before_return = blanks_before_return
27+
self.blanks_before_except = blanks_before_except

flake8_plus/defaults.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
BLANKS_BEFORE_IMPORTS = 0
44
BLANKS_BEFORE_RETURN = 0
5+
BLANKS_BEFORE_EXCEPT = 0

flake8_plus/plugin.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .version import VERSION
1212
from .visitors.plu001_visitor import PLU001Visitor
1313
from .visitors.plu002_visitor import PLU002Visitor
14+
from .visitors.plu003_visitor import PLU003Visitor
1415

1516

1617
class Plugin:
@@ -21,6 +22,7 @@ class Plugin:
2122
visitors = [
2223
PLU001Visitor,
2324
PLU002Visitor,
25+
PLU003Visitor,
2426
]
2527

2628
def __init__(self, tree: ast.AST, lines: list[str]):
@@ -71,7 +73,20 @@ def add_options(option_manager: OptionManager) -> None: # pragma: no cover
7173
"(Default: %(default)s)",
7274
)
7375

76+
option_manager.add_option(
77+
"--blanks-before-except",
78+
type="int",
79+
metavar="n",
80+
default=defaults.BLANKS_BEFORE_EXCEPT,
81+
parse_from_config=True,
82+
help="Expected number of blank lines before except. (Default: %(default)s)",
83+
)
84+
7485
@classmethod
7586
def parse_options(cls, options: Namespace) -> None: # pragma: no cover
7687
"""Parse the custom configuration options given to flake8."""
77-
cls.config = Config(options.blanks_before_imports, options.blanks_before_return)
88+
cls.config = Config(
89+
options.blanks_before_imports,
90+
options.blanks_before_return,
91+
options.blanks_before_except,
92+
)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Exception classes raised by various operations within pylint."""
2+
# pylint: disable=too-few-public-methods
3+
import ast
4+
from typing import Any
5+
6+
from ..problem import Problem
7+
from .base_visitor import BaseVisitor
8+
9+
10+
class PLU003Problem(Problem):
11+
"""Problem 003: Number of blank lines before except."""
12+
13+
code = "PLU003"
14+
format_ = "expected {} blank lines before except, found {}"
15+
16+
def __init__( # pylint: disable=unused-argument
17+
self,
18+
line_number: int,
19+
col_offset: int,
20+
blanks_actual: int,
21+
blanks_expected: int,
22+
**kwargs: dict[str, Any],
23+
):
24+
"""
25+
Initialize a `PLU003Problem` instance.
26+
27+
Args:
28+
line_number (int): The line number.
29+
col_offset (int): The column offset.
30+
blanks_actual (int): Number of actual blanks before except.
31+
blanks_expected (int): Number of expected blanks before except.
32+
"""
33+
message = PLU003Problem.format_.format(blanks_expected, blanks_actual)
34+
super().__init__(line_number, col_offset, message)
35+
36+
37+
class PLU003Visitor(BaseVisitor):
38+
"""Visitor class for the PLU003 rule."""
39+
40+
def visit_ExceptHandler(self, node: ast.ExceptHandler) -> Any:
41+
"""
42+
Visit a `ExceptHandler` node.
43+
44+
Args:
45+
node (ast.ExceptHandler): The node to visit.
46+
47+
Returns:
48+
Any: The result of calling `generic_visit`.
49+
"""
50+
# pylint: disable=invalid-name
51+
actual = self.compute_blanks_before(node)
52+
if actual != self.config.blanks_before_except:
53+
problem = PLU003Problem(
54+
node.lineno,
55+
node.col_offset,
56+
actual,
57+
self.config.blanks_before_except,
58+
)
59+
self.problems.append(problem)
60+
return self.generic_visit(node)

tests/case_files/plu003/1_blank.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
try:
2+
print(1 / 0)
3+
4+
except ZeroDivisionError:
5+
pass

tests/case_files/plu003/cases.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[
2+
{
3+
"filename": "1_blank.py",
4+
"cases": [
5+
{
6+
"expectation": { "blanks_expected": 0 },
7+
"problems": [{ "line_number": 4, "col_offset": 0, "blanks_actual": 1 }]
8+
},
9+
{
10+
"expectation": { "blanks_expected": 1 },
11+
"problems": []
12+
}
13+
]
14+
},
15+
{
16+
"filename": "no_blanks.py",
17+
"cases": [
18+
{
19+
"expectation": { "blanks_expected": 0 },
20+
"problems": []
21+
},
22+
{
23+
"expectation": { "blanks_expected": 1 },
24+
"problems": [{ "line_number": 3, "col_offset": 0, "blanks_actual": 0 }]
25+
}
26+
]
27+
}
28+
]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
try:
2+
print(1 / 0)
3+
except ZeroDivisionError:
4+
pass
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Tests for the `plu003_visitor` module."""
2+
# pylint: disable=no-self-use,too-few-public-methods
3+
import pytest
4+
5+
from flake8_plus.config import Config
6+
from flake8_plus.visitors.plu003_visitor import PLU003Problem, PLU003Visitor
7+
8+
from .util import generate_bulk_cases, generate_results
9+
10+
11+
class TestPLU003Visitor:
12+
"""Tests for the `PLU003Visitor` class."""
13+
14+
@pytest.mark.parametrize(
15+
("source_code", "expectation", "problems_expected"),
16+
generate_bulk_cases(PLU003Problem),
17+
)
18+
def test_bulk(
19+
self, source_code: str, expectation: dict[str, int], problems_expected: set[str]
20+
):
21+
"""Run bulk test cases."""
22+
blanks_expected = expectation["blanks_expected"]
23+
config = Config(blanks_before_except=blanks_expected)
24+
actual = generate_results(PLU003Visitor, config, source_code)
25+
assert actual == problems_expected

0 commit comments

Comments
 (0)