From 3f11fe629a7b89d2a3b92dce09ac5818f3904cee Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 24 Mar 2022 22:03:35 +0100 Subject: [PATCH] [refactor] Create a package for the NameChecker in pylint.checker.base --- pylint/checkers/base/__init__.py | 757 +----------------- pylint/checkers/base/name_checker/__init__.py | 25 + pylint/checkers/base/name_checker/checker.py | 588 ++++++++++++++ .../base/name_checker/naming_style.py | 175 ++++ tests/checkers/base/unittest_base.py | 260 ------ .../base/unittest_multi_naming_style.py | 176 ++++ tests/checkers/base/unittest_name_preset.py | 99 +++ 7 files changed, 1088 insertions(+), 992 deletions(-) create mode 100644 pylint/checkers/base/name_checker/__init__.py create mode 100644 pylint/checkers/base/name_checker/checker.py create mode 100644 pylint/checkers/base/name_checker/naming_style.py create mode 100644 tests/checkers/base/unittest_multi_naming_style.py create mode 100644 tests/checkers/base/unittest_name_preset.py diff --git a/pylint/checkers/base/__init__.py b/pylint/checkers/base/__init__.py index d4e9417efe..67c18d8d14 100644 --- a/pylint/checkers/base/__init__.py +++ b/pylint/checkers/base/__init__.py @@ -3,23 +3,44 @@ # Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Basic checker for Python code.""" + +__all__ = [ + "NameChecker", + "NamingStyle", + "KNOWN_NAME_TYPES_WITH_STYLE", + "SnakeCaseStyle", + "CamelCaseStyle", + "UpperCaseStyle", + "PascalCaseStyle", + "AnyStyle", +] + import collections import itertools -import re import sys -from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, Pattern, Tuple, cast +from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, cast import astroid from astroid import nodes -from pylint import constants, interfaces +from pylint import interfaces from pylint import utils as lint_utils from pylint.checkers import utils from pylint.checkers.base.basic_checker import _BasicChecker from pylint.checkers.base.comparison_checker import ComparisonChecker from pylint.checkers.base.docstring_checker import DocStringChecker +from pylint.checkers.base.name_checker import ( + KNOWN_NAME_TYPES_WITH_STYLE, + AnyStyle, + CamelCaseStyle, + NamingStyle, + PascalCaseStyle, + SnakeCaseStyle, + UpperCaseStyle, +) +from pylint.checkers.base.name_checker.checker import NameChecker from pylint.checkers.base.pass_checker import PassChecker -from pylint.checkers.utils import infer_all, is_property_deleter, is_property_setter +from pylint.checkers.utils import infer_all from pylint.reporters.ureports import nodes as reporter_nodes from pylint.utils import LinterStats from pylint.utils.utils import get_global_option @@ -33,109 +54,12 @@ from typing_extensions import Literal -class NamingStyle: - """It may seem counterintuitive that single naming style has multiple "accepted" - forms of regular expressions, but we need to special-case stuff like dunder names in method names. - """ - - ANY: Pattern[str] = re.compile(".*") - CLASS_NAME_RGX: Pattern[str] = ANY - MOD_NAME_RGX: Pattern[str] = ANY - CONST_NAME_RGX: Pattern[str] = ANY - COMP_VAR_RGX: Pattern[str] = ANY - DEFAULT_NAME_RGX: Pattern[str] = ANY - CLASS_ATTRIBUTE_RGX: Pattern[str] = ANY - - @classmethod - def get_regex(cls, name_type): - return { - "module": cls.MOD_NAME_RGX, - "const": cls.CONST_NAME_RGX, - "class": cls.CLASS_NAME_RGX, - "function": cls.DEFAULT_NAME_RGX, - "method": cls.DEFAULT_NAME_RGX, - "attr": cls.DEFAULT_NAME_RGX, - "argument": cls.DEFAULT_NAME_RGX, - "variable": cls.DEFAULT_NAME_RGX, - "class_attribute": cls.CLASS_ATTRIBUTE_RGX, - "class_const": cls.CONST_NAME_RGX, - "inlinevar": cls.COMP_VAR_RGX, - }[name_type] - - -class SnakeCaseStyle(NamingStyle): - """Regex rules for snake_case naming style.""" - - CLASS_NAME_RGX = re.compile(r"[^\W\dA-Z][^\WA-Z]+$") - MOD_NAME_RGX = re.compile(r"[^\W\dA-Z][^\WA-Z]*$") - CONST_NAME_RGX = re.compile(r"([^\W\dA-Z][^\WA-Z]*|__.*__)$") - COMP_VAR_RGX = re.compile(r"[^\W\dA-Z][^\WA-Z]*$") - DEFAULT_NAME_RGX = re.compile( - r"([^\W\dA-Z][^\WA-Z]{2,}|_[^\WA-Z]*|__[^\WA-Z\d_][^\WA-Z]+__)$" - ) - CLASS_ATTRIBUTE_RGX = re.compile(r"([^\W\dA-Z][^\WA-Z]{2,}|__.*__)$") - - -class CamelCaseStyle(NamingStyle): - """Regex rules for camelCase naming style.""" - - CLASS_NAME_RGX = re.compile(r"[^\W\dA-Z][^\W_]+$") - MOD_NAME_RGX = re.compile(r"[^\W\dA-Z][^\W_]*$") - CONST_NAME_RGX = re.compile(r"([^\W\dA-Z][^\W_]*|__.*__)$") - COMP_VAR_RGX = re.compile(r"[^\W\dA-Z][^\W_]*$") - DEFAULT_NAME_RGX = re.compile(r"([^\W\dA-Z][^\W_]{2,}|__[^\W\dA-Z_]\w+__)$") - CLASS_ATTRIBUTE_RGX = re.compile(r"([^\W\dA-Z][^\W_]{2,}|__.*__)$") - - -class PascalCaseStyle(NamingStyle): - """Regex rules for PascalCase naming style.""" - - CLASS_NAME_RGX = re.compile(r"[^\W\da-z][^\W_]+$") - MOD_NAME_RGX = re.compile(r"[^\W\da-z][^\W_]+$") - CONST_NAME_RGX = re.compile(r"([^\W\da-z][^\W_]*|__.*__)$") - COMP_VAR_RGX = re.compile(r"[^\W\da-z][^\W_]+$") - DEFAULT_NAME_RGX = re.compile(r"([^\W\da-z][^\W_]{2,}|__[^\W\dA-Z_]\w+__)$") - CLASS_ATTRIBUTE_RGX = re.compile(r"[^\W\da-z][^\W_]{2,}$") - - -class UpperCaseStyle(NamingStyle): - """Regex rules for UPPER_CASE naming style.""" - - CLASS_NAME_RGX = re.compile(r"[^\W\da-z][^\Wa-z]+$") - MOD_NAME_RGX = re.compile(r"[^\W\da-z][^\Wa-z]+$") - CONST_NAME_RGX = re.compile(r"([^\W\da-z][^\Wa-z]*|__.*__)$") - COMP_VAR_RGX = re.compile(r"[^\W\da-z][^\Wa-z]+$") - DEFAULT_NAME_RGX = re.compile(r"([^\W\da-z][^\Wa-z]{2,}|__[^\W\dA-Z_]\w+__)$") - CLASS_ATTRIBUTE_RGX = re.compile(r"[^\W\da-z][^\Wa-z]{2,}$") - - -class AnyStyle(NamingStyle): - pass - - -NAMING_STYLES = { - "snake_case": SnakeCaseStyle, - "camelCase": CamelCaseStyle, - "PascalCase": PascalCaseStyle, - "UPPER_CASE": UpperCaseStyle, - "any": AnyStyle, -} - -# Default patterns for name types that do not have styles -DEFAULT_PATTERNS = { - "typevar": re.compile( - r"^_{0,2}(?:[^\W\da-z_]+|(?:[^\W\da-z_][^\WA-Z_]+)+T?(? symbol, to be used when generating messages # about dangerous default values as arguments @@ -162,29 +86,6 @@ class AnyStyle(NamingStyle): # List of methods which can be redefined REDEFINABLE_METHODS = frozenset(("__module__",)) TYPING_FORWARD_REF_QNAME = "typing.ForwardRef" -TYPING_TYPE_VAR_QNAME = "typing.TypeVar" - - -def _redefines_import(node): - """Detect that the given node (AssignName) is inside an - exception handler and redefines an import from the tryexcept body. - - Returns True if the node redefines an import, False otherwise. - """ - current = node - while current and not isinstance(current.parent, nodes.ExceptHandler): - current = current.parent - if not current or not utils.error_of_type(current.parent, ImportError): - return False - try_block = current.parent.parent - for import_node in try_block.nodes_of_class((nodes.ImportFrom, nodes.Import)): - for name, alias in import_node.names: - if alias: - if alias == node.name: - return True - elif name == node.name: - return True - return False LOOPLIKE_NODES = ( @@ -258,71 +159,6 @@ def _loop_exits_early(loop): ) -def _is_multi_naming_match(match, node_type, confidence): - return ( - match is not None - and match.lastgroup is not None - and match.lastgroup not in EXEMPT_NAME_CATEGORIES - and (node_type != "method" or confidence != interfaces.INFERENCE_FAILURE) - ) - - -BUILTIN_PROPERTY = "builtins.property" - - -def _get_properties(config): - """Returns a tuple of property classes and names. - - Property classes are fully qualified, such as 'abc.abstractproperty' and - property names are the actual names, such as 'abstract_property'. - """ - property_classes = {BUILTIN_PROPERTY} - property_names = set() # Not returning 'property', it has its own check. - if config is not None: - property_classes.update(config.property_classes) - property_names.update( - prop.rsplit(".", 1)[-1] for prop in config.property_classes - ) - return property_classes, property_names - - -def _determine_function_name_type(node: nodes.FunctionDef, config=None): - """Determine the name type whose regex the function's name should match. - - :param node: A function node. - :param config: Configuration from which to pull additional property classes. - :type config: :class:`optparse.Values` - - :returns: One of ('function', 'method', 'attr') - :rtype: str - """ - property_classes, property_names = _get_properties(config) - if not node.is_method(): - return "function" - - if is_property_setter(node) or is_property_deleter(node): - # If the function is decorated using the prop_method.{setter,getter} - # form, treat it like an attribute as well. - return "attr" - - decorators = node.decorators.nodes if node.decorators else [] - for decorator in decorators: - # If the function is a property (decorated with @property - # or @abc.abstractproperty), the name type is 'attr'. - if isinstance(decorator, nodes.Name) or ( - isinstance(decorator, nodes.Attribute) - and decorator.attrname in property_names - ): - inferred = utils.safe_infer(decorator) - if ( - inferred - and hasattr(inferred, "qname") - and inferred.qname() in property_classes - ): - return "attr" - return "method" - - def _has_abstract_methods(node): """Determine if the given `node` has abstract methods. @@ -1597,549 +1433,6 @@ def visit_for(self, node: nodes.For) -> None: self._check_redeclared_assign_name([node.target]) -# Name types that have a style option -KNOWN_NAME_TYPES_WITH_STYLE = { - "module", - "const", - "class", - "function", - "method", - "attr", - "argument", - "variable", - "class_attribute", - "class_const", - "inlinevar", -} - -# Name types that have a 'rgx' option -KNOWN_NAME_TYPES = { - *KNOWN_NAME_TYPES_WITH_STYLE, - "typevar", -} - -DEFAULT_NAMING_STYLES = { - "module": "snake_case", - "const": "UPPER_CASE", - "class": "PascalCase", - "function": "snake_case", - "method": "snake_case", - "attr": "snake_case", - "argument": "snake_case", - "variable": "snake_case", - "class_attribute": "any", - "class_const": "UPPER_CASE", - "inlinevar": "any", -} - - -def _create_naming_options(): - name_options = [] - for name_type in sorted(KNOWN_NAME_TYPES): - human_readable_name = constants.HUMAN_READABLE_TYPES[name_type] - name_type_hyphened = name_type.replace("_", "-") - - help_msg = f"Regular expression matching correct {human_readable_name} names. " - if name_type in KNOWN_NAME_TYPES_WITH_STYLE: - help_msg += f"Overrides {name_type_hyphened}-naming-style. " - help_msg += f"If left empty, {human_readable_name} names will be checked with the set naming style." - - # Add style option for names that support it - if name_type in KNOWN_NAME_TYPES_WITH_STYLE: - default_style = DEFAULT_NAMING_STYLES[name_type] - name_options.append( - ( - f"{name_type_hyphened}-naming-style", - { - "default": default_style, - "type": "choice", - "choices": list(NAMING_STYLES.keys()), - "metavar": "