Skip to content

Commit

Permalink
Add PLU003: blank lines before except
Browse files Browse the repository at this point in the history
  • Loading branch information
sorenlind committed Oct 31, 2022
1 parent ecddaf4 commit 112d65c
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 8 deletions.
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
[![black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

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

## Installation

Expand All @@ -31,11 +31,11 @@ $ flake8 --version
## Configuration

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

```shell
$ flake8 --blanks-before-imports 1 --blanks-before-return 1
$ flake8 --blanks-before-imports 1 --blanks-before-return 1 --blanks-before-except 1
```

Or from one of the `setup.cfg`, `tox.ini`, or `.flake8` files:
Expand All @@ -44,6 +44,7 @@ Or from one of the `setup.cfg`, `tox.ini`, or `.flake8` files:
[flake8]
blanks-before-imports=1
blanks-before-return=1
blanks-before-except=1
```

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

### Before `except`

Neither Black, Flake8 nor Pylint enforces a specific number of blank lines preceding
`except`. However, they all use zero blank lines more frequently than they use any other
number of blanks. The table below shows the frequency of the number of blank lines
before an `except` statement in the code bases for Black, Flake8 and Pylint (as of
October 2022).

| Package | Total `except`s | 0 blanks | 1 blank | 2 blanks | Folder |
| ------- | --------------: | -------: | ------: | -------: | ------------- |
| Black | 71 | 64 | 7 | 0 | `src` |
| Flake8 | 26 | 26 | 0 | 0 | `src/flake8/` |
| Pylint | 285 | 283 | 2 | 0 | `pylint` |

Since zero blank lines is the style used most frequently, Flake8-plus uses that as that
as the default.

## Reported problems

| Code |  Description |
| ------ | ----------------------------------------------------------- |
| PLU001 | "expected {} blank lines before first import, found {}" |
| PLU002 | "expected {} blank lines before return statement, found {}" |
| PLU003 | "expected {} blank lines before except, found {}" |
1 change: 1 addition & 0 deletions en_US_custom.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ ast
BaseVisitor
config
docstring
ExceptHandler
ParameterSet
PLU
pragma
Expand Down
3 changes: 3 additions & 0 deletions flake8_plus/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def __init__(
self,
blanks_before_imports: int = defaults.BLANKS_BEFORE_IMPORTS,
blanks_before_return: int = defaults.BLANKS_BEFORE_RETURN,
blanks_before_except: int = defaults.BLANKS_BEFORE_EXCEPT,
):
"""
Initialize a `Configuration` instance.
Expand All @@ -19,6 +20,8 @@ def __init__(
import statements.
blanks_before_return (int): Number of blanks line expected before return
statement.
blanks_before_except (int): Number of blanks line expected before except.
"""
self.blanks_before_imports = blanks_before_imports
self.blanks_before_return = blanks_before_return
self.blanks_before_except = blanks_before_except
1 change: 1 addition & 0 deletions flake8_plus/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

BLANKS_BEFORE_IMPORTS = 0
BLANKS_BEFORE_RETURN = 0
BLANKS_BEFORE_EXCEPT = 0
17 changes: 16 additions & 1 deletion flake8_plus/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .version import VERSION
from .visitors.plu001_visitor import PLU001Visitor
from .visitors.plu002_visitor import PLU002Visitor
from .visitors.plu003_visitor import PLU003Visitor


class Plugin:
Expand All @@ -21,6 +22,7 @@ class Plugin:
visitors = [
PLU001Visitor,
PLU002Visitor,
PLU003Visitor,
]

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

option_manager.add_option(
"--blanks-before-except",
type="int",
metavar="n",
default=defaults.BLANKS_BEFORE_EXCEPT,
parse_from_config=True,
help="Expected number of blank lines before except. (Default: %(default)s)",
)

@classmethod
def parse_options(cls, options: Namespace) -> None: # pragma: no cover
"""Parse the custom configuration options given to flake8."""
cls.config = Config(options.blanks_before_imports, options.blanks_before_return)
cls.config = Config(
options.blanks_before_imports,
options.blanks_before_return,
options.blanks_before_except,
)
60 changes: 60 additions & 0 deletions flake8_plus/visitors/plu003_visitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Exception classes raised by various operations within pylint."""
# pylint: disable=too-few-public-methods
import ast
from typing import Any

from ..problem import Problem
from .base_visitor import BaseVisitor


class PLU003Problem(Problem):
"""Problem 003: Number of blank lines before except."""

code = "PLU003"
format_ = "expected {} blank lines before except, found {}"

def __init__( # pylint: disable=unused-argument
self,
line_number: int,
col_offset: int,
blanks_actual: int,
blanks_expected: int,
**kwargs: dict[str, Any],
):
"""
Initialize a `PLU003Problem` instance.
Args:
line_number (int): The line number.
col_offset (int): The column offset.
blanks_actual (int): Number of actual blanks before except.
blanks_expected (int): Number of expected blanks before except.
"""
message = PLU003Problem.format_.format(blanks_expected, blanks_actual)
super().__init__(line_number, col_offset, message)


class PLU003Visitor(BaseVisitor):
"""Visitor class for the PLU003 rule."""

def visit_ExceptHandler(self, node: ast.ExceptHandler) -> Any:
"""
Visit a `ExceptHandler` node.
Args:
node (ast.ExceptHandler): The node to visit.
Returns:
Any: The result of calling `generic_visit`.
"""
# pylint: disable=invalid-name
actual = self.compute_blanks_before(node)
if actual != self.config.blanks_before_except:
problem = PLU003Problem(
node.lineno,
node.col_offset,
actual,
self.config.blanks_before_except,
)
self.problems.append(problem)
return self.generic_visit(node)
5 changes: 5 additions & 0 deletions tests/case_files/plu003/1_blank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
try:
print(1 / 0)

except ZeroDivisionError:
pass
28 changes: 28 additions & 0 deletions tests/case_files/plu003/cases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[
{
"filename": "1_blank.py",
"cases": [
{
"expectation": { "blanks_expected": 0 },
"problems": [{ "line_number": 4, "col_offset": 0, "blanks_actual": 1 }]
},
{
"expectation": { "blanks_expected": 1 },
"problems": []
}
]
},
{
"filename": "no_blanks.py",
"cases": [
{
"expectation": { "blanks_expected": 0 },
"problems": []
},
{
"expectation": { "blanks_expected": 1 },
"problems": [{ "line_number": 3, "col_offset": 0, "blanks_actual": 0 }]
}
]
}
]
4 changes: 4 additions & 0 deletions tests/case_files/plu003/no_blanks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
try:
print(1 / 0)
except ZeroDivisionError:
pass
25 changes: 25 additions & 0 deletions tests/visitors/test_plu003_visitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Tests for the `plu003_visitor` module."""
# pylint: disable=no-self-use,too-few-public-methods
import pytest

from flake8_plus.config import Config
from flake8_plus.visitors.plu003_visitor import PLU003Problem, PLU003Visitor

from .util import generate_bulk_cases, generate_results


class TestPLU003Visitor:
"""Tests for the `PLU003Visitor` class."""

@pytest.mark.parametrize(
("source_code", "expectation", "problems_expected"),
generate_bulk_cases(PLU003Problem),
)
def test_bulk(
self, source_code: str, expectation: dict[str, int], problems_expected: set[str]
):
"""Run bulk test cases."""
blanks_expected = expectation["blanks_expected"]
config = Config(blanks_before_except=blanks_expected)
actual = generate_results(PLU003Visitor, config, source_code)
assert actual == problems_expected

0 comments on commit 112d65c

Please sign in to comment.