diff --git a/README.md b/README.md index 792dc45..5ec40ee 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ a linter for pandas usage, please see [pandas-vet](https://github.com/deppen8/pa | PDF020 | found private import across modules | | PDF021 | found 'np.bool' or 'np.object' (use 'np.bool_' or 'np.object_' instead) | | PDF022 | found import from 'numpy.random' | +| PDF023 | found assignment to single-letter variable | ## contributing See `contributing.md` for how to get started. diff --git a/pandas_dev_flaker/_plugins_tree/single_letter_variables.py b/pandas_dev_flaker/_plugins_tree/single_letter_variables.py new file mode 100644 index 0000000..8222579 --- /dev/null +++ b/pandas_dev_flaker/_plugins_tree/single_letter_variables.py @@ -0,0 +1,24 @@ +import ast +from typing import Iterator, Tuple + +from pandas_dev_flaker._data_tree import State, register + +MSG = "PDF023 found assignment to single-letter variable" + + +@register(ast.Assign) +def visit_Assign( + state: State, + node: ast.Assign, + parent: ast.AST, +) -> Iterator[Tuple[int, int, str]]: + + # Unpacking is represented by putting a Tuple or List within targets + if isinstance(node.targets[0], (ast.Tuple, ast.List)): + assignment_names = node.targets[0].elts + else: + assignment_names = node.targets + + for item in assignment_names: + if isinstance(item, ast.Name) and item.id != "_" and len(item.id) == 1: + yield item.lineno, item.col_offset, MSG diff --git a/tests/single_letter_variables_test.py b/tests/single_letter_variables_test.py new file mode 100644 index 0000000..ff152ba --- /dev/null +++ b/tests/single_letter_variables_test.py @@ -0,0 +1,63 @@ +import ast +import tokenize +from io import StringIO + +import pytest + +from pandas_dev_flaker.__main__ import run + + +def results(s): + return { + "{}:{}: {}".format(*r) + for r in run( + ast.parse(s), + list(tokenize.generate_tokens(StringIO(s).readline)), + ) + } + + +@pytest.mark.parametrize( + "source", + ( + pytest.param( + "ab = 3", + id="Multi-letter assignment", + ), + pytest.param( + "_ = 3", + id="Underscore assignment", + ), + pytest.param( + "ab, cd, _ = (1, 2, 3)", + id="Unpacking including an underscore", + ), + ), +) +def test_noop(source): + assert not results(source) + + +@pytest.mark.parametrize( + "source, expected", + ( + pytest.param( + "a = 3", + "1:0: PDF023 found assignment to single-letter variable", + id="Single letter variable", + ), + pytest.param( + "bar = a = 1", + "1:6: PDF023 found assignment to single-letter variable", + id="Multiple assignment", + ), + pytest.param( + "a, bar = (3, 5)", + "1:0: PDF023 found assignment to single-letter variable", + id="Unpacking", + ), + ), +) +def test_violation(source, expected): + (result,) = results(source) + assert result == expected diff --git a/tests/string_to_concatenate_test.py b/tests/string_to_concatenate_test.py index 3afb9ee..85aaf0e 100644 --- a/tests/string_to_concatenate_test.py +++ b/tests/string_to_concatenate_test.py @@ -21,7 +21,7 @@ def results(s): "source", ( pytest.param( - "a = (\n" " 'foo'\n" " 'bar'\n" ")", + "var = (\n" " 'foo'\n" " 'bar'\n" ")", id="separate lines", ), ), @@ -34,8 +34,8 @@ def test_noop(source): "source, expected", ( pytest.param( - "a = 'foo''bar'", - "1:4: PDF007 line split in two unnecessarily by 'black' formatter", + "var = 'foo''bar'", + "1:6: PDF007 line split in two unnecessarily by 'black' formatter", id="consecutive strings", ), ),