Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into raiseA002ForLambda
Browse files Browse the repository at this point in the history
  • Loading branch information
cielavenir committed Mar 30, 2024
2 parents 5b3cbc3 + 5f02040 commit 6936615
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 16 deletions.
9 changes: 8 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
Changelog
=========

2.2.1 (unreleased)
2.3.1 (unreleased)
------------------

- Nothing changed yet.


2.3.0 (2024-03-29)
------------------

- Add rule for builtin module name shadowing (`A005`).
[asfaltboy]


2.2.0 (2023-11-03)
------------------

Expand Down
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ A004:
An import statement is shadowing a Python builtin.

A005:
A module is shadowing a Python builtin module (e.g: `logging` or `socket`)

A006:
A lambda argument is shadowing a Python builtin.

License
Expand Down
46 changes: 42 additions & 4 deletions flake8_builtins.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from flake8 import utils as stdin_utils
from pathlib import Path

import ast
import builtins
import inspect
import sys


class BuiltinsChecker:
Expand All @@ -12,7 +14,8 @@ class BuiltinsChecker:
argument_msg = 'A002 argument "{0}" is shadowing a Python builtin'
class_attribute_msg = 'A003 class attribute "{0}" is shadowing a Python builtin'
import_msg = 'A004 import statement "{0}" is shadowing a Python builtin'
lambda_argument_msg = 'A005 lambda argument "{0}" is shadowing a Python builtin'
module_name_msg = 'A005 the module is shadowing a Python builtin module "{0}"'
lambda_argument_msg = 'A006 lambda argument "{0}" is shadowing a Python builtin'

names = []
ignore_list = {
Expand All @@ -21,6 +24,7 @@ class BuiltinsChecker:
'credits',
'_',
}
ignored_module_names = set()

def __init__(self, tree, filename):
self.tree = tree
Expand All @@ -35,6 +39,13 @@ def add_options(cls, option_manager):
comma_separated_list=True,
help='A comma separated list of builtins to skip checking',
)
option_manager.add_option(
'--builtins-allowed-modules',
metavar='builtins',
parse_from_config=True,
comma_separated_list=True,
help='A comma separated list of builtin module names to allow',
)

@classmethod
def parse_options(cls, options):
Expand All @@ -48,12 +59,26 @@ def parse_options(cls, options):
if flake8_builtins:
cls.names.update(flake8_builtins)

if options.builtins_allowed_modules is not None:
cls.ignored_module_names.update(options.builtins_allowed_modules)

if hasattr(sys, 'stdlib_module_names'):
# stdlib_module_names is only available in Python 3.10+
known_module_names = sys.stdlib_module_names
cls.module_names = {
m for m in known_module_names if m not in cls.ignored_module_names
}
else:
cls.module_names = set()

def run(self):
tree = self.tree

if self.filename == 'stdin':
lines = stdin_utils.stdin_get_value()
tree = ast.parse(lines)
else:
yield from self.check_module_name(self.filename)

for statement in ast.walk(tree):
for child in ast.iter_child_nodes(statement):
Expand Down Expand Up @@ -252,13 +277,26 @@ def check_class(self, statement):
if statement.name in self.names:
yield self.error(statement, variable=statement.name)

def error(self, statement, variable, message=None):
def error(self, statement=None, variable=None, message=None):
if not message:
message = self.assign_msg

# lineno and col_offset must be integers
return (
statement.lineno,
statement.col_offset,
statement.lineno if statement else 0,
statement.col_offset if statement else 0,
message.format(variable),
type(self),
)

def check_module_name(self, filename: str):
if not self.module_names:
return
path = Path(filename)
module_name = path.name.removesuffix('.py')
if module_name in self.module_names:
yield self.error(
None,
module_name,
message=self.module_name_msg,
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "flake8-builtins"
version = "2.2.1.dev0"
version = "2.3.1.dev0"
authors = [
{ name="Gil Forcada Codinachs", email="gil.gnome@gmail.com" },
]
Expand Down
60 changes: 50 additions & 10 deletions run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,25 @@
class FakeOptions:
builtins_ignorelist = []
builtins = None
builtins_allowed_modules = None

def __init__(self, ignore_list='', builtins=None):
def __init__(self, ignore_list='', builtins=None, builtins_allowed_modules=None):
if ignore_list:
self.builtins_ignorelist = ignore_list
if builtins:
self.builtins = builtins


def check_code(source, expected_codes=None, ignore_list=None, builtins=None):
if builtins_allowed_modules:
self.builtins_allowed_modules = builtins_allowed_modules


def check_code(
source,
expected_codes=None,
ignore_list=None,
builtins=None,
builtins_allowed_modules=None,
filename='/home/script.py',
):
"""Check if the given source code generates the given flake8 errors
If `expected_codes` is a string is converted to a list,
Expand All @@ -37,8 +47,14 @@ def check_code(source, expected_codes=None, ignore_list=None, builtins=None):
if ignore_list is None:
ignore_list = []
tree = ast.parse(textwrap.dedent(source))
checker = BuiltinsChecker(tree, '/home/script.py')
checker.parse_options(FakeOptions(ignore_list=ignore_list, builtins=builtins))
checker = BuiltinsChecker(tree, filename)
checker.parse_options(
FakeOptions(
ignore_list=ignore_list,
builtins=builtins,
builtins_allowed_modules=builtins_allowed_modules,
)
)
return_statements = list(checker.run())

assert len(return_statements) == len(expected_codes)
Expand Down Expand Up @@ -477,12 +493,36 @@ async def bla():
def test_stdin(stdin_get_value):
source = 'max = 4'
stdin_get_value.return_value = source
checker = BuiltinsChecker('', 'stdin')
checker.parse_options(FakeOptions())
ret = list(checker.run())
assert len(ret) == 1
check_code('', expected_codes='A001', filename='stdin')


def test_tuple_unpacking():
source = 'a, *(b, c) = 1, 2, 3'
check_code(source)


@pytest.mark.skipif(
sys.version_info < (3, 10),
reason='Skip A005, module testing is only supported in Python 3.10 and above',
)
def test_module_name():
source = ''
check_code(source, expected_codes='A005', filename='./temp/logging.py')


@pytest.mark.skipif(
sys.version_info < (3, 10),
reason='Skip A005, module testing is only supported in Python 3.10 and above',
)
def test_module_name_ignore_module():
source = ''
check_code(
source,
filename='./temp/logging.py',
builtins_allowed_modules=['logging'],
)


def test_module_name_not_builtin():
source = ''
check_code(source, filename='log_config')
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ description = get a test coverage report
use_develop = true
skip_install = false
deps =
pytest-cov
coverage
commands =
pytest run_tests.py --cov --cov-report term-missing
Expand Down

0 comments on commit 6936615

Please sign in to comment.