PK!uh$wemake_python_styleguide/__init__.py# -*- coding: utf-8 -*- PK!tV#wemake_python_styleguide/checker.py# -*- coding: utf-8 -*- """ Entry point to the app. Represents a :term:`checker` business entity. There's only a single checker instance that runs a lot of :term:`visitors `. .. mermaid:: :caption: Checker relation with visitors. graph TD C1[Checker] --> V1[Visitor 1] C1[Checker] --> V2[Visitor 2] C1[Checker] --> VN[Visitor N] That's how all ``flake8`` plugins work: .. mermaid:: :caption: ``flake8`` API calls order. graph LR F1[flake8] --> F2[add_options] F2 --> F3[parse_options] F3 --> F4[__init__] F4 --> F5[run] .. _checker: Checker API ----------- .. autoclass:: Checker :no-undoc-members: :exclude-members: name, version, visitors, _run_checks :special-members: __init__ """ import ast import tokenize import traceback from typing import ClassVar, Iterator, Sequence, Type from flake8.options.manager import OptionManager from typing_extensions import final from wemake_python_styleguide import constants, types from wemake_python_styleguide import version as pkg_version from wemake_python_styleguide.options.config import Configuration from wemake_python_styleguide.options.validation import validate_options from wemake_python_styleguide.presets.types import file_tokens as tokens_preset from wemake_python_styleguide.presets.types import filename as filename_preset from wemake_python_styleguide.presets.types import tree as tree_preset from wemake_python_styleguide.transformations.ast_tree import transform from wemake_python_styleguide.visitors import base VisitorClass = Type[base.BaseVisitor] @final class Checker(object): """ Implementation of :term:`checker`. See also: http://flake8.pycqa.org/en/latest/plugin-development/index.html Attributes: name: required by the ``flake8`` API, should match the package name. version: required by the ``flake8`` API, defined in the packaging file. config: custom configuration object used to provide and parse options: :class:`wemake_python_styleguide.options.config.Configuration`. options: option structure passed by ``flake8``: :class:`wemake_python_styleguide.types.ConfigurationOptions`. visitors: :term:`preset` of visitors that are run by this checker. """ name: ClassVar[str] = pkg_version.pkg_name version: ClassVar[str] = pkg_version.pkg_version options: types.ConfigurationOptions config = Configuration() _visitors: ClassVar[Sequence[VisitorClass]] = ( *filename_preset.PRESET, *tree_preset.PRESET, *tokens_preset.PRESET, ) def __init__( self, tree: ast.AST, file_tokens: Sequence[tokenize.TokenInfo], filename: str = constants.STDIN, ) -> None: """ Creates new checker instance. These parameter names should not be changed. ``flake8`` has special API that passes concrete parameters to the plugins that ask for them. ``flake8`` also decides how to execute this plugin based on its parameters. This one is executed once per module. Arguments: tree: ``ast`` parsed by ``flake8``. Differs from ``ast.parse`` since it is mutated by multiple ``flake8`` plugins. Why mutated? Since it is really expensive to copy all ``ast`` information in terms of memory. file_tokens: ``tokenize.tokenize`` parsed file tokens. filename: module file name, might be empty if piping is used. """ self.tree = transform(tree) self.filename = filename self.file_tokens = file_tokens @classmethod def add_options(cls, parser: OptionManager) -> None: """ ``flake8`` api method to register new plugin options. See :class:`wemake_python_styleguide.options.config.Configuration` docs for detailed options reference. Arguments: parser: ``flake8`` option parser instance. """ cls.config.register_options(parser) @classmethod def parse_options(cls, options: types.ConfigurationOptions) -> None: """Parses registered options for providing them to each visitor.""" cls.options = validate_options(options) def run(self) -> Iterator[types.CheckResult]: """ Runs the checker. This method is used by ``flake8`` API. It is executed after all configuration is parsed. Yields: Violations that were found by the passed visitors. """ yield from self._run_checks(self._visitors) def _run_checks( self, visitors: Sequence[VisitorClass], ) -> Iterator[types.CheckResult]: """Runs all passed visitors one by one.""" for visitor_class in visitors: visitor = visitor_class.from_checker(self) try: visitor.run() except Exception: # In case we fail misserably, we want users to see at # least something! Full stack trace # and some rules that still work. print(traceback.format_exc()) # noqa: T001 for error in visitor.violations: yield (*error.node_items(), type(self)) PK!uh+wemake_python_styleguide/compat/__init__.py# -*- coding: utf-8 -*- PK!%)*wemake_python_styleguide/compat/aliases.py# -*- coding: utf-8 -*- """ Here we store useful aliases to make sure code works between versions. Please, document everything you do. Add links to the changes in the changelog if possible. And provide links to the python source code. """ import ast from typing_extensions import Final #: We need this tuple to easily check that this is a real assign node. AssignNodes: Final = (ast.Assign, ast.AnnAssign) #: We need this tuple since ``async def`` now has its own ast class. FunctionNodes: Final = (ast.FunctionDef, ast.AsyncFunctionDef) #: We need this tuple since ``ast.AsyncFor``` was introduced. ForNodes: Final = (ast.For, ast.AsyncFor) #: We need this tuple since ``ast.AsyncWith`` was introduced. WithNodes: Final = (ast.With, ast.AsyncWith) PK!scYY,wemake_python_styleguide/compat/functions.py# -*- coding: utf-8 -*- import ast from typing import List from wemake_python_styleguide.types import AnyAssign def get_assign_targets(node: AnyAssign) -> List[ast.expr]: """Returns list of assign targets without knowing the type of assign.""" if isinstance(node, ast.AnnAssign): return [node.target] return node.targets PK!k  (wemake_python_styleguide/compat/nodes.py# -*- coding: utf-8 -*- import ast try: # pragma: no cover from ast import Constant as Constant # type: ignore # noqa: WPS433, WPS113 except ImportError: # pragma: no cover class Constant(ast.AST): # type: ignore # noqa: WPS440 """ Fallback for pythons that do not have ``ast.Constant``. In this case ``Constant`` is replaced with: - ``ast.Num`` - ``ast.Str`` and ``ast.Bytes`` - ``ast.NameConstant`` Only ``python3.8+`` has this node. """ PK!kfn%wemake_python_styleguide/constants.py# -*- coding: utf-8 -*- """ This module contains list of white- and black-listed ``python`` members. It contains lists of keywords and built-in functions we discourage to use. It also contains some exceptions that we allow to use in our codebase. """ import re from typing_extensions import Final #: List of functions we forbid to use. FUNCTIONS_BLACKLIST: Final = frozenset(( # Code generation: 'eval', 'exec', 'compile', # Termination: 'exit', 'quit', # Magic: 'globals', 'locals', 'vars', 'dir', # IO: 'input', # print is handled via `flake8-print` 'breakpoint', # Attribute access: 'hasattr', 'delattr', # Gratis: 'copyright', 'help', 'credits', # Dynamic imports: '__import__', # OOP: 'staticmethod', # Mypy: 'reveal_type', )) #: List of module metadata we forbid to use. MODULE_METADATA_VARIABLES_BLACKLIST: Final = frozenset(( '__author__', '__all__', '__version__', '__about__', )) #: List of variable names we forbid to use. VARIABLE_NAMES_BLACKLIST: Final = frozenset(( # Meaningless words: 'data', 'result', 'results', 'item', 'items', 'value', 'values', 'val', 'vals', 'var', 'vars', 'variable', 'content', 'contents', 'info', 'handle', 'handler', 'file', 'obj', 'objects', 'objs', 'some', 'do', 'param', 'params', 'parameters', # Confuseables: 'no', 'true', 'false', # Names from examples: 'foo', 'bar', 'baz', )) #: List of special names that are used only as first argument in methods. SPECIAL_ARGUMENT_NAMES_WHITELIST: Final = frozenset(( 'self', 'cls', 'mcs', )) #: List of all magic methods from the python docs. ALL_MAGIC_METHODS: Final = frozenset(( '__new__', '__init__', '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__delattr__', '__dir__', '__get__', '__set__', '__delete__', '__set_name__', '__init_subclass__', '__instancecheck__', '__subclasscheck__', '__class_getitem__', '__call__', '__len__', '__length_hint__', '__getitem__', '__setitem__', '__delitem__', '__missing__', '__iter__', '__reversed__', '__contains__', '__add__', '__sub__', '__mul__', '__matmul__', '__truediv__', '__floordiv__', '__mod__', '__divmod__', '__pow__', '__lshift__', '__rshift__', '__and__', '__xor__', '__or__', '__radd__', '__rsub__', '__rmul__', '__rmatmul__', '__rtruediv__', '__rfloordiv__', '__rmod__', '__rdivmod__', '__rpow__', '__rlshift__', '__rrshift__', '__rand__', '__rxor__', '__ror__', '__iadd__', '__isub__', '__imul__', '__imatmul__', '__itruediv__', '__ifloordiv__', '__imod__', '__ipow__', '__ilshift__', '__irshift__', '__iand__', '__ixor__', '__ior__', '__neg__', '__pos__', '__abs__', '__invert__', '__complex__', '__int__', '__float__', '__index__', '__round__', '__trunc__', '__floor__', '__ceil__', '__enter__', '__exit__', '__await__', '__aiter__', '__anext__', '__aenter__', '__aexit__', )) #: List of magic methods that are forbidden to use. MAGIC_METHODS_BLACKLIST: Final = frozenset(( # Since we don't use `del`: '__del__', '__delitem__', '__delete__', '__dir__', # since we don't use `dir()` '__delattr__', # since we don't use `delattr()` )) #: List of magic methods that are not allowed to be generators. YIELD_MAGIC_METHODS_BLACKLIST: Final = ALL_MAGIC_METHODS.difference({ # Allowed to be used with ``yield`` keyowrd: '__iter__', }) #: List of magic methods that are not allowed to be async. ASYNC_MAGIC_METHODS_BLACKLIST: Final = ALL_MAGIC_METHODS.difference({ # In order of appearance on # https://docs.python.org/3/reference/datamodel.html#basic-customization # Allowed magic methods are: '__anext__', '__aenter__', '__aexit__', }) #: List of nested classes' names we allow to use. NESTED_CLASSES_WHITELIST: Final = frozenset(( 'Meta', # django forms, models, drf, etc 'Params', # factoryboy specific )) #: List of builtin classes that are allowed to subclass. ALLOWED_BUILTIN_CLASSES: Final = frozenset(( 'type', 'object', )) #: List of nested functions' names we allow to use. NESTED_FUNCTIONS_WHITELIST: Final = frozenset(( 'decorator', 'factory', )) #: List of allowed ``__future__`` imports. FUTURE_IMPORTS_WHITELIST: Final = frozenset(( 'annotations', 'generator_stop', )) #: List of blacklisted module names. MODULE_NAMES_BLACKLIST: Final = frozenset(( 'util', 'utils', 'utilities', 'helpers', )) #: List of allowed module magic names. MAGIC_MODULE_NAMES_WHITELIST: Final = frozenset(( '__init__', '__main__', )) #: List of bad magic module functions. MAGIC_MODULE_NAMES_BLACKLIST: Final = frozenset(( '__getattr__', '__dir__', )) #: Regex pattern to name modules. MODULE_NAME_PATTERN: Final = re.compile(r'^_?_?[a-z][a-z\d_]*[a-z\d](__)?$') #: Common numbers that are allowed to be used without being called "magic". MAGIC_NUMBERS_WHITELIST: Final = frozenset(( 0, # both int and float 0.5, 100, 1000, 1024, # bytes 24, # hours 60, # seconds, minutes 1j, # imaginary part of a complex number )) #: Maximum amount of ``noqa`` comments per module. MAX_NOQA_COMMENTS: Final = 10 #: Maximum amount of ``pragma`` no-cover comments per module. MAX_NO_COVER_COMMENTS: Final = 5 #: Maximum length of ``yield`` ``tuple`` expressions. MAX_LEN_YIELD_TUPLE: Final = 5 # Internal variables # They are not publicly documented since they are not used by the end user. # Used as a default filename, when it is not passed by flake8: STDIN: Final = 'stdin' # Used as a special name patterns for unused variables, like _, __: UNUSED_VARIABLE_REGEX: Final = re.compile(r'^_+$') # Used to specify as a placeholder for `__init__`: INIT: Final = '__init__' # Allowed magic number modulo: NON_MAGIC_MODULO: Final = 10 # Used to specify a pattern which checks variables and modules for underscored # numbers in their names: UNDERSCORED_NUMBER_PATTERN: Final = re.compile(r'.+\D\_\d+(\D|$)') PK!-5%wemake_python_styleguide/formatter.py# -*- coding: utf-8 -*- """ Our very own ``flake8`` formatter for better error messages. That's how all ``flake8`` formatters work: .. mermaid:: :caption: ``flake8`` formatting API calls order. graph LR F2[start] --> F3[after_init] F3 --> F4[start] F4 --> F5[beggining] F5 --> F6[handle] F6 --> F7[format] F6 --> F8[show_source] F6 --> F9[show_statistic] F7 --> F10[finished] F8 --> F10[finished] F9 --> F10[finished] F10 -.-> F5 F10 --> F11[stop] .. autoclass:: WemakeFormatter :no-undoc-members: """ from collections import defaultdict from typing import ClassVar, DefaultDict, List from flake8.formatting.base import BaseFormatter from flake8.statistics import Statistics from flake8.style_guide import Violation from pygments import highlight from pygments.formatters import TerminalFormatter from pygments.lexers import PythonLexer from typing_extensions import Final from wemake_python_styleguide.version import pkg_version #: That url is generated and hosted by Sphinx. DOCS_URL_TEMPLATE: Final = ( 'https://wemake-python-stylegui.de/en/{0}/pages/usage/violations/' ) class WemakeFormatter(BaseFormatter): # noqa: WPS214 """ We need to format our style :term:`violations ` beatifully. The default formatter does not allow us to do that. What things do we miss? 1. Spacing, everything is just mixed up and glued together 2. Colors and decoration, some information is easier to gather just with colors or underlined text 3. Grouping, we need explicit grouping by filename 4. Incomplete and non-informative statistics """ _doc_url: ClassVar[str] = DOCS_URL_TEMPLATE.format(pkg_version) # API: def after_init(self): """Called after the original ``init`` is used to set extra fields.""" self._lexer = PythonLexer() self._formatter = TerminalFormatter() # Logic: self._proccessed_filenames: List[str] = [] self._error_count = 0 def handle(self, error: Violation) -> None: # noqa: WPS110 """Processes each :term:`violation` to print it and all related.""" if error.filename not in self._proccessed_filenames: self._print_header(error.filename) self._proccessed_filenames.append(error.filename) super().handle(error) self._error_count += 1 def format(self, error: Violation) -> str: # noqa: A003 """Called to format each individual :term:`violation`.""" return '{newline} {row_col:<8} {code:<5} {text}'.format( newline=self.newline if self._should_show_source(error) else '', code=error.code, text=error.text, row_col='{0}:{1}'.format(error.line_number, error.column_number), ) def show_source(self, error: Violation) -> str: """Called when ``--show-source`` option is provided.""" if not self._should_show_source(error): return '' formated_line = error.physical_line.lstrip() adjust = len(error.physical_line) - len(formated_line) code = _highlight( formated_line, self._lexer, self._formatter, ) return ' {code} {pointer}^'.format( code=code, pointer=' ' * (error.column_number - 1 - adjust), ) def show_statistics(self, statistics: Statistics) -> None: # noqa: WPS210 """Called when ``--statistic`` option is passed.""" all_errors = 0 for error_code in statistics.error_codes(): stats_for_error_code = statistics.statistics_for(error_code) statistic = next(stats_for_error_code) count = statistic.count count += sum(stat.count for stat in stats_for_error_code) all_errors += count error_by_file = _count_per_filename(statistics, error_code) self._write( '{newline}{error_code}: {message}'.format( newline=self.newline, error_code=_bold(error_code), message=statistic.message, ), ) for filename in error_by_file: self._write( ' {error_count:<5} {filename}'.format( error_count=error_by_file[filename], filename=filename, ), ) self._write(_underline('Total: {0}'.format(count))) self._write(self.newline) self._write(_underline(_bold('All errors: {0}'.format(all_errors)))) def stop(self) -> None: """Runs once per app when the formatting ends.""" if self._error_count: message = '{0}Full list of violations and explanations:{0}{1}' self._write(message.format(self.newline, self._doc_url)) # Our own methods: def _print_header(self, filename: str) -> None: self._write( '{newline}{filename}'.format( filename=_underline(_bold(filename)), newline=self.newline, ), ) def _should_show_source(self, error: Violation) -> bool: return self.options.show_source and error.physical_line is not None # Formatting text: def _bold(text: str) -> str: r""" Returns bold formatted text. >>> _bold('Hello!') '\x1b[1mHello!\x1b[0m' """ return '\033[1m{0}\033[0m'.format(text) def _underline(text: str) -> str: r""" Returns underlined formatted text. >>> _underline('Hello!') '\x1b[4mHello!\x1b[0m' """ return '\033[4m{0}\033[0m'.format(text) def _highlight(source: str, lexer, formatter) -> str: """ Highlights source code. Might fail. See also: https://github.com/wemake-services/wemake-python-styleguide/issues/794 """ try: return highlight(source, lexer, formatter) except Exception: # pragma: no cover # Might fail on some systems, when colors are set incorrectly, # or not available at all. In this case code will be just text. return source # Helpers: def _count_per_filename( statistics: Statistics, error_code: str, ) -> DefaultDict[str, int]: filenames: DefaultDict[str, int] = defaultdict(int) stats_for_error_code = statistics.statistics_for(error_code) for stat in stats_for_error_code: filenames[stat.filename] += stat.count return filenames PK!uh*wemake_python_styleguide/logic/__init__.py# -*- coding: utf-8 -*- PK!uh4wemake_python_styleguide/logic/arguments/__init__.py# -*- coding: utf-8 -*- PK!vqdd9wemake_python_styleguide/logic/arguments/function_args.py# -*- coding: utf-8 -*- import ast from itertools import zip_longest from typing import Dict, Iterator, List, Optional, Tuple from wemake_python_styleguide import types from wemake_python_styleguide.logic.arguments import method_args def get_starred_args(call: ast.Call) -> Iterator[ast.Starred]: """Gets ``ast.Starred`` arguments from ``ast.Call``.""" for argument in call.args: if isinstance(argument, ast.Starred): yield argument def has_same_vararg( node: types.AnyFunctionDefAndLambda, call: ast.Call, ) -> bool: """Tells whether ``call`` has the same vararg ``*args`` as ``node``.""" vararg_name: Optional[str] = None for starred_arg in get_starred_args(call): # 'args': [<_ast.Starred object at 0x10d77a3c8>] if isinstance(starred_arg.value, ast.Name): vararg_name = starred_arg.value.id else: # We can judge on things like `*[]` return False if vararg_name and node.args.vararg: return node.args.vararg.arg == vararg_name return node.args.vararg == vararg_name # type: ignore def has_same_kwarg(node: types.AnyFunctionDefAndLambda, call: ast.Call) -> bool: """Tells whether ``call`` has the same kwargs as ``node``.""" kwarg_name: Optional[str] = None null_arg_keywords = filter(lambda key: key.arg is None, call.keywords) for keyword in null_arg_keywords: # `a=1` vs `**kwargs`: # {'arg': 'a', 'value': <_ast.Num object at 0x1027882b0>} # {'arg': None, 'value': <_ast.Name object at 0x102788320>} if isinstance(keyword.value, ast.Name): kwarg_name = keyword.value.id else: # We can judge on things like `**{}` return False if node.args.kwarg and kwarg_name: return node.args.kwarg.arg == kwarg_name return node.args.kwarg == kwarg_name # type: ignore def has_same_args(node: types.AnyFunctionDefAndLambda, call: ast.Call) -> bool: """Tells whether ``call`` has the same positional args as ``node``.""" node_args = method_args.get_args_without_special_argument(node) paired_arguments = zip_longest(call.args, node_args) for call_arg, func_arg in paired_arguments: if isinstance(call_arg, ast.Starred): # nevertheless `*args` is vararg ensure there is no # plain arg defined on corresponding position if isinstance(func_arg, ast.arg): return False elif isinstance(call_arg, ast.Name): # for each found call arg there should be not null # same func arg defined on the same position if not func_arg or call_arg.id != func_arg.arg: return False else: return False return True def _clean_call_keyword_args( call: ast.Call, ) -> Tuple[Dict[str, ast.keyword], List[ast.keyword]]: prepared_kw_args = {} real_kw_args = [] for kw in call.keywords: if isinstance(kw.value, ast.Name) and kw.arg == kw.value.id: prepared_kw_args[kw.arg] = kw if not (isinstance(kw.value, ast.Name) and kw.arg is None): # We need to remove ** args from here: real_kw_args.append(kw) return prepared_kw_args, real_kw_args def has_same_kw_args( node: types.AnyFunctionDefAndLambda, call: ast.Call, ) -> bool: """Tells whether ``call`` has the same keyword-only args as ``node``.""" prepared_kw_args, real_kw_args = _clean_call_keyword_args(call) for func_arg in node.args.kwonlyargs: func_arg_name = getattr(func_arg, 'arg', None) call_arg = prepared_kw_args.get(func_arg_name) if func_arg and not call_arg: return False return len(real_kw_args) == len(node.args.kwonlyargs) def is_call_matched_by_arguments( node: types.AnyFunctionDefAndLambda, call: ast.Call, ) -> bool: """Tells whether ``call`` is matched by arguments of ``node``.""" same_vararg = has_same_vararg(node, call) same_kwarg = has_same_kwarg(node, call) same_args = has_same_args(node, call) same_kw_args = has_same_kw_args(node, call) return same_vararg and same_kwarg and same_args and same_kw_args PK!~,  7wemake_python_styleguide/logic/arguments/method_args.py# -*- coding: utf-8 -*- import ast from typing import List from wemake_python_styleguide import constants, types def get_args_without_special_argument( node: types.AnyFunctionDefAndLambda, ) -> List[ast.arg]: """Gets ``node`` arguments excluding ``self``, ``cls``, ``mcs``.""" node_args = node.args.args if not node_args or isinstance(node, ast.Lambda): return node_args if node_args[0].arg not in constants.SPECIAL_ARGUMENT_NAMES_WHITELIST: return node_args return node_args[1:] PK!ʇ",6wemake_python_styleguide/logic/arguments/super_args.py# -*- coding: utf-8 -*- import ast from typing import Dict, Optional def get_keyword_args_by_names( call: ast.Call, *names: str, ) -> Dict[str, ast.expr]: """Returns keywords of ``call`` by specified ``names``.""" keyword_args = {} for keyword in call.keywords: if keyword.arg in names: keyword_args[keyword.arg] = keyword.value return keyword_args def is_super_called_with(call: ast.Call, type_: str, object_: str) -> bool: """Tells whether super ``call`` was done with ``type_`` and ``object_``.""" arg1: Optional[ast.expr] arg2: Optional[ast.expr] if len(call.args) == 2: # branch for super(Test, self) arg1 = call.args[0] arg2 = call.args[1] elif len(call.keywords) == 2: # branch for super(t=Test, obj=self) keyword_args = get_keyword_args_by_names(call, 't', 'obj') arg1 = keyword_args.get('t') arg2 = keyword_args.get('obj') else: # branch for super(Test, obj=self) arg1 = call.args[0] arg2 = call.keywords[0].value is_expected_type = isinstance(arg1, ast.Name) and arg1.id == type_ is_expected_object = isinstance(arg2, ast.Name) and arg2.id == object_ return is_expected_type and is_expected_object def get_super_call(node: ast.AST) -> Optional[ast.Call]: """Returns given ``node`` if it represents ``super`` ``ast.Call``.""" if not isinstance(node, ast.Call): return None if not isinstance(node.func, ast.Name) or node.func.id != 'super': return None return node def is_ordinary_super_call(node: ast.AST, class_name: str) -> bool: """ Tells whether super ``call`` is ordinary. By ordinary we mean: - either call without arguments:: super().function() - or call with our class and self arguments:: super(Class, self).function() Any other combination of arguments is considered as unordinary by this function. """ call = get_super_call(node) if call is None: return False args_number = len(call.args) + len(call.keywords) if args_number == 0: return True return args_number == 2 and is_super_called_with( call, type_=class_name, object_='self', ) PK!eMVV)wemake_python_styleguide/logic/classes.py# -*- coding: utf-8 -*- from typing import Optional from wemake_python_styleguide.constants import ALLOWED_BUILTIN_CLASSES from wemake_python_styleguide.logic.naming.builtins import is_builtin_name def is_forbidden_super_class(class_name: Optional[str]) -> bool: """ Tells whether or not the base class is forbidden to be subclassed. >>> is_forbidden_super_class('str') True >>> is_forbidden_super_class('Exception') False >>> is_forbidden_super_class('object') False >>> is_forbidden_super_class('type') False >>> is_forbidden_super_class('CustomName') False >>> is_forbidden_super_class(None) False """ if not class_name or not class_name.islower(): return False if class_name in ALLOWED_BUILTIN_CLASSES: return False return is_builtin_name(class_name) PK!CC-wemake_python_styleguide/logic/collections.py# -*- coding: utf-8 -*- import ast from typing import List, Sequence def normalize_dict_elements(node: ast.Dict) -> Sequence[ast.AST]: """ Normalizes ``dict`` elements and enforces consistent order. We had a problem that some ``dict`` objects might not have some keys. Example:: some_dict = {**one, **two} This ``dict`` contains two values and zero keys. This function will normalize this structure to use values instead of missing keys. See also: https://github.com/wemake-services/wemake-python-styleguide/issues/450 """ elements: List[ast.AST] = [] for dict_key, dict_value in zip(node.keys, node.values): if dict_key is None: elements.append(dict_value) # type: ignore else: elements.append(dict_key) return elements PK!~(Z*wemake_python_styleguide/logic/compares.py# -*- coding: utf-8 -*- import ast import types from collections import defaultdict from typing import DefaultDict, Mapping, Set, Tuple, Type, Union import attr from typing_extensions import Final, final from wemake_python_styleguide.logic import source @final @attr.dataclass(frozen=True, slots=True) class _Bounds(object): """Represents the bounds we use to calculate the similar compare nodes.""" lower_bound: Set[ast.Compare] = attr.ib(factory=set) upper_bound: Set[ast.Compare] = attr.ib(factory=set) _MultipleCompareOperators = Tuple[Type[ast.cmpop], ...] #: Type to represent `SIMILAR_OPERATORS` constant. _ComparesMapping = Mapping[ Type[ast.cmpop], _MultipleCompareOperators, ] #: Used to track the operator usages in `a > b and b >c` compares. _OperatorUsages = DefaultDict[str, _Bounds] #: Constant to define similar operators. SIMILAR_OPERATORS: Final[_ComparesMapping] = types.MappingProxyType({ ast.Gt: (ast.Gt, ast.GtE), ast.GtE: (ast.Gt, ast.GtE), ast.Lt: (ast.Lt, ast.LtE), ast.LtE: (ast.Lt, ast.LtE), }) def get_similar_operators( operator: ast.cmpop, ) -> Union[Type[ast.cmpop], _MultipleCompareOperators]: """Returns similar operators types for the given operator.""" operator_type = operator.__class__ return SIMILAR_OPERATORS.get(operator_type, operator_type) @final class CompareBounds(object): """ Calculates bounds of expressions like ``a > b and b > c`` in python. Later we call ``.is_valid()`` method to be sure that we raise violations for incorrect bounds. Credit goes to: https://github.com/PyCQA/pylint/blob/master/pylint/checkers/refactoring.py """ def __init__(self, node: ast.BoolOp) -> None: """Conctructs the basic data to calculate the bounds.""" self._node = node self._uses: _OperatorUsages = defaultdict(_Bounds) def is_valid(self) -> bool: """We say that bounds are invalid, when we can refactor them.""" local_uses = self._build_bounds().values() for bounds in local_uses: num_shared = len( bounds.lower_bound.intersection(bounds.upper_bound), ) num_lower_bounds = len(bounds.lower_bound) num_upper_bounds = len(bounds.upper_bound) if num_shared < num_lower_bounds and num_shared < num_upper_bounds: return False return True def _build_bounds(self) -> _OperatorUsages: for comparison_node in self._node.values: if isinstance(comparison_node, ast.Compare): self._find_lower_upper_bounds(comparison_node) return self._uses def _find_lower_upper_bounds( self, comparison_node: ast.Compare, ) -> None: left_operand = comparison_node.left comparators = zip(comparison_node.ops, comparison_node.comparators) for operator, right_operand in comparators: for operand in (left_operand, right_operand): self._mutate( comparison_node, operator, source.node_to_string(operand), operand is left_operand, ) left_operand = right_operand def _mutate( self, comparison_node: ast.Compare, operator: ast.cmpop, name: str, is_left: bool, ) -> None: key_name = None if isinstance(operator, (ast.Lt, ast.LtE)): key_name = 'lower_bound' if is_left else 'upper_bound' elif isinstance(operator, (ast.Gt, ast.GtE)): key_name = 'upper_bound' if is_left else 'lower_bound' if key_name: getattr(self._uses[name], key_name).add(comparison_node) PK!'?Ť,wemake_python_styleguide/logic/exceptions.py# -*- coding: utf-8 -*- import ast from typing import Optional def get_exception_name(node: ast.Raise) -> Optional[str]: """Returns the exception name or ``None`` if node has not it.""" exception = node.exc if exception is None: return None exception_func = getattr(exception, 'func', None) if exception_func: exception = exception_func return getattr(exception, 'id', None) PK!+wemake_python_styleguide/logic/filenames.py# -*- coding: utf-8 -*- from pathlib import PurePath def get_stem(file_path: str) -> str: """ Returns the last element of path without extension. >>> get_stem('/some/module.py') 'module' >>> get_stem('C:/User/package/__init__.py') '__init__' >>> get_stem('c:/package/abc.py') 'abc' >>> get_stem('episode2.py') 'episode2' """ return PurePath(file_path).stem PK!#+wemake_python_styleguide/logic/functions.py# -*- coding: utf-8 -*- from ast import Call, Yield, YieldFrom, arg from typing import Container, List, Optional from wemake_python_styleguide.logic import source from wemake_python_styleguide.logic.walk import is_contained from wemake_python_styleguide.types import ( AnyFunctionDef, AnyFunctionDefAndLambda, ) def given_function_called(node: Call, to_check: Container[str]) -> str: """ Returns function name if it is called and contained in the container. >>> import ast >>> module = ast.parse('print(123, 456)') >>> given_function_called(module.body[0].value, ['print']) 'print' >>> given_function_called(module.body[0].value, ['adjust']) '' """ function_name = source.node_to_string(node.func) if function_name in to_check: return function_name return '' def is_method(function_type: Optional[str]) -> bool: """ Returns whether a given function type belongs to a class. >>> is_method('function') False >>> is_method(None) False >>> is_method('method') True >>> is_method('classmethod') True >>> is_method('staticmethod') True >>> is_method('') False """ return function_type in {'method', 'classmethod', 'staticmethod'} def get_all_arguments(node: AnyFunctionDefAndLambda) -> List[arg]: """ Returns list of all arguments that exist in a function. Respects the correct parameters order. Positional args, ``*args``, keyword-only, ``**kwargs``. """ names = [ *node.args.args, *node.args.kwonlyargs, ] if node.args.vararg: names.insert(len(node.args.args), node.args.vararg) if node.args.kwarg: names.append(node.args.kwarg) return names def is_first_argument(node: AnyFunctionDefAndLambda, name: str) -> bool: """Tells whether an argument name is the logically first in function.""" if not node.args.args: return False return name == node.args.args[0].arg def is_generator(node: AnyFunctionDef) -> bool: """Tells whether a given function is a generator.""" for body_item in node.body: if is_contained(node=body_item, to_check=(Yield, YieldFrom)): return True return False PK!XGNN%wemake_python_styleguide/logic/ifs.py# -*- coding: utf-8 -*- import ast from typing import Iterable, List, Optional, Union from wemake_python_styleguide.types import AnyNodes _IfAndElifASTNode = Union[ast.If, List[ast.stmt]] def has_elif(node: ast.If) -> bool: """Tells if this node is a part of a ``if`` chain or just a single one.""" return getattr(node, 'wps_if_chain', False) # noqa: WPS425 def root_if(node: ast.If) -> Optional[ast.If]: """Returns the previous ``if`` node in the chain if it exists.""" return getattr(node, 'wps_if_chained', None) def chain(node: ast.If) -> Iterable[_IfAndElifASTNode]: """ Yields the whole chain of ``if`` statements. This function also does go not up in the tree to find all parent ``if`` nodes. The rest order is preserved. The first one to return is the node itself. The last element of array is always a list of expressions that represent the last ``elif`` or ``else`` node in the chain. That's ugly, but that's how ``ast`` works in python. """ iterator: _IfAndElifASTNode = node yield iterator while True: if not isinstance(iterator, ast.If): return next_if = iterator.orelse if len(next_if) == 1 and isinstance(next_if[0], ast.If): yield next_if[0] iterator = next_if[0] else: yield next_if iterator = next_if def has_nodes( to_check: AnyNodes, iterable: Iterable[ast.AST], ) -> bool: """Finds the given nodes types in ``if`` body.""" return any( isinstance(line, to_check) for line in iterable ) PK!7)wemake_python_styleguide/logic/imports.py# -*- coding: utf-8 -*- from typing import List from wemake_python_styleguide.types import AnyImport def get_import_parts(node: AnyImport) -> List[str]: """Returns list of import modules.""" module_path = getattr(node, 'module', '') or '' return module_path.split('.') PK!uh1wemake_python_styleguide/logic/naming/__init__.py# -*- coding: utf-8 -*- PK!kd  /wemake_python_styleguide/logic/naming/access.py# -*- coding: utf-8 -*- from wemake_python_styleguide.constants import UNUSED_VARIABLE_REGEX def is_unused(name: str) -> bool: """ Checks whether the given ``name`` is unused. >>> is_unused('_') True >>> is_unused('___') True >>> is_unused('_protected') False >>> is_unused('__private') False """ return UNUSED_VARIABLE_REGEX.match(name) is not None def is_magic(name: str) -> bool: """ Checks whether the given ``name`` is magic. >>> is_magic('__init__') True >>> is_magic('some') False >>> is_magic('cli') False >>> is_magic('_') False >>> is_magic('__version__') True >>> is_magic('__main__') True """ return name.startswith('__') and name.endswith('__') def is_private(name: str) -> bool: """ Checks if name has private name pattern. >>> is_private('regular') False >>> is_private('__private') True >>> is_private('_protected') False >>> is_private('__magic__') False >>> is_private('_') False """ return name.startswith('__') and not is_magic(name) def is_protected(name: str) -> bool: """ Checks if name has protected name pattern. >>> is_protected('_protected') True >>> is_protected('__private') False >>> is_protected('__magic__') False >>> is_protected('common_variable') False >>> is_protected('_') False """ if not name.startswith('_'): return False if is_unused(name): return False return not is_private(name) and not is_magic(name) def is_public(name: str) -> bool: """ Tells if this name is public. >>> is_public('public') True >>> is_public('_') False >>> is_public('_protected') False >>> is_public('__private') False >>> is_public('__magic__') False """ return ( not is_protected(name) and not is_private(name) and not is_magic(name) and not is_unused(name) ) PK!CM1wemake_python_styleguide/logic/naming/builtins.py# -*- coding: utf-8 -*- import keyword from flake8_builtins import BUILTINS from wemake_python_styleguide.logic.naming.access import is_magic, is_unused ALL_BUILTINS = frozenset(( *keyword.kwlist, *BUILTINS, # Special case. # Some python version have them, some do not have them: 'async', 'await', )) def is_builtin_name(variable_name: str) -> bool: """ Tells whether a variable name is builtin or not. >>> is_builtin_name('str') True >>> is_builtin_name('_') False >>> is_builtin_name('custom') False >>> is_builtin_name('Exception') True >>> is_builtin_name('async') True """ return variable_name in ALL_BUILTINS def is_wrong_alias(variable_name: str) -> bool: """ Tells whether a variable is wrong builtins alias or not. >>> is_wrong_alias('regular_name_') True >>> is_wrong_alias('_') False >>> is_wrong_alias('_async') False >>> is_wrong_alias('_await') False >>> is_wrong_alias('regular_name') False >>> is_wrong_alias('class_') False >>> is_wrong_alias('list_') False >>> is_wrong_alias('list') False >>> is_wrong_alias('__spec__') False """ if is_magic(variable_name): return False if is_unused(variable_name) or not variable_name.endswith('_'): return False return not is_builtin_name(variable_name[:-1]) PK!NJ2wemake_python_styleguide/logic/naming/constants.py# -*- coding: utf-8 -*- from wemake_python_styleguide.logic.naming.access import is_unused def is_constant(name: str) -> bool: """ Checks whether the given ``name`` is a constant. >>> is_constant('CONST') True >>> is_constant('ALLOWED_EMPTY_LINE_TOKEN') True >>> is_constant('Some') False >>> is_constant('_') False >>> is_constant('lower_case') False """ if is_unused(name): return False return all( # We check that constant names consist of: # UPPERCASE LETTERS and `_` char character.isupper() or character == '_' for character in name ) PK!v0wemake_python_styleguide/logic/naming/logical.py# -*- coding: utf-8 -*- from typing import Iterable from wemake_python_styleguide import constants from wemake_python_styleguide.logic.naming import access def is_wrong_name(name: str, to_check: Iterable[str]) -> bool: """ Checks that name is not prohibited by explicitly listing it's name. >>> is_wrong_name('wrong', ['wrong']) True >>> is_wrong_name('correct', ['wrong']) False >>> is_wrong_name('_wrong', ['wrong']) True >>> is_wrong_name('wrong_', ['wrong']) True >>> is_wrong_name('wrong__', ['wrong']) False >>> is_wrong_name('__wrong', ['wrong']) False """ for name_to_check in to_check: choices_to_check = { name_to_check, '_{0}'.format(name_to_check), '{0}_'.format(name_to_check), } if name in choices_to_check: return True return False def is_upper_case_name(name: str) -> bool: """ Checks that attribute name has no upper-case letters. >>> is_upper_case_name('camelCase') True >>> is_upper_case_name('UPPER_CASE') True >>> is_upper_case_name('camel_Case') True >>> is_upper_case_name('snake_case') False >>> is_upper_case_name('snake') False >>> is_upper_case_name('snake111') False >>> is_upper_case_name('__variable_v2') False """ return any(character.isupper() for character in name) def is_too_short_name( name: str, min_length: int, *, trim: bool = True, ) -> bool: """ Checks for too short names. >>> is_too_short_name('test', min_length=2) False >>> is_too_short_name('o', min_length=2) True >>> is_too_short_name('_', min_length=2) False >>> is_too_short_name('z1', min_length=2) False >>> is_too_short_name('z', min_length=1) False >>> is_too_short_name('_z', min_length=2, trim=True) True >>> is_too_short_name('z_', min_length=2, trim=True) True >>> is_too_short_name('z_', min_length=2, trim=False) False >>> is_too_short_name('__z', min_length=2, trim=True) True >>> is_too_short_name('xy', min_length=2, trim=True) False """ if access.is_unused(name): return False if trim: name = name.strip('_') return len(name) < min_length def is_too_long_name( name: str, max_length: int, ) -> bool: """ Checks for too long names. >>> is_too_long_name('test', max_length=4) False >>> is_too_long_name('_', max_length=4) False >>> is_too_long_name('test', max_length=3) True >>> is_too_long_name('this_is_twentynine_characters', max_length=29) False """ return len(name) > max_length def does_contain_underscored_number(name: str) -> bool: """ Checks for names with underscored number. >>> does_contain_underscored_number('star_wars_episode2') False >>> does_contain_underscored_number('come2_me') False >>> does_contain_underscored_number('_') False >>> does_contain_underscored_number('z1') False >>> does_contain_underscored_number('iso123_456') False >>> does_contain_underscored_number('star_wars_episode_2') True >>> does_contain_underscored_number('come_2_me') True >>> does_contain_underscored_number('come_44_me') True >>> does_contain_underscored_number('iso_123_456') True """ return constants.UNDERSCORED_NUMBER_PATTERN.match(name) is not None def does_contain_consecutive_underscores(name: str) -> bool: """ Checks if name contains consecutive underscores in middle of name. >>> does_contain_consecutive_underscores('name') False >>> does_contain_consecutive_underscores('__magic__') False >>> does_contain_consecutive_underscores('__private') False >>> does_contain_consecutive_underscores('name') False >>> does_contain_consecutive_underscores('some__value') True >>> does_contain_consecutive_underscores('__some__value__') True >>> does_contain_consecutive_underscores('__private__value') True >>> does_contain_consecutive_underscores('some_value__') True """ if access.is_magic(name) or access.is_private(name): return '__' in name.strip('_') return '__' in name def does_contain_unicode(name: str) -> bool: """ Check if name contains unicode characters. >>> does_contain_unicode('hello_world1') False >>> does_contain_unicode('') False >>> does_contain_unicode('привет_мир1') True >>> does_contain_unicode('russian_техт') True """ try: name.encode('ascii') except UnicodeEncodeError: return True else: return False PK!E!= = 3wemake_python_styleguide/logic/naming/name_nodes.py# -*- coding: utf-8 -*- import ast import itertools from typing import Iterable, List, Optional from wemake_python_styleguide.compat.functions import get_assign_targets from wemake_python_styleguide.types import AnyAssign def is_same_variable(left: ast.AST, right: ast.AST) -> bool: """Ensures that nodes are the same variable.""" if isinstance(left, ast.Name) and isinstance(right, ast.Name): return left.id == right.id return False def get_assigned_name(node: ast.AST) -> Optional[str]: """ Returns variable names for node that is just assigned. Returns ``None`` for nodes that are used in a different manner. """ if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store): return node.id if isinstance(node, ast.Attribute) and isinstance(node.ctx, ast.Store): return node.attr if isinstance(node, ast.ExceptHandler): return node.name return None def flat_variable_names(nodes: Iterable[AnyAssign]) -> Iterable[str]: """ Returns flat variable names from several nodes. Use this function when you need to get list of string variable names from assign nodes. Here's an example: >>> import ast >>> tree = ast.parse('x: int = 0') >>> node = tree.body[0] >>> list(flat_variable_names([node])) ['x'] >>> tree = ast.parse('z = y = 0') >>> node = tree.body[0] >>> list(flat_variable_names([node])) ['z', 'y'] """ return itertools.chain.from_iterable(( get_variables_from_node(target) for node in nodes for target in get_assign_targets(node) )) def get_variables_from_node(node: ast.AST) -> List[str]: """ Gets the assigned names from the list of nodes. Can be used with any nodes that operate with ``ast.Name`` or ``ast.Tuple`` as targets for the assignment. Can be used with nodes like ``ast.Assign``, ``ast.Tuple``, ``ast.For``, ``ast.With``, etc. """ names: List[str] = [] naive_attempt = extract_name(node) if naive_attempt: names.append(naive_attempt) elif isinstance(node, ast.Tuple): for subnode in node.elts: extracted_name = get_variables_from_node(subnode) if extracted_name: names.extend(extracted_name) return names def extract_name(node: ast.AST) -> Optional[str]: """ Utility to extract names for several types of nodes. Is used to get name from node in case it is ``ast.Name``. Should not be used direclty with assigns, use safer :py:`~get_assign_names` function. Example: >>> import ast >>> tree = ast.parse('a') >>> node = tree.body[0].value >>> extract_name(node) 'a' """ if isinstance(node, ast.Starred): node = node.value if isinstance(node, ast.Name): return node.id return None PK!QE**'wemake_python_styleguide/logic/nodes.py# -*- coding: utf-8 -*- import ast from typing import Optional from wemake_python_styleguide.types import ContextNodes def is_literal(node: ast.AST) -> bool: """ Checks for nodes that contains only constants. If the node contains only literals it will be evaluated. When node relies on some other names, it won't be evaluated. """ try: ast.literal_eval(node) except ValueError: return False else: return True def get_parent(node: ast.AST) -> Optional[ast.AST]: """Returns the parent node or ``None`` if node has no parent.""" return getattr(node, 'wps_parent', None) def get_context(node: ast.AST) -> Optional[ContextNodes]: """Returns the context or ``None`` if node has no context.""" return getattr(node, 'wps_context', None) PK!F+wemake_python_styleguide/logic/operators.py# -*- coding: utf-8 -*- import ast from typing import Optional, Type from wemake_python_styleguide.logic.nodes import get_parent def unwrap_unary_node(node: ast.AST) -> ast.AST: """ Returns a real unwrapped node from the unary wrapper. It recursively unwraps any level of unary operators. Returns the node itself if it is not wrapped in unary operator. """ while True: if not isinstance(node, ast.UnaryOp): return node node = node.operand def unwrap_starred_node(node: ast.AST) -> ast.AST: """Unwraps the unary ``*`` starred node.""" if isinstance(node, ast.Starred): return node.value return node def get_parent_ignoring_unary(node: ast.AST) -> Optional[ast.AST]: """ Returns real parent ignoring proxy unary parent level. What can go wrong? 1. Number can be negative: ``x = -1``, so ``1`` has ``UnaryOp`` as parent, but should return ``Assign`` 2. Some values can be negated: ``x = --some``, so ``some`` has ``UnaryOp`` as parent, but should return ``Assign`` """ while True: parent = get_parent(node) if parent is None or not isinstance(parent, ast.UnaryOp): return parent node = parent def count_unary_operator( node: ast.AST, operator: Type[ast.unaryop], amount: int = 0, ) -> int: """Returns amount of unary operators matching input.""" parent = get_parent(node) if parent is None or not isinstance(parent, ast.UnaryOp): return amount if isinstance(parent.op, operator): return count_unary_operator(parent, operator, amount + 1) return count_unary_operator(parent, operator, amount) PK!jlTJJ-wemake_python_styleguide/logic/prop_access.py# -*- coding: utf-8 -*- import ast from typing import Iterable, Optional from wemake_python_styleguide.types import AnyAccess def _chained_item(iterator: ast.AST) -> Optional[ast.AST]: if isinstance(iterator, (ast.Attribute, ast.Subscript)): return iterator.value elif isinstance(iterator, ast.Call): return iterator.func return None def parts(node: AnyAccess) -> Iterable[ast.AST]: """ Returns all ``.`` separated elements for attributes and subscripts. Attributes might be complex: .. code:: python self.profiler._store[cache_id].execute() We need all parts from it. """ iterator: ast.AST = node while True: yield iterator chained_item = _chained_item(iterator) if chained_item is None: return iterator = chained_item PK! +wemake_python_styleguide/logic/safe_eval.py# -*- coding: utf-8 -*- from ast import ( AST, Add, BinOp, Bytes, Dict, List, Name, NameConstant, Num, Set, Str, Sub, Tuple, UAdd, UnaryOp, USub, ) from typing import Any, Optional, Union from wemake_python_styleguide.compat.nodes import Constant def _convert_num(node: AST): if isinstance(node, Constant): # pragma: no cover if isinstance(node.value, (int, float, complex)): return node.value elif isinstance(node, Num): return node.n elif isinstance(node, Name): # We return string names as is, see how we return strings: return node.id raise ValueError('malformed node or string: {0!r}'.format(node)) def _convert_signed_num(node: AST): if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)): operand = _convert_num(node.operand) return +operand if isinstance(node.op, UAdd) else -operand return _convert_num(node) def _convert_complex(node: BinOp) -> Optional[complex]: left = _convert_signed_num(node.left) right = _convert_num(node.right) if isinstance(left, (int, float)) and isinstance(right, complex): if isinstance(node.op, Add): return left + right return left - right return None def _convert_iterable(node: Union[Tuple, List, Set, Dict]): if isinstance(node, Tuple): return tuple(map(literal_eval_with_names, node.elts)) elif isinstance(node, List): return list(map(literal_eval_with_names, node.elts)) elif isinstance(node, Set): return set(map(literal_eval_with_names, node.elts)) return dict(zip( map(literal_eval_with_names, node.keys), map(literal_eval_with_names, node.values), )) def literal_eval_with_names(node: AST) -> Optional[Any]: """ Safely evaluate constants and ``ast.Name`` nodes. We need this function to tell that ``[name]`` and ``[name]`` are the same nodes. Copied from the CPython's source code. Modified to treat ``ast.Name`` nodes as constants. See: :py:`ast.literal_eval` source. """ if isinstance(node, (Constant, NameConstant)): return node.value elif isinstance(node, (Str, Bytes, Num)): # We wrap strings to tell the difference between strings and names: return node.n if isinstance(node, Num) else '"{0}"'.format(node.s) elif isinstance(node, (Tuple, List, Set, Dict)): return _convert_iterable(node) elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)): maybe_complex = _convert_complex(node) if maybe_complex is not None: return maybe_complex return _convert_signed_num(node) PK!(wemake_python_styleguide/logic/scopes.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from typing import ClassVar, DefaultDict, Set, cast from typing_extensions import final from wemake_python_styleguide.logic.naming import access, name_nodes from wemake_python_styleguide.logic.nodes import get_context from wemake_python_styleguide.types import ContextNodes #: That's how we represent scopes that are bound to contexts. _ContextStore = DefaultDict[ContextNodes, Set[str]] class _BaseScope(object): """Base class for scope operations.""" @final def __init__(self, node: ast.AST) -> None: """Saving current node and context.""" self._node = node self._context = cast(ContextNodes, get_context(self._node)) def add_to_scope(self, names: Set[str]) -> None: # pragma: no cover """Adds a given set of names to some scope.""" raise NotImplementedError() def shadowing(self, names: Set[str]) -> Set[str]: # pragma: no cover """Tells either some shadowing exist between existing scopes.""" raise NotImplementedError() @final def _exclude_unused(self, names: Set[str]) -> Set[str]: """Removes unused variables from set of names.""" return { var_name # we allow to reuse explicit `_` variables for var_name in names if not access.is_unused(var_name) } @final class BlockScope(_BaseScope): """Represents the visibility scope of a variable in a block.""" #: Updated when we have a new block variable. _block_scopes: ClassVar[_ContextStore] = defaultdict(set) #: Updated when we have a new local variable. _local_scopes: ClassVar[_ContextStore] = defaultdict(set) def add_to_scope( self, names: Set[str], *, is_local: bool = False, ) -> None: """Adds a set of names to the specified scope.""" scope = self._get_scope(is_local=is_local) scope[self._context] = scope[self._context].union( self._exclude_unused(names), ) def shadowing( self, names: Set[str], *, is_local: bool = False, ) -> Set[str]: """Calculates the intersection for a set of names and a context.""" if not names: return set() scope = self._get_scope(is_local=not is_local) current_names = scope[self._context] if not is_local: # Why do we care to update the scope for block variables? # Because, block variables cannot shadow each other. scope = self._get_scope(is_local=is_local) current_names = current_names.union(scope[self._context]) return set(current_names).intersection(names) def _get_scope(self, *, is_local: bool = False) -> _ContextStore: return self._local_scopes if is_local else self._block_scopes @final class OuterScope(_BaseScope): """Represents scoping store to check name shadowing.""" _scopes: ClassVar[_ContextStore] = defaultdict(set) def add_to_scope(self, names: Set[str]) -> None: """Adds a set of variables to the context scope.""" if isinstance(self._context, ast.ClassDef): # Class names are not available to the caller directly. return self._scopes[self._context] = self._scopes[self._context].union( self._exclude_unused(names), ) def shadowing(self, names: Set[str]) -> Set[str]: """Calculates the intersection for a set of names and a context.""" if isinstance(self._context, ast.ClassDef): # Class names are not available to the caller directly. return set() current_names = self._build_outer_context() return set(current_names).intersection(names) def _build_outer_context(self) -> Set[str]: outer_names: Set[str] = set() context = self._context while True: context = cast(ContextNodes, get_context(context)) outer_names = outer_names.union(self._scopes[context]) if not context: break return outer_names def extract_names(node: ast.AST) -> Set[str]: """Extracts unique set of names from a given node.""" return set(name_nodes.get_variables_from_node(node)) PK![(wemake_python_styleguide/logic/source.py# -*- coding: utf-8 -*- import ast import astor def node_to_string(node: ast.AST) -> str: """Returns the source code by doing ``ast`` to string convert.""" return astor.to_source(node).strip() PK!e pp)wemake_python_styleguide/logic/strings.py# -*- coding: utf-8 -*- import ast def is_doc_string(node: ast.stmt) -> bool: """ Tells whether or not the given node is a docstring. We call docstrings any string nodes that are placed right after function, class, or module definition. """ if not isinstance(node, ast.Expr): return False return isinstance(node.value, ast.Str) PK! G dd(wemake_python_styleguide/logic/tokens.py# -*- coding: utf-8 -*- import tokenize from typing import Container, Iterable, Tuple def split_prefixes(token: tokenize.TokenInfo) -> Tuple[str, str]: """ Splits string token by prefixes and the quoted content. Returns the tuple of modifiers and untouched string contents. >>> import tokenize >>> import token >>> token = tokenize.TokenInfo(token.STRING, "Br'test'", 1, 9, "Br'test'") >>> split_prefixes(token) ('Br', "'test'") """ split = token.string.split(token.string[-1]) return split[0], token.string.replace(split[0], '', 1) def has_triple_string_quotes(string_contents: str) -> bool: """Tells whether string token is written as inside triple quotes.""" if string_contents.startswith('"""') and string_contents.endswith('"""'): return True elif string_contents.startswith("'''") and string_contents.endswith("'''"): return True return False def only_contains( tokens: Iterable[tokenize.TokenInfo], container: Container[int], ) -> bool: """Determins that only tokens from the given list are contained.""" for token in tokens: if token.exact_type not in container: return False return True def get_comment_text(token: tokenize.TokenInfo) -> str: """Returns comment without `#` char from comment tokens.""" return token.string[1:].strip() PK!ǧ+wemake_python_styleguide/logic/variables.py# -*- coding: utf-8 -*- import ast from typing import Union VarDefinition = Union[ast.AST, ast.expr] def _is_valid_single(node: VarDefinition) -> bool: if isinstance(node, ast.Name): return True if isinstance(node, ast.Starred) and isinstance(node.value, ast.Name): return True return False def is_valid_block_variable_definition(node: VarDefinition) -> bool: """Is used to check either block variables are correctly defined.""" if isinstance(node, ast.Tuple): for var_definition in node.elts: if not _is_valid_single(var_definition): return False return True return _is_valid_single(node) PK!s&wemake_python_styleguide/logic/walk.py# -*- coding: utf-8 -*- import ast from typing import Iterator, Optional, Type, TypeVar, Union from wemake_python_styleguide.logic.nodes import get_parent from wemake_python_styleguide.types import AnyNodes _SubnodeType = TypeVar('_SubnodeType', bound=ast.AST) _IsInstanceContainer = Union[AnyNodes, type] def is_contained( node: ast.AST, to_check: _IsInstanceContainer, ) -> bool: """ Checks whether node does contain given subnode types. Goes down by the tree to check all children. """ for child in ast.walk(node): if isinstance(child, to_check): return True return False def get_closest_parent( node: ast.AST, parents: _IsInstanceContainer, ) -> Optional[ast.AST]: """Returns the closes parent of a node of requested types.""" parent = get_parent(node) while True: if parent is None: return None if isinstance(parent, parents): return parent parent = get_parent(parent) def is_child_of(node: ast.AST, parents: _IsInstanceContainer) -> bool: """ Checks whether node is inside a given set of parents or not. Goes up by the tree of ``node`` to check all parents. Works with general types. """ closest_parent = get_closest_parent(node, parents) return closest_parent is not None def is_contained_by(node: ast.AST, container: ast.AST) -> bool: """ Tells you if a node is contained by a given node. Goes up by the tree of ``node`` to check all parents. Works with specific instances. """ parent = get_parent(node) while True: if parent is None: return False if parent == container: return True parent = get_parent(parent) def get_subnodes_by_type( node: ast.AST, subnodes_type: Type[_SubnodeType], ) -> Iterator[_SubnodeType]: """Returns the list of subnodes of given node with given subnode type.""" for child in ast.walk(node): if isinstance(child, subnodes_type): yield child PK!uh,wemake_python_styleguide/options/__init__.py# -*- coding: utf-8 -*- PK!ԏר&&*wemake_python_styleguide/options/config.py# -*- coding: utf-8 -*- """ Provides configuration options for ``wemake-python-styleguide``. We do not like our linter to be highly configurable. Since, people may take the wrong path or make wrong decisions. We try to make all defaults as reasonable as possible. However, you can currently adjust some complexity options. Why? Because we are not quite sure about the ideal values. All options are configurable via ``flake8`` CLI. .. code:: ini flake8 --max-returns=2 --max-arguments=4 Or you can provide options in ``setup.cfg`` or similar supported files. .. code:: ini [flake8] max-returns = 2 max-arguments = 4 We use ``setup.cfg`` as a default way to provide configuration. You can also show all options that ``flake8`` supports by running: .. code:: bash flake8 --help .. rubric:: General options - ``min-name-length`` - minimum number of chars to define a valid variable and module name, defaults to :str:`wemake_python_styleguide.options.defaults.MIN_NAME_LENGTH` - ``max-name-length`` - maximum number of chars to define a valid variable and module name, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_NAME_LENGTH` - ``i-control-code`` - whether you control ones who use your code, more rules are enforced when you do control it, defaults to :str:`wemake_python_styleguide.options.defaults.I_CONTROL_CODE` .. rubric:: Complexity options - ``max-returns`` - maximum allowed number of ``return`` statements in one function, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_RETURNS` - ``max-local-variables`` - maximum allowed number of local variables in one function, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_LOCAL_VARIABLES` - ``max-expressions`` - maximum allowed number of expressions in one function, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_EXPRESSIONS` - ``max-arguments`` - maximum allowed number of arguments in one function, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_ARGUMENTS` - ``max-module-members`` - maximum number of classes and functions in a single module, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_MODULE_MEMBERS` - ``max-methods`` - maximum number of methods in a single class, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_METHODS` - ``max-line-complexity`` - maximum line complexity measured in number of ``ast`` nodes per line, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_LINE_COMPLEXITY` - ``max-jones-score`` - maximum Jones score for a module, which is equal to the median of all lines complexity sum, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_JONES_SCORE` - ``max-imports`` - maximum number of imports in a single module, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_IMPORTS` - ``max-imported-names`` - maximum number of imported names in a single module, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_IMPORTED_NAMES` - ``max-base-classes`` - maximum number of parent classes inside a class definition, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_BASE_CLASSES` - ``max-decorators`` - maximum number of decorators for single function or class definition, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_DECORATORS` - ``max-string-usages`` - maximum number of repeated string constants in your modules, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_DECORATORS` - ``max-awaits`` - maximum allowed number of ``await`` expressions in one function, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_AWAITS` - ``max-try-body-length`` - maximum amount of ``try`` node body length, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_TRY_BODY_LENGTH` - ``max-module-expressions`` - maximum number of expression usages in a module, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_MODULE_EXPRESSIONS` - ``max-function-expressions`` - maximum number of expression usages in a function or method, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_FUNCTION_EXPRESSIONS` - ``max-asserts`` - maximum number of ``assert`` statements in a function, default to :str:`wemake_python_styleguide.options.defaults.MAX_ASSERTS` - ``max-access-level`` - maximum number of access level in an expression, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_ACCESS_LEVEL` - ``max-attributes`` - maximum number of public instance attributes, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_ATTRIBUTES` """ from typing import ClassVar, Mapping, Optional, Sequence, Union import attr from flake8.options.manager import OptionManager from typing_extensions import final from wemake_python_styleguide.options import defaults #: Immutable config values passed from `flake8`. ConfigValues = Mapping[str, Union[str, int, bool]] @final @attr.dataclass(frozen=True, slots=True) class _Option(object): """Represents ``flake8`` option object.""" long_option_name: str default: int help: str type: Optional[str] = 'int' # noqa: A003 parse_from_config: bool = True action: str = 'store' def __attrs_post_init__(self): """Is called after regular init is done.""" object.__setattr__( # noqa: WPS609 self, 'help', ' '.join((self.help, 'Defaults to: %default')), ) @final class Configuration(object): """Simple configuration store with all options.""" _options: ClassVar[Sequence[_Option]] = [ # Complexity: _Option( '--max-returns', defaults.MAX_RETURNS, 'Maximum allowed number of return statements in one function.', ), _Option( '--max-local-variables', defaults.MAX_LOCAL_VARIABLES, 'Maximum allowed number of local variables in one function.', ), _Option( '--max-expressions', defaults.MAX_EXPRESSIONS, 'Maximum allowed number of expressions in one function.', ), _Option( '--max-arguments', defaults.MAX_ARGUMENTS, 'Maximum allowed number of arguments in one function.', ), _Option( '--max-module-members', defaults.MAX_MODULE_MEMBERS, 'Maximum number of classes and functions in a single module.', ), _Option( '--max-methods', defaults.MAX_METHODS, 'Maximum number of methods in a single class.', ), _Option( '--max-line-complexity', defaults.MAX_LINE_COMPLEXITY, 'Maximum line complexity, measured in `ast` nodes.', ), _Option( '--max-jones-score', defaults.MAX_JONES_SCORE, 'Maximum median module complexity, based on sum of lines.', ), _Option( '--max-imports', defaults.MAX_IMPORTS, 'Maximum number of imports in a single module.', ), _Option( '--max-imported-names', defaults.MAX_IMPORTED_NAMES, 'Maximum number of imported names in a single module.', ), _Option( '--max-base-classes', defaults.MAX_BASE_CLASSES, 'Maximum number of base classes.', ), _Option( '--max-decorators', defaults.MAX_DECORATORS, 'Maximum number of decorators.', ), _Option( '--max-string-usages', defaults.MAX_STRING_USAGES, 'Maximum number of string constant usages.', ), _Option( '--max-awaits', defaults.MAX_AWAITS, 'Maximum allowed number of await expressions in one function.', ), _Option( '--max-try-body-length', defaults.MAX_TRY_BODY_LENGTH, 'Maximum amount of try block node body length.', ), _Option( '--max-module-expressions', defaults.MAX_MODULE_EXPRESSIONS, 'Maximum amount of expression usages in a module.', ), _Option( '--max-function-expressions', defaults.MAX_FUNCTION_EXPRESSIONS, 'Maximum amount of expression usages in a function or method.', ), _Option( '--max-asserts', defaults.MAX_ASSERTS, 'Maximum allowed number of assert statements in one function.', ), _Option( '--max-access-level', defaults.MAX_ACCESS_LEVEL, 'Maximum number of access level in an expression.', ), _Option( '--max-attributes', defaults.MAX_ATTRIBUTES, 'Maximum number of public instance attributes.', ), # General: _Option( '--min-name-length', defaults.MIN_NAME_LENGTH, 'Minimum required length of variable and module names.', ), _Option( '--max-name-length', defaults.MAX_NAME_LENGTH, 'Maximum possible length of the variable and module names.', ), _Option( '--i-control-code', defaults.I_CONTROL_CODE, 'Whether you control ones who use your code.', action='store_true', type=None, ), ] def register_options(self, parser: OptionManager) -> None: """Registers options for our plugin.""" for option in self._options: parser.add_option(**attr.asdict(option)) PK!7BV V ,wemake_python_styleguide/options/defaults.py# -*- coding: utf-8 -*- """ Constants with default values for plugin's configuration. We try to stick to "the magical 7 ± 2 number". https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two What does it mean? It means that we choose these values based on our mind capacity. And it is really hard to keep in mind more that 9 objects at the same time. These values can be changed in the ``setup.cfg`` file on a per-project bases, if you find them too strict or too permissive. """ from typing_extensions import Final # General: #: Minimum variable's name length. MIN_NAME_LENGTH: Final = 2 #: Maximum variable and module name length: MAX_NAME_LENGTH: Final = 45 #: Whether you control ones who use your code. I_CONTROL_CODE: Final = True # Complexity: #: Maximum number of `return` statements allowed in a single function. MAX_RETURNS: Final = 5 # 7-2 #: Maximum number of local variables in a function. MAX_LOCAL_VARIABLES: Final = 5 # 7-2 #: Maximum number of expressions in a single function. MAX_EXPRESSIONS: Final = 9 # 7+2 #: Maximum number of arguments for functions or methods. MAX_ARGUMENTS: Final = 5 # 7-2 #: Maximum number of classes and functions in a single module. MAX_MODULE_MEMBERS: Final = 7 # 7 #: Maximum number of methods in a single class. MAX_METHODS: Final = 7 # the same as module members #: Maximum line complexity. MAX_LINE_COMPLEXITY: Final = 14 # 7 * 2, also almost guessed #: Maximum median module Jones complexity. MAX_JONES_SCORE: Final = 12 # guessed #: Maximum number of imports in a single module. MAX_IMPORTS: Final = 12 # guessed #: Maximum number of imported names in a single module. MAX_IMPORTED_NAMES: Final = 50 # guessed #: Maximum number of base classes. MAX_BASE_CLASSES: Final = 3 # guessed #: Maximum number of decorators. MAX_DECORATORS: Final = 5 # 7-2 #: Maximum number of same string usage in code. MAX_STRING_USAGES: Final = 3 # guessed #: Maximum number of ``await`` expressions for functions or methods. MAX_AWAITS: Final = 5 # the same as returns #: Maximum amount of ``try`` node body length. MAX_TRY_BODY_LENGTH: Final = 1 # best practice #: Maximum amount of same expressions per module. MAX_MODULE_EXPRESSIONS: Final = 7 # the same as module elements #: Maximum amount of same expressions per function. MAX_FUNCTION_EXPRESSIONS: Final = 4 # guessed #: Maximum number of ``assert`` statements in a function. MAX_ASSERTS: Final = 5 # 7-2 #: Maximum number of access level in an expression. MAX_ACCESS_LEVEL: Final = 4 # guessed #: Maximum number of public attributes in a single class. MAX_ATTRIBUTES: Final = 6 # guessed PK!  .wemake_python_styleguide/options/validation.py# -*- coding: utf-8 -*- from typing import Optional import attr from typing_extensions import final from wemake_python_styleguide.types import ConfigurationOptions def _min_max( min: Optional[int] = None, # noqa: A002 max: Optional[int] = None, # noqa: A002 ): """Validator to check that value is in bounds.""" def factory(instance, attribute, field_value): min_contract = min is not None and field_value < min max_contract = max is not None and field_value > max if min_contract or max_contract: raise ValueError('Option {0} is out of bounds: {1}'.format( attribute.name, field_value, )) return factory @final @attr.dataclass(slots=True) class _ValidatedOptions(object): """ Here we write all the required structured validation for the options. It is an internal class and is not used anywhere else. """ # General: min_name_length: int = attr.ib(validator=[_min_max(min=1)]) i_control_code: bool max_name_length: int = attr.ib(validator=[_min_max(min=1)]) # Complexity: max_arguments: int = attr.ib(validator=[_min_max(min=1)]) max_local_variables: int = attr.ib(validator=[_min_max(min=1)]) max_returns: int = attr.ib(validator=[_min_max(min=1)]) max_expressions: int = attr.ib(validator=[_min_max(min=1)]) max_module_members: int = attr.ib(validator=[_min_max(min=1)]) max_methods: int = attr.ib(validator=[_min_max(min=1)]) max_line_complexity: int = attr.ib(validator=[_min_max(min=1)]) max_jones_score: int = attr.ib(validator=[_min_max(min=1)]) max_imports: int = attr.ib(validator=[_min_max(min=1)]) max_imported_names: int = attr.ib(validator=[_min_max(min=1)]) max_base_classes: int = attr.ib(validator=[_min_max(min=1)]) max_decorators: int = attr.ib(validator=[_min_max(min=1)]) max_string_usages: int = attr.ib(validator=[_min_max(min=1)]) max_awaits: int = attr.ib(validator=[_min_max(min=1)]) max_try_body_length: int = attr.ib(validator=[_min_max(min=1)]) max_module_expressions: int = attr.ib(validator=[_min_max(min=1)]) max_function_expressions: int = attr.ib(validator=[_min_max(min=1)]) max_asserts: int = attr.ib(validator=[_min_max(min=1)]) max_access_level: int = attr.ib(validator=[_min_max(min=1)]) max_attributes: int = attr.ib(validator=[_min_max(min=1)]) def validate_options(options: ConfigurationOptions) -> _ValidatedOptions: """Validates all options from ``flake8``, uses a subset of them.""" fields_to_validate = [ field.name for field in attr.fields(_ValidatedOptions) ] options_subset = { field: getattr(options, field, None) for field in fields_to_validate } return _ValidatedOptions(**options_subset) # raises TypeError PK!}{??,wemake_python_styleguide/presets/__init__.py# -*- coding: utf-8 -*- """See :term:`preset` in the docs.""" PK!uh3wemake_python_styleguide/presets/topics/__init__.py# -*- coding: utf-8 -*- PK!335wemake_python_styleguide/presets/topics/complexity.py# -*- coding: utf-8 -*- from typing_extensions import Final from wemake_python_styleguide.visitors.ast.complexity import ( access, classes, counts, function, jones, nested, offset, overuses, ) #: Used to store all complexity related visitors to be later passed to checker: PRESET: Final = ( function.FunctionComplexityVisitor, jones.JonesComplexityVisitor, nested.NestedComplexityVisitor, offset.OffsetVisitor, counts.ImportMembersVisitor, counts.ModuleMembersVisitor, counts.MethodMembersVisitor, counts.ConditionsVisitor, counts.ElifVisitor, counts.TryExceptVisitor, counts.YieldTupleVisitor, classes.ClassComplexityVisitor, overuses.StringOveruseVisitor, overuses.ExpressionOveruseVisitor, access.AccessVisitor, ) PK!uh2wemake_python_styleguide/presets/types/__init__.py# -*- coding: utf-8 -*- PK!$[5wemake_python_styleguide/presets/types/file_tokens.py# -*- coding: utf-8 -*- from typing_extensions import Final from wemake_python_styleguide.visitors.tokenize import ( comments, conditions, keywords, primitives, statements, ) #: Used to store all token related visitors to be later passed to checker: PRESET: Final = ( comments.WrongCommentVisitor, comments.FileMagicCommentsVisitor, keywords.WrongKeywordTokenVisitor, primitives.WrongNumberTokenVisitor, primitives.WrongStringTokenVisitor, primitives.WrongStringConcatenationVisitor, statements.ExtraIndentationVisitor, statements.BracketLocationVisitor, conditions.IfElseVisitor, ) PK!֨2wemake_python_styleguide/presets/types/filename.py# -*- coding: utf-8 -*- from typing_extensions import Final from wemake_python_styleguide.visitors.filenames.module import ( WrongModuleNameVisitor, ) #: Here we define all filename-based visitors. PRESET: Final = ( WrongModuleNameVisitor, ) PK! .wemake_python_styleguide/presets/types/tree.py# -*- coding: utf-8 -*- from typing_extensions import Final from wemake_python_styleguide.presets.topics import complexity from wemake_python_styleguide.visitors.ast import ( annotations, attributes, blocks, builtins, classes, compares, conditions, exceptions, functions, imports, keywords, loops, modules, naming, operators, statements, ) #: Used to store all general visitors to be later passed to checker: PRESET: Final = ( # General: statements.StatementsWithBodiesVisitor, statements.WrongParametersIndentationVisitor, statements.PointlessStarredVisitor, keywords.WrongRaiseVisitor, keywords.WrongKeywordVisitor, keywords.WrongContextManagerVisitor, keywords.ConsistentReturningVisitor, keywords.ConsistentReturningVariableVisitor, keywords.ConstantKeywordVisitor, loops.WrongComprehensionVisitor, loops.WrongLoopVisitor, loops.WrongLoopDefinitionVisitor, attributes.WrongAttributeVisitor, annotations.WrongAnnotationVisitor, functions.WrongFunctionCallVisitor, functions.FunctionDefinitionVisitor, functions.UselessLambdaDefinitionVisitor, functions.WrongFunctionCallContextVisitior, exceptions.WrongTryExceptVisitor, exceptions.NestedTryBlocksVisitor, imports.WrongImportVisitor, naming.WrongNameVisitor, naming.WrongModuleMetadataVisitor, naming.WrongVariableAssignmentVisitor, naming.WrongVariableUsageVisitor, builtins.MagicNumberVisitor, builtins.WrongStringVisitor, builtins.WrongAssignmentVisitor, builtins.WrongCollectionVisitor, operators.UselessOperatorsVisitor, operators.WrongMathOperatorVisitor, compares.WrongConditionalVisitor, compares.CompareSanityVisitor, compares.WrongComparisionOrderVisitor, compares.UnaryCompareVisitor, compares.WrongConstantCompareVisitor, conditions.IfStatementVisitor, conditions.BooleanConditionVisitor, conditions.ImplicitBoolPatternsVisitor, # Classes: classes.WrongClassVisitor, classes.WrongMethodVisitor, classes.WrongSlotsVisitor, classes.ClassAttributeVisitor, classes.ClassMethodOrderVisitor, # Modules: modules.EmptyModuleContentsVisitor, modules.MagicModuleFunctionsVisitor, modules.ModuleConstantsVisitor, # Blocks: blocks.BlockVariableVisitor, blocks.AfterBlockVariablesVisitor, # Complexity: *complexity.PRESET, ) PK!!wemake_python_styleguide/py.typedPK!uh4wemake_python_styleguide/transformations/__init__.py# -*- coding: utf-8 -*- PK!uh8wemake_python_styleguide/transformations/ast/__init__.py# -*- coding: utf-8 -*- PK!78wemake_python_styleguide/transformations/ast/bugfixes.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.logic.nodes import get_parent def fix_async_offset(tree: ast.AST) -> ast.AST: """ Fixes ``col_offest`` values for async nodes. This is a temporary check for async-based expressions, because offset for them isn't calculated properly. We can calculate right version of offset with subscripting ``6`` (length of "async " part). Affected ``python`` versions: - all versions below ``python3.6.7`` Read more: https://bugs.python.org/issue29205 https://github.com/wemake-services/wemake-python-styleguide/issues/282 """ nodes_to_fix = ( ast.AsyncFor, ast.AsyncWith, ast.AsyncFunctionDef, ) for node in ast.walk(tree): if isinstance(node, nodes_to_fix): error = 6 if node.col_offset % 4 != 0 else 0 node.col_offset = node.col_offset - error return tree def fix_line_number(tree: ast.AST) -> ast.AST: """ Adjusts line number for some nodes. They are set incorrectly for some collections. It might be either a bug or a feature. We do several checks here, to be sure that we won't get an incorrect line number. But, we basically check if there's a parent, so we can compare and adjust. Example:: print(( # should start from here 1, 2, 3, # actually starts from here )) """ affected = (ast.Tuple,) for node in ast.walk(tree): if isinstance(node, affected): parent_lineno = getattr(get_parent(node), 'lineno', None) if parent_lineno and parent_lineno < node.lineno: node.lineno = node.lineno - 1 return tree PK!;M6% % <wemake_python_styleguide/transformations/ast/enhancements.py# -*- coding: utf-8 -*- import ast from typing import Optional, Tuple, Type from wemake_python_styleguide.compat.aliases import FunctionNodes from wemake_python_styleguide.logic.nodes import get_parent from wemake_python_styleguide.types import ContextNodes def set_if_chain(tree: ast.AST) -> ast.AST: """ Used to create ``if`` chains. We have a problem, because we can not tell which situation is happening: .. code:: python if some_value: if other_value: ... .. code:: python if some_value: ... elif other_value: ... Since they are very similar it very hard to make a different when actually working with nodes. So, we need a simple way to separate them. """ for statement in ast.walk(tree): if not isinstance(statement, ast.If): continue for child in ast.iter_child_nodes(statement): if isinstance(child, ast.If): if child in statement.orelse: setattr(statement, 'wps_if_chained', True) # noqa: WPS425 setattr(child, 'wps_if_chain', statement) # noqa: B010 return tree def set_node_context(tree: ast.AST) -> ast.AST: """ Used to set proper context to all nodes. What we call "a context"? Context is where exactly this node belongs on a global level. Example: .. code:: python if some_value > 2: test = 'passed' Despite the fact ``test`` variable has ``Assign`` as it parent it will have ``Module`` as a context. What contexts do we respect? - :py:class:`ast.Module` - :py:class:`ast.ClassDef` - :py:class:`ast.FunctionDef` and :py:class:`ast.AsyncFunctionDef` .. versionchanged:: 0.8.1 """ contexts: Tuple[Type[ContextNodes], ...] = ( ast.Module, ast.ClassDef, *FunctionNodes, ) for statement in ast.walk(tree): current_context = _find_context(statement, contexts) setattr(statement, 'wps_context', current_context) # noqa: B010 return tree def _find_context( node: ast.AST, contexts: Tuple[Type[ast.AST], ...], ) -> Optional[ast.AST]: """ We changed how we find and assign contexts in 0.8.1 version. It happened because of the bug #520 See: https://github.com/wemake-services/wemake-python-styleguide/issues/520 """ parent = get_parent(node) if parent is None: return None elif isinstance(parent, contexts): return parent return _find_context(parent, contexts) PK!4wemake_python_styleguide/transformations/ast_tree.py# -*- coding: utf-8 -*- import ast from pep8ext_naming import NamingChecker from typing_extensions import final from wemake_python_styleguide.transformations.ast.bugfixes import ( fix_async_offset, fix_line_number, ) from wemake_python_styleguide.transformations.ast.enhancements import ( set_if_chain, set_node_context, ) @final class _ClassVisitor(ast.NodeVisitor): """Used to set method types inside classes.""" def __init__(self, transformer: NamingChecker) -> None: super().__init__() self.transformer = transformer def visit_ClassDef(self, node: ast.ClassDef) -> None: # noqa: N802 self.transformer.tag_class_functions(node) self.generic_visit(node) def _set_parent(tree: ast.AST) -> ast.AST: """ Sets parents for all nodes that do not have this prop. This step is required due to how `flake8` works. It does not set the same properties as `ast` module. This function was the cause of `issue-112`. Twice. Since the ``0.6.1`` we use ``'wps_parent'`` with a prefix. This should fix the issue with conflicting plugins. .. versionchanged:: 0.0.11 .. versionchanged:: 0.6.1 """ for statement in ast.walk(tree): for child in ast.iter_child_nodes(statement): setattr(child, 'wps_parent', statement) # noqa: B010 return tree def _set_function_type(tree: ast.AST) -> ast.AST: """ Sets the function type for methods. Can set: `method`, `classmethod`, `staticmethod`. .. versionchanged:: 0.3.0 """ transformer = _ClassVisitor(NamingChecker(tree, 'stdin')) transformer.visit(tree) return tree def transform(tree: ast.AST) -> ast.AST: """ Mutates the given ``ast`` tree. Applies all possible tranformations. Ordering: - initial ones - bugfixes - enhancements """ pipeline = ( # Initial, should be the first ones, ordering inside is important: _set_parent, _set_function_type, # Bugfixes, order is not important: fix_async_offset, fix_line_number, # Enhancements, order is not important: set_node_context, set_if_chain, ) for tranformation in pipeline: tree = tranformation(tree) return tree PK!c !wemake_python_styleguide/types.py# -*- coding: utf-8 -*- """ This module contains knowledge about the most important types that we use. There are also different :term:`visitor` specific types that are defined and use exclusively in that file. Policy ~~~~~~ If any of the following statements is true, move the type to this file: - if type is used in multiple files - if type is complex enough it has to be documented - if type is very important for the public API final ~~~~~ As you can see in the source code almost everything is marked as ``@final`` or ``Final``. It means that this value can not be subclassed or reassigned. This it only a ``mypy`` feature, it does not affect ``python`` runtime. We do this, because we value composition over inheritance. And this ``@final`` decorators help you to define readable and clear APIs for cases when inheritance is used. See also: My guide about ``@final`` type in ``python``: https://sobolevn.me/2018/07/real-python-contants Reference ~~~~~~~~~ """ import ast from typing import Tuple, Type, Union from typing_extensions import Protocol, final #: In cases we need to work with both import types. AnyImport = Union[ast.Import, ast.ImportFrom] #: In cases we need to work with both function definitions. AnyFunctionDef = Union[ast.FunctionDef, ast.AsyncFunctionDef] #: In cases we need to work with all function definitions (including lambdas). AnyFunctionDefAndLambda = Union[AnyFunctionDef, ast.Lambda] #: In cases we need to work with both forms of if functions. AnyIf = Union[ast.If, ast.IfExp] #: In cases we need to work with both sync and async loops. AnyFor = Union[ast.For, ast.AsyncFor] #: In cases we need to work with both sync and async context managers. AnyWith = Union[ast.With, ast.AsyncWith] #: Flake8 API format to return error messages. CheckResult = Tuple[int, int, str, type] #: Tuple of AST node types for declarative syntax. AnyNodes = Tuple[Type[ast.AST], ...] #: When we search for assign elements, we also need typed assign. AnyAssign = Union[ast.Assign, ast.AnnAssign] #: That's how we define context of operations. ContextNodes = Union[ ast.Module, ast.ClassDef, AnyFunctionDef, ] #: In cases we need to work with both access types. AnyAccess = Union[ ast.Attribute, ast.Subscript, ] @final class ConfigurationOptions(Protocol): """ Provides structure for the options we use in our checker and visitors. Then this protocol is passed to each individual visitor. It uses structural sub-typing, and does not represent any kind of a real class or structure. See also: https://mypy.readthedocs.io/en/latest/protocols.html """ # General: min_name_length: int i_control_code: bool max_name_length: int # Complexity: max_arguments: int max_local_variables: int max_returns: int max_expressions: int max_module_members: int max_methods: int max_line_complexity: int max_jones_score: int max_imports: int max_imported_names: int max_base_classes: int max_decorators: int max_string_usages: int max_awaits: int max_try_body_length: int max_module_expressions: int max_function_expressions: int max_asserts: int max_access_level: int max_attributes: int PK!lU#wemake_python_styleguide/version.py# -*- coding: utf-8 -*- import pkg_resources def _get_version(dist_name: str) -> str: # pragma: no cover """Fetches distribution name. Contains a fix for Sphinx.""" try: return pkg_resources.get_distribution(dist_name).version except pkg_resources.DistributionNotFound: return '' # readthedocs can not install `poetry` projects pkg_name = 'wemake-python-styleguide' #: We store the version number inside the `pyproject.toml`: pkg_version = _get_version(pkg_name) PK!uh/wemake_python_styleguide/violations/__init__.py# -*- coding: utf-8 -*- PK!y+wemake_python_styleguide/violations/base.py# -*- coding: utf-8 -*- """ Contains detailed technical information about :term:`violation` internals. .. _violations: Violations API -------------- .. currentmodule:: wemake_python_styleguide.violations.base .. autoclasstree:: wemake_python_styleguide.violations.base .. autosummary:: :nosignatures: ASTViolation MaybeASTViolation TokenizeViolation SimpleViolation Violation can not have more than one base class. See :ref:`tutorial` for more information about choosing a correct base class. Conventions ~~~~~~~~~~~ - Each violation class name should end with "Violation" - Each violation must have a long docstring with full description - Each violation must have "Reasoning" and "Solution" sections - Each violation must have "versionadded" policy - Each violation should have an example with correct and wrong usages - If violation error template should have a parameter it should be the last part of the text: ``: {0}`` Reference ~~~~~~~~~ """ import ast import tokenize from typing import ClassVar, Optional, Set, Tuple, Union from typing_extensions import final #: General type for all possible nodes where error happens. ErrorNode = Union[ ast.AST, tokenize.TokenInfo, None, ] class BaseViolation(object): """ Abstract base class for all style violations. It basically just defines how to create any error and how to format this error later on. Each subclass must define ``error_template`` and ``code`` fields. Attributes: error_template: message that will be shown to user after formatting. code: violation unique number. Used to identify the violation. previous_codes: just a documentation thing to track changes in time. """ error_template: ClassVar[str] code: ClassVar[int] previous_codes: ClassVar[Set[int]] def __init__(self, node: ErrorNode, text: Optional[str] = None) -> None: """ Creates a new instance of an abstract violation. Arguments: node: violation was raised by this node. If applicable. text: extra text to format the final message. If applicable. """ self._node = node self._text = text @final def message(self) -> str: """ Returns error's formatted message with code and reason. Conditionally formats the ``error_template`` if it is required. """ return '{0} {1}'.format( self._full_code(), self.error_template.format(self._text), ) @final def node_items(self) -> Tuple[int, int, str]: """Returns tuple to match ``flake8`` API format.""" return (*self._location(), self.message()) @final def _full_code(self) -> str: """ Returns fully formatted code. Adds violation letter to the numbers. Also ensures that codes like ``3`` will be represented as ``WPS003``. """ return 'WPS{0}'.format(str(self.code).zfill(3)) def _location(self) -> Tuple[int, int]: """ Return violation location inside the file. Default location is in the so-called "file beginning". """ return 0, 0 class _BaseASTViolation(BaseViolation): """Used as a based type for all ``ast`` violations.""" _node: Optional[ast.AST] @final def _location(self) -> Tuple[int, int]: line_number = getattr(self._node, 'lineno', 0) column_offset = getattr(self._node, 'col_offset', 0) return line_number, column_offset class ASTViolation(_BaseASTViolation): """Violation for ``ast`` based style visitors.""" _node: ast.AST class MaybeASTViolation(_BaseASTViolation): """ Violation for ``ast`` and modules visitors. Is used for violations that share the same rule for nodes and module names. Is wildly used for naming rules. """ def __init__(self, node=None, text: Optional[str] = None) -> None: """Creates new instance of module violation without explicit node.""" super().__init__(node, text=text) class TokenizeViolation(BaseViolation): """Violation for ``tokenize`` based visitors.""" _node: tokenize.TokenInfo @final def _location(self) -> Tuple[int, int]: return self._node.start class SimpleViolation(BaseViolation): """Violation for cases where there's no associated nodes.""" _node: None def __init__(self, node=None, text: Optional[str] = None) -> None: """Creates new instance of simple style violation.""" super().__init__(node, text=text) PK!LIDD5wemake_python_styleguide/violations/best_practices.py# -*- coding: utf-8 -*- """ These checks ensure that you follow the best practices. The source for these best practices is hidden inside countless hours we have spent debugging software or reviewing it. How do we find inspiration for new rules? We find some ugly code during code reviews and audits. Then we forbid to use this bad code forever. So, this error will never return to our codebase. .. currentmodule:: wemake_python_styleguide.violations.best_practices Summary ------- .. autosummary:: :nosignatures: WrongMagicCommentViolation WrongDocCommentViolation OveruseOfNoqaCommentViolation OveruseOfNoCoverCommentViolation ComplexDefaultValueViolation LoopVariableDefinitionViolation ContextManagerVariableDefinitionViolation MutableModuleConstantViolation SameElementsInConditionViolation HeterogenousCompareViolation WrongModuleMetadataViolation EmptyModuleViolation InitModuleHasLogicViolation BadMagicModuleFunctionViolation WrongUnpackingViolation DuplicateExceptionViolation YieldInComprehensionViolation NonUniqueItemsInHashViolation BaseExceptionSubclassViolation TryExceptMultipleReturnPathViolation WrongKeywordViolation WrongFunctionCallViolation FutureImportViolation RaiseNotImplementedViolation BaseExceptionViolation BooleanPositionalArgumentViolation LambdaInsideLoopViolation UnreachableCodeViolation StatementHasNoEffectViolation MultipleAssignmentsViolation NestedFunctionViolation NestedClassViolation MagicNumberViolation NestedImportViolation ReassigningVariableToItselfViolation ListMultiplyViolation ProtectedModuleViolation ProtectedAttributeViolation StopIterationInsideGeneratorViolation WrongUnicodeEscapeViolation BlockAndLocalOverlapViolation ControlVarUsedAfterBlockViolation OuterScopeShadowingViolation UnhashableTypeInHashViolation WrongKeywordConditionViolation Best practices -------------- .. autoclass:: WrongMagicCommentViolation .. autoclass:: WrongDocCommentViolation .. autoclass:: OveruseOfNoqaCommentViolation .. autoclass:: OveruseOfNoCoverCommentViolation .. autoclass:: ComplexDefaultValueViolation .. autoclass:: LoopVariableDefinitionViolation .. autoclass:: ContextManagerVariableDefinitionViolation .. autoclass:: MutableModuleConstantViolation .. autoclass:: SameElementsInConditionViolation .. autoclass:: HeterogenousCompareViolation .. autoclass:: WrongModuleMetadataViolation .. autoclass:: EmptyModuleViolation .. autoclass:: InitModuleHasLogicViolation .. autoclass:: BadMagicModuleFunctionViolation .. autoclass:: WrongUnpackingViolation .. autoclass:: DuplicateExceptionViolation .. autoclass:: YieldInComprehensionViolation .. autoclass:: NonUniqueItemsInHashViolation .. autoclass:: BaseExceptionSubclassViolation .. autoclass:: TryExceptMultipleReturnPathViolation .. autoclass:: WrongKeywordViolation .. autoclass:: WrongFunctionCallViolation .. autoclass:: FutureImportViolation .. autoclass:: RaiseNotImplementedViolation .. autoclass:: BaseExceptionViolation .. autoclass:: BooleanPositionalArgumentViolation .. autoclass:: LambdaInsideLoopViolation .. autoclass:: UnreachableCodeViolation .. autoclass:: StatementHasNoEffectViolation .. autoclass:: MultipleAssignmentsViolation .. autoclass:: NestedFunctionViolation .. autoclass:: NestedClassViolation .. autoclass:: MagicNumberViolation .. autoclass:: NestedImportViolation .. autoclass:: ReassigningVariableToItselfViolation .. autoclass:: ListMultiplyViolation .. autoclass:: ProtectedModuleViolation .. autoclass:: ProtectedAttributeViolation .. autoclass:: StopIterationInsideGeneratorViolation .. autoclass:: WrongUnicodeEscapeViolation .. autoclass:: BlockAndLocalOverlapViolation .. autoclass:: ControlVarUsedAfterBlockViolation .. autoclass:: OuterScopeShadowingViolation .. autoclass:: UnhashableTypeInHashViolation .. autoclass:: WrongKeywordConditionViolation """ from typing_extensions import final from wemake_python_styleguide.violations.base import ( ASTViolation, SimpleViolation, TokenizeViolation, ) @final class WrongMagicCommentViolation(SimpleViolation): """ Restricts to use several control (or magic) comments. We do not allow to use: 1. ``# noqa`` comment without specified violations 2. ``# type: some_type`` comments to specify a type for ``typed_ast`` This violation is reported at the top of the module, so it cannot be locally ignored. Reasoning: We cover several different use-cases in a single rule. ``# noqa`` comment is restricted because it can hide other violations. ``# type: some_type`` comment is restricted because we can already use type annotations instead. Solution: Use ``# noqa`` comments with specified error types. Use type annotations to specify types. We still allow to use ``# type: ignore`` comment. Since sometimes it is totally required. Example:: # Correct: type = MyClass.get_type() # noqa: A001 coordinate: int = 10 some.int_field = 'text' # type: ignore number: int for number in some_untyped_iterable(): ... # Wrong: type = MyClass.get_type() # noqa coordinate = 10 # type: int .. versionadded:: 0.1.0 """ code = 400 error_template = 'Found wrong magic comment: {0}' @final class WrongDocCommentViolation(TokenizeViolation): """ Forbids to use empty doc comments (``#:``). Reasoning: Doc comments are used to provide a documentation. But supplying empty doc comments breaks this use-case. It is unclear why they can be used with no contents. Solution: Add some documentation to this comment. Or remove it. Empty doc comments are not caught by the default ``pycodestyle`` checks. Example:: # Correct: #: List of allowed names: NAMES_WHITELIST = ['feature', 'bug', 'research'] # Wrong: #: NAMES_WHITELIST = ['feature', 'bug', 'research'] .. versionadded:: 0.1.0 """ code = 401 error_template = 'Found wrong doc comment' @final class OveruseOfNoqaCommentViolation(SimpleViolation): """ Forbids to use too many ``# noqa`` comments. We count it on a per-module basis. We use :str:`wemake_python_styleguide.constants.MAX_NOQA_COMMENTS` as a hard limit. Reasoning: Having too many ``# noqa`` comments make your code less readable and clearly indicates that there's something wrong with it. Solution: Refactor your code to match our style. Or use a config file to switch off some checks. .. versionadded:: 0.7.0 """ error_template = 'Found `noqa` comments overuse: {0}' code = 402 @final class OveruseOfNoCoverCommentViolation(SimpleViolation): """ Forbids to use too many ``# pragma: no cover`` comments. We count it on a per-module basis. We use :str:`wemake_python_styleguide.constants.MAX_NO_COVER_COMMENTS` as a default value. Reasoning: Having too many ``# pragma: no cover`` comments clearly indicates that there's something wrong with it. Moreover, it makes your tests useless, since they do not cover a big partion of your code. Solution: Refactor your code to much the style. Or use a config file to switch off some checks. .. versionadded:: 0.8.0 """ error_template = 'Found `noqa` comments overuse: {0}' code = 403 @final class ComplexDefaultValueViolation(ASTViolation): """ Forbids to use complex defaults. Anything that is not a ``ast.Name``, ``ast.Attribute``, ``ast.Str``, ``ast.NameConstant``, ``ast.Tuple``, ``ast.Bytes``, ``ast.Num`` or ``ast.Ellipsis`` should be moved out from defaults. Reasoning: It can be tricky. Nothing stops you from making database calls or http requests in such expressions. It is also not readable for us. Solution: Move the expression out from default value. Example:: # Correct: SHOULD_USE_DOCTEST = 'PYFLAKES_DOCTEST' in os.environ def __init__(self, with_doctest=SHOULD_USE_DOCTEST): # Wrong: def __init__(self, with_doctest='PYFLAKES_DOCTEST' in os.environ): .. versionadded:: 0.8.0 .. versionchanged:: 0.11.0 """ error_template = 'Found complex default value' code = 404 previous_codes = {459} @final class LoopVariableDefinitionViolation(ASTViolation): """ Forbids to use anything rather than ``ast.Name`` to define loop variables. Reasoning: When defining a ``for`` loop with attributes, indexes, calls, or any other nodes it does dirty things inside. Solution: Use regular ``ast.Name`` variables. Or tuple of ``ast.Name`` variables. Star names are also fine. Example:: # Correct: for person in database.people(): ... # Wrong: for context['person'] in database.people(): ... .. versionadded:: 0.8.0 .. versionchanged:: 0.11.0 """ error_template = 'Found wrong `for` loop variable definition' code = 405 previous_codes = {460} @final class ContextManagerVariableDefinitionViolation(ASTViolation): """ Forbids to use anything rather than ``ast.Name`` to define contexts. Reasoning: When defining a ``with`` context managers with attributes, indexes, calls, or any other nodes it does dirty things inside. Solution: Use regular ``ast.Name`` variables. Or tuple of ``ast.Name`` variables. Star names are also fine. Example:: # Correct: with open('README.md') as readme: ... # Wrong: with open('README.md') as files['readme']: ... .. versionadded:: 0.8.0 .. versionchanged:: 0.11.0 """ error_template = 'Found wrong context manager variable definition' code = 406 previous_codes = {461} @final class MutableModuleConstantViolation(ASTViolation): """ Forbids mutable constants on a module level. Reasoning: Constants should be immutable. Solution: Use immutable types for constants. We only treat ``ast.Set``, ``ast.Dict``, ``ast.List``, and comprehensions as mutable things. All other nodes are still fine. Example:: # Correct: import types CONST1 = frozenset((1, 2, 3)) CONST2 = (1, 2, 3) CONST3 = types.MappingProxyType({'key': 'value'}) # Wrong: CONST1 = {1, 2, 3} CONST2 = [x for x in some()] CONST3 = {'key': 'value'} .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found mutable module constant' code = 407 previous_codes = {466} @final class SameElementsInConditionViolation(ASTViolation): """ Forbids to use the same logical conditions in one expression. Reasoning: Using the same name in logical condition more that once indicates that you are either making a logical mistake, or just over-complicating your design. Solution: Remove the duplicating condition. Example:: # Correct: if some_value or other_value: ... # Wrong: if some_value or some_value: ... .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found duplicate logical condition' code = 408 previous_codes = {469} @final class HeterogenousCompareViolation(ASTViolation): """ Forbids to heterogenous operators in one compare. Note, that we allow to mix ``>`` with ``>=`` and ``<`` with ``<=`` operators. Reasoning: This is hard to read and understand. Solution: Refactor the expression to have separate parts joined with ``and`` boolean operator. Example:: # Correct: if x == y == z: ... if x > y >= z: ... # Wrong: if x > y == 5: ... if x == y != z: ... .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found heterogenous compare' code = 409 previous_codes = {471} @final class WrongModuleMetadataViolation(ASTViolation): """ Forbids to have some module level variables. Reasoning: We discourage using module variables like ``__author__``, because code should not contain any metadata. Solution: Place all the metadata in ``setup.py``, ``setup.cfg``, or ``pyproject.toml``. Use proper docstrings and packaging classifiers. Use ``pkg_resources`` if you need to import this data into your app. See :py:data:`~wemake_python_styleguide.constants.MODULE_METADATA_VARIABLES_BLACKLIST` for full list of bad names. Example:: # Wrong: __author__ = 'Nikita Sobolev' __version__ = 0.1.2 .. versionadded:: 0.1.0 """ error_template = 'Found wrong metadata variable: {0}' code = 410 @final class EmptyModuleViolation(SimpleViolation): """ Forbids to have empty modules. Reasoning: Why is it even there? Do not pollute your project with empty files. Solution: If you have an empty module there are two ways to handle that: 1. delete it 2. drop some documentation in it, so you will explain why it is there .. versionadded:: 0.1.0 """ error_template = 'Found empty module' code = 411 @final class InitModuleHasLogicViolation(SimpleViolation): """ Forbids to have logic inside ``__init__`` module. Reasoning: If you have logic inside the ``__init__`` module it means several things: 1. you are keeping some outdated stuff there, you need to refactor 2. you are placing this logic into the wrong file, just create another one 3. you are doing some dark magic, and you should not do that Solution: Put your code in other modules. However, we allow to have some contents inside the ``__init__`` module: 1. comments, since they are dropped before AST comes in play 2. docs string, because sometimes it is required to state something It is also fine when you have different users that use your code. And you do not want to break everything for them. In this case this rule can be configured. Configuration: This rule is configurable with ``--i-control-code``. Default: :str:`wemake_python_styleguide.options.defaults.I_CONTROL_CODE` .. versionadded:: 0.1.0 """ error_template = 'Found `__init__.py` module with logic' code = 412 @final class BadMagicModuleFunctionViolation(ASTViolation): """ Forbids to use ``__getaddr__`` and ``__dir__`` module magic methods. Reasoning: It does not bring any features, only making it harder to understand what is going on. Solution: Refactor your code to use custom methods instead. Configuration: This rule is configurable with ``--i-control-code``. Default: :str:`wemake_python_styleguide.options.defaults.I_CONTROL_CODE` .. versionadded:: 0.9.0 """ error_template = 'Found bad magic module function: {0}' code = 413 @final class WrongUnpackingViolation(ASTViolation): """ Forbids to have tuple unpacking with side-effects. Reasoning: Having unpacking with side-effects is very dirty. You might get in serious and very hard-to-debug troubles because of this technique. So, do not use it. Solution: Use unpacking with only variables, not any other entities. Example:: # Correct: first, second = some() # Wrong: first, some_dict['alias'] = some() .. versionadded:: 0.6.0 .. versionchanged:: 0.11.0 """ error_template = 'Found incorrect unpacking target' code = 414 previous_codes = {446} @final class DuplicateExceptionViolation(ASTViolation): """ Forbids to have the same exception class in multiple ``except`` blocks. Reasoning: Having the same exception name in different blocks means that something is not right: since only one branch will work. Other one will always be ignored. So, that is clearly an error. Solution: Use unique exception handling rules. Example:: # Correct: try: ... except ValueError: ... # Wrong: try: ... except ValueError: ... except ValueError: ... .. versionadded:: 0.6.0 .. versionchanged:: 0.11.0 """ error_template = 'Found duplicate exception: {0}' code = 415 previous_codes = {447} @final class YieldInComprehensionViolation(ASTViolation): """ Forbids to have ``yield`` keyword inside comprehensions. Reasoning: Having the ``yield`` keyword inside comprehensions is error-prone. You can shoot yourself in a foot by an inaccurate usage of this feature. Solution: Use regular ``for`` loops with ``yield`` keywords. Or create a separate generator function. Example:: # Wrong: list((yield letter) for letter in 'ab') # Will resilt in: ['a', None, 'b', None] list([(yield letter) for letter in 'ab']) # Will result in: ['a', 'b'] See also: https://github.com/satwikkansal/wtfPython#-yielding-none .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 """ error_template = 'Found `yield` inside comprehension' code = 416 previous_codes = {448} @final class NonUniqueItemsInHashViolation(ASTViolation): """ Forbids to have duplicate items in hashes. Reasoning: When you explicitly put duplicate items in ``set`` literals or in ``dict`` keys it just does not make any sense. Since hashes cannot contain duplicate items and they will be removed anyway. Solution: Remove duplicate items. Example:: # Correct: some_set = {'a', variable1} some_set = {make_call(), make_call()} # Wrong: some_set = {'a', 'a', variable1, variable1} Things that we consider duplicates: builtins and variables. These nodes are not checked because they may return different results: - function and method calls - comprehensions - attributes - subscribe operations .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 .. versionchanged:: 0.12.0 """ error_template = 'Found non-unique item in hash: {0}' code = 417 previous_codes = {449} @final class BaseExceptionSubclassViolation(ASTViolation): """ Forbids to have duplicate items in ``set`` literals. Reasoning: ``BaseException`` is a special case: it is not designed to be extended by users. A lot of your ``except Exception`` cases won't work. That's incorrect and dangerous. Solution: Change the base class to ``Exception``. Example:: # Correct: class MyException(Exception): ... # Wrong: class MyException(BaseException): ... See also: https://docs.python.org/3/library/exceptions.html#exception-hierarchy .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 """ error_template = 'Found exception inherited from `BaseException`' code = 418 previous_codes = {450} @final class TryExceptMultipleReturnPathViolation(ASTViolation): """ Forbids to use multiple returning paths with ``try`` / ``except`` case. Note, that we check for any ``return``, ``break``, or ``raise`` nodes. Reasoning: The problem with ``return`` in ``else`` and ``finally`` is that it is impossible to say what value is going to be actually returned without looking up the implementation details. Why? Because ``return`` does not expect that some other code will be executed after it. But, ``finally`` is always executed, even after ``return``. And ``else`` will not be executed when there are no exceptions in ``try`` case and a ``return`` statement. Solution: Remove ``return`` from one of the cases. Example:: # Correct: try: return 1 except YourException: ... finally: clear_things_up() # Wrong: try: return 1 # this line will never return except Exception: ... finally: return 2 # this line will actually return try: return 1 # this line will actually return except ZeroDivisionError: ... else: return 0 # this line will never return .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 .. versionchanged:: 0.12.0 """ error_template = 'Found `try`/`else`/`finally` with multiple return paths' code = 419 previous_codes = {458} @final class WrongKeywordViolation(ASTViolation): """ Forbids to use some ``python`` keywords. Reasoning: Using some keywords generally gives you more pain that relieve. ``del`` keyword is not composable with other functions, you cannot pass it as a regular function. It is also quite error-prone due to ``__del__`` magic method complexity and that ``del`` is actually used to nullify variables and delete them from the execution scope. Moreover, it has a lot of substitutions. You won't miss it! ``pass`` keyword is just useless by design. There's no usecase for it. Because it does literally nothing. ``global`` and ``nonlocal`` promote bad-practices of having an external mutable state somewhere. This solution does not scale. And leads to multiple possible mistakes in the future. Solution: Solutions differ from keyword to keyword. ``pass`` should be replaced with docstring or ``contextlib.suppress``. ``del`` should be replaced with specialized methods like ``.pop()``. ``global`` and ``nonlocal`` usages should be refactored. .. versionadded:: 0.1.0 """ error_template = 'Found wrong keyword: {0}' code = 420 @final class WrongFunctionCallViolation(ASTViolation): """ Forbids to call some built-in functions. Reasoning: Some functions are only suitable for very specific use cases, we forbid to use them in a free manner. See :py:data:`~wemake_python_styleguide.constants.FUNCTIONS_BLACKLIST` for the full list of blacklisted functions. See also: https://www.youtube.com/watch?v=YjHsOrOOSuI .. versionadded:: 0.1.0 """ error_template = 'Found wrong function call: {0}' code = 421 @final class FutureImportViolation(ASTViolation): """ Forbids to use ``__future__`` imports. Reasoning: Almost all ``__future__`` imports are legacy ``python2`` compatibility tools that are no longer required. Solution: Remove them. Drop ``python2`` support. Except, there are some new ones for ``python4`` support. See :py:data:`~wemake_python_styleguide.constants.FUTURE_IMPORTS_WHITELIST` for the full list of allowed future imports. Example:: # Correct: from __future__ import annotations # Wrong: from __future__ import print_function .. versionadded:: 0.1.0 """ error_template = 'Found future import: {0}' code = 422 @final class RaiseNotImplementedViolation(ASTViolation): """ Forbids to use ``NotImplemented`` error. Reasoning: These two violations look so similar. But, these violations have different use cases. Use cases of ``NotImplemented`` is too limited to be generally available. Solution: Use ``NotImplementedError``. Example:: # Correct: raise NotImplementedError('To be done') # Wrong: raise NotImplemented .. versionadded:: 0.1.0 See also: https://stackoverflow.com/a/44575926/4842742 """ error_template = 'Found raise NotImplemented' code = 423 @final class BaseExceptionViolation(ASTViolation): """ Forbids to use ``BaseException`` exception. Reasoning: We can silence system exit and keyboard interrupt with this exception handler. It is almost the same as raw ``except:`` block. Solution: Handle ``Exception``, ``KeyboardInterrupt``, ``GeneratorExit``, and ``SystemExit`` separately. Do not use the plain ``except:`` keyword. Example:: # Correct: except Exception as ex: ... # Wrong: except BaseException as ex: ... .. versionadded:: 0.3.0 See also: https://docs.python.org/3/library/exceptions.html#exception-hierarchy https://help.semmle.com/wiki/pages/viewpage.action?pageId=1608527 """ error_template = 'Found except `BaseException`' code = 424 @final class BooleanPositionalArgumentViolation(ASTViolation): """ Forbids to pass booleans as non-keyword parameters. Reasoning: Passing boolean as regular positional parameters is very non-descriptive. It is almost impossible to tell, what does this parameter means. And you almost always have to look up the implementation to tell what is going on. Solution: Pass booleans as keywords only. This will help you to save extra context on what's going on. Example:: # Correct: UsersRepository.update(cache=True) # Wrong: UsersRepository.update(True) .. versionadded:: 0.6.0 """ error_template = 'Found boolean non-keyword argument: {0}' code = 425 @final class LambdaInsideLoopViolation(ASTViolation): """ Forbids to use ``lambda`` inside loops. Reasoning: It is error-prone to use ``lambda`` inside ``for`` and ``while`` loops due to the famous late-binding. Solution: Use regular functions, factory functions, or ``partial`` functions. Save yourself from possible confusion. Example:: # Correct: for index in range(10): some.append(partial_function(index)) # Wrong: for index in range(10): some.append(lambda index=index: index * 10)) other.append(lambda: index * 10)) .. versionadded:: 0.5.0 .. versionchanged:: 0.11.0 See also: https://docs.python-guide.org/writing/gotchas/#late-binding-closures """ error_template = "Found `lambda` in loop's body" code = 426 previous_codes = {442} @final class UnreachableCodeViolation(ASTViolation): """ Forbids to have unreachable code. What is unreachable code? It is some lines of code that cannot be executed by python's interpreter. This is probably caused by ``return`` or ``raise`` statements. However, we can not cover 100% of truly unreachable code by this rule. This happens due to the dynamic nature of python. For example, detecting that ``1 / some_value`` would sometimes raise an exception is too complicated and is out of the scope of this rule. Reasoning: Having dead code in your project is an indicator that you do not care about your code base at all. It dramatically reduces code quality and readability. It also demotivates team members. Solution: Delete any unreachable code you have. Or refactor it, if this happens by your mistake. Example:: # Correct: def some_function(): print('This line is reachable, all good') return 5 # Wrong: def some_function(): return 5 print('This line is unreachable') .. versionadded:: 0.5.0 .. versionchanged:: 0.11.0 """ error_template = 'Found unreachable code' code = 427 previous_codes = {443} @final class StatementHasNoEffectViolation(ASTViolation): """ Forbids to have statements that do nothing. Reasoning: Statements that just access the value or expressions used as statements indicate that your code contains deadlines. They just pollute your codebase and do nothing. Solution: Refactor your code in case it was a typo or error. Or just delete this code. Example:: # Correct: def some_function(): price = 8 + 2 return price # Wrong: def some_function(): 8 + 2 print .. versionadded:: 0.5.0 .. versionchanged:: 0.11.0 """ error_template = 'Found statement that has no effect' code = 428 previous_codes = {444} @final class MultipleAssignmentsViolation(ASTViolation): """ Forbids to have multiple assignments on the same line. Reasoning: Multiple assignments on the same line might not do what you think they do. They can also grown pretty long. And you will not notice the rising complexity of your code. Solution: Use separate lines for each assignment. Example:: # Correct: a = 1 b = 1 # Wrong: a = b = 1 .. versionadded:: 0.6.0 .. versionchanged:: 0.11.0 """ error_template = 'Found multiple assign targets' code = 429 previous_codes = {445} @final class NestedFunctionViolation(ASTViolation): """ Forbids to have nested functions. Reasoning: Nesting functions is a bad practice. It is hard to test them, it is hard then to separate them. People tend to overuse closures, so it's hard to manage the dataflow. Solution: Just write flat functions, there's no need to nest them. Pass parameters as normal arguments, do not use closures. Until you need them for decorators or factories. We also disallow to nest ``lambda`` and ``async`` functions. See :py:data:`~wemake_python_styleguide.constants.NESTED_FUNCTIONS_WHITELIST` for the whole list of whitelisted names. Example:: # Correct: def do_some(): ... def other(): ... # Wrong: def do_some(): def inner(): ... .. versionadded:: 0.1.0 """ error_template = 'Found nested function: {0}' code = 430 @final class NestedClassViolation(ASTViolation): """ Forbids to use nested classes. Reasoning: Nested classes are really hard to manage. You can not even create an instance of this class in many cases. Testing them is also really hard. Solution: Just write flat classes, there's no need nest them. If you are nesting classes inside a function for parametrization, then you will probably need to use different design (or metaclasses). See :py:data:`~wemake_python_styleguide.constants.NESTED_CLASSES_WHITELIST` for the full list of whitelisted names. Example:: # Correct: class Some(object): ... class Other(object): ... # Wrong: class Some(object): class Inner(object): ... .. versionadded:: 0.1.0 """ error_template = 'Found nested class: {0}' code = 431 @final class MagicNumberViolation(ASTViolation): """ Forbids to use magic numbers in your code. What we call a "magic number"? Well, it is actually any number that appears in your code out of nowhere. Like ``42``. Or ``0.32``. Reasoning: It is very hard to remember what these numbers actually mean. Why were they used? Should they ever be changed? Or are they eternal like ``3.14``? Solution: Give these numbers a name! Move them to a separate variable, giving more context to the reader. And by moving things into new variables you will trigger other complexity checks. Example:: # Correct: price_in_euro = 3.33 # could be changed later total = get_items_from_cart() * price_in_euro # Wrong: total = get_items_from_cart() * 3.33 What are numbers that we exclude from this check? Any numbers that are assigned to a variable, array, dictionary, or keyword arguments inside a function. ``int`` numbers that are in range ``[-10, 10]`` and some other common numbers, that are defined in :py:data:`~wemake_python_styleguide.constants.MAGIC_NUMBERS_WHITELIST` .. versionadded:: 0.1.0 See also: https://en.wikipedia.org/wiki/Magic_number_(programming) """ code = 432 error_template = 'Found magic number: {0}' @final class NestedImportViolation(ASTViolation): """ Forbids to have nested imports in functions. Reasoning: Usually, nested imports are used to fix the import cycle. So, nested imports show that there's an issue with your design. Solution: You don't need nested imports, you need to refactor your code. Introduce a new module or find another way to do what you want to do. Rethink how your layered architecture should look like. Example:: # Correct: from my_module import some_function def some(): ... # Wrong: def some(): from my_module import some_function .. versionadded:: 0.1.0 .. versionchanged:: 0.11.0 See also: https://github.com/seddonym/layer_linter """ error_template = 'Found nested import' code = 433 previous_codes = {435} @final class ReassigningVariableToItselfViolation(ASTViolation): """ Forbids to assign variable to itself. Reasoning: There is no need to do that. Generally, it is an indication of some errors or just dead code. Example:: # Correct: some = some + 1 x_coord, y_coord = y_coord, x_coord # Wrong: some = some x_coord, y_coord = x_coord, y_coord .. versionadded:: 0.3.0 .. versionchanged:: 0.11.0 """ error_template = 'Found reassigning variable to itself: {0}' code = 434 previous_codes = {438} @final class ListMultiplyViolation(ASTViolation): """ Forbids to multiply lists. Reasoning: When you multiply lists - it does not create new values, it creates references to the existing value. It is not what people mean in 99.9% of cases. Solution: Use list comprehension or loop instead. Example:: # Wrong: my_list = [1, 2, 3] * 3 See also: https://github.com/satwikkansal/wtfPython#-explanation-8 .. versionadded:: 0.12.0 """ error_template = 'Found list multiply' code = 435 @final class ProtectedModuleViolation(ASTViolation): """ Forbids to import protected modules. Reasoning: When importing protected modules we break a contract that authors of this module enforce. This way we are not respecting encapsulation and it may break our code at any moment. Solution: Do not import anything from protected modules. Respect the encapsulation. Example:: # Correct: from some.public.module import FooClass # Wrong: import _compat from some._protected.module import BarClass from some.module import _protected .. versionadded:: 0.3.0 .. versionchanged:: 0.11.0 """ error_template = 'Found protected module import' code = 436 previous_codes = {440} @final class ProtectedAttributeViolation(ASTViolation): """ Forbids to use protected attributes and methods. Reasoning: When using protected attributes and method we break a contract that authors of this class enforce. This way we are not respecting encapsulation and it may break our code at any moment. Solution: Do not use protected attributes and methods. Respect the encapsulation. Example:: # Correct: self._protected = 1 cls._hidden_method() some.public() super()._protected() # Wrong: print(some._protected) instance._hidden() self.container._internal = 10 Note, that it is possible to use protected attributes with ``self``, ``cls``, and ``super()`` as base names. We allow this so you can create and use protected attributes and methods inside the class context. This is how protected attributes should be used. .. versionadded:: 0.3.0 .. versionchanged:: 0.11.0 """ error_template = 'Found protected attribute usage: {0}' code = 437 previous_codes = {441} @final class StopIterationInsideGeneratorViolation(ASTViolation): """ Forbids to raise ``StopIteration`` inside generators. Reasoning: ``StopIteration`` should not be raised explicitly in generators. Solution: Use return statement to get out of a generator. Example:: # Correct: def some_generator(): if some_value: return yield 1 # Wrong: def some_generator(): if some_value: raise StopIteration yield 1 See also: https://docs.python.org/3/library/exceptions.html#StopIteration .. versionadded:: 0.12.0 """ error_template = 'Found `StopIteration` raising inside generator' code = 438 @final class WrongUnicodeEscapeViolation(TokenizeViolation): r""" Forbids to use unicode escape sequences in binary strings. Reasoning: Binary strings do not work with unicode. Having unicode escape characters in there means that you have an error in your code. Solution: Use regular strings when escaping unicode strings. Example:: # Correct: escaped = '\u0041' # equals to 'A' # Wrong: escaped = b'\u0040' # equals to b'\\u0040' .. versionadded:: 0.12.0 """ error_template = 'Found unicode escape in a binary string: {0}' code = 439 @final class BlockAndLocalOverlapViolation(ASTViolation): """ Forbids to local and block variables to overlap. What we call local variables: 1. Assigns and annotations 2. Function arguments (they are local to the function body) What we call block variables: 1. Imports 2. Functions and async functions definitions 3. Classes, methods, and async methods definitions 4. For and async for loops variables 5. Except block exception aliases We allow local variables to overlap theirselfs, we forbid block varibals to overlap theirselfs. Reasoning: A lot of complex errors might happen when you shadow local varibales with block variables or when you shadow block variables with local variables. Solution: Use names that do not overlap. Example:: # Correct: my_value = 1 my_value = my_value + 1 # Wrong: import my_value my_value = 1 # overlaps with import See also: https://github.com/satwikkansal/wtfPython#-explanation-20 .. versionadded:: 0.12.0 """ error_template = 'Found block variables overlap: {0}' code = 440 @final class ControlVarUsedAfterBlockViolation(ASTViolation): """ Forbids to use control variables after the block body. What we call block control variables: 1. ``for`` loop unpacked variables 2. ``with`` context variables 3. ``except`` exception names Reasoning: Variables leaking from the blocks can damage your logic. It might not contain what you think they contain. Some variables even might be deleted right after the block, just like in ``except Exception as exc:`` where ``exc`` won't be in scope after ``except`` body. Solution: Use names inside the scope they are defined. Create new functions to return values in case you need to use block variables: when searching for a value, etc. Example:: # Correct: for my_item in collection: print(my_item) # Wrong: for my_item in collection: ... print(my_item) See also: https://github.com/satwikkansal/wtfPython#-explanation-32 .. versionadded:: 0.12.0 """ error_template = 'Found control variable used after block: {0}' code = 441 @final class OuterScopeShadowingViolation(ASTViolation): """ Forbids to shadow variables from outer scopes. We check function, method, and module scopes. While we do not check class scope. Because class level constants are not available via regular name, and they are scope to ``ClassName.var_name``. Reasoning: Shadowing can lead you to a big pile of strage and unexpected bugs. Solution: Use different names and do not allow scoping. Example:: # Correct: def test(): ... def other(): test1 = 1 # Wrong: def test(): ... def other(): test = 1 # shadows `test()` function .. versionadded:: 0.12.0 """ error_template = 'Found outer scope names shadowing: {0}' code = 442 @final class UnhashableTypeInHashViolation(ASTViolation): """ Forbids to use exlicit unhashable types as set items and dict keys. Reasoning: This will resolve in ``TypeError`` in runtime. Solution: Use hashable types to define set items and dict keys. Example:: # Correct: my_dict = {1: {}, (1, 2): [], (2, 3): {1, 2}} # Wrong: my_dict = {[1, 2]: [], {2, 3}: {1, 2}} .. versionadded:: 0.12.0 """ error_template = 'Found unhashable item' code = 443 @final class WrongKeywordConditionViolation(ASTViolation): """ Forbids to use exlicit falsly-evaluated conditions with several keywords. We check: - ``ast.While`` - ``ast.Assert`` We only check constants. We do not check variables, attributes, calls, etc. Reasoning: Some conditions clearly tell us that this node won't work correctly. So, we need to check that we can fix that. Solution: Remove the unreachable node, or change the condition item. Example:: # Correct: assert some_variable while True: ... # Wrong: assert [] while False: ... .. versionadded:: 0.12.0 """ error_template = 'Found wrong keyword condition: {0}' code = 444 PK!W:`hh1wemake_python_styleguide/violations/complexity.py# -*- coding: utf-8 -*- """ These checks find flaws in your application design. We try to stick to "the magical 7 ± 2 number" when counting things. https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two That's how many objects we can keep in our memory at a time. We try hard not to exceed the memory capacity limit. You can also find interesting reading about "Cognitive complexity": https://www.sonarsource.com/docs/CognitiveComplexity.pdf Note: Simple is better than complex. Complex is better than complicated. .. currentmodule:: wemake_python_styleguide.violations.complexity Summary ------- .. autosummary:: :nosignatures: JonesScoreViolation TooManyImportsViolation TooManyModuleMembersViolation TooManyImportedNamesViolation OverusedExpressionViolation TooManyLocalsViolation TooManyArgumentsViolation TooManyReturnsViolation TooManyExpressionsViolation TooManyMethodsViolation TooManyBaseClassesViolation TooManyDecoratorsViolation TooManyAwaitsViolation TooManyAssertsViolation TooDeepAccessViolation TooDeepNestingViolation LineComplexityViolation TooManyConditionsViolation TooManyElifsViolation TooManyForsInComprehensionViolation TooManyExceptCasesViolation OverusedStringViolation TooLongYieldTupleViolation TooLongCompareViolation TooLongTryBodyViolation TooManyPublicAttributesViolation Module complexity ----------------- .. autoclass:: JonesScoreViolation .. autoclass:: TooManyImportsViolation .. autoclass:: TooManyModuleMembersViolation .. autoclass:: TooManyImportedNamesViolation .. autoclass:: OverusedExpressionViolation Structure complexity -------------------- .. autoclass:: TooManyLocalsViolation .. autoclass:: TooManyArgumentsViolation .. autoclass:: TooManyReturnsViolation .. autoclass:: TooManyExpressionsViolation .. autoclass:: TooManyMethodsViolation .. autoclass:: TooManyBaseClassesViolation .. autoclass:: TooManyDecoratorsViolation .. autoclass:: TooManyAwaitsViolation .. autoclass:: TooManyAssertsViolation .. autoclass:: TooDeepAccessViolation .. autoclass:: TooDeepNestingViolation .. autoclass:: LineComplexityViolation .. autoclass:: TooManyConditionsViolation .. autoclass:: TooManyElifsViolation .. autoclass:: TooManyForsInComprehensionViolation .. autoclass:: TooManyExceptCasesViolation .. autoclass:: OverusedStringViolation .. autoclass:: TooLongYieldTupleViolation .. autoclass:: TooLongCompareViolation .. autoclass:: TooLongTryBodyViolation .. autoclass:: TooManyPublicAttributesViolation """ from typing_extensions import final from wemake_python_styleguide.violations.base import ( ASTViolation, MaybeASTViolation, SimpleViolation, ) @final class JonesScoreViolation(SimpleViolation): """ Forbids to have modules with complex lines. We are using Jones Complexity algorithm to count module's score. See :py:class:`~.LineComplexityViolation` for details of per-line-complexity. How it is done: we count complexity per line, then measuring the median complexity across the lines in the whole module. Reasoning: Having complex modules will decrease your code maintainability. Solution: Refactor the module contents. Configuration: This rule is configurable with ``--max-jones-score``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_JONES_SCORE` .. versionadded:: 0.1.0 See also: https://github.com/Miserlou/JonesComplexity """ error_template = 'Found module with high Jones Complexity score: {0}' code = 200 @final class TooManyImportsViolation(SimpleViolation): """ Forbids to have modules with too many imports. Namespaces are one honking great idea -- let's do more of those! Reasoning: Having too many imports without prefixes is quite expensive. You have to memorize all the source locations of the imports. And sometimes it is hard to remember what kind of functions and classes are already injected into your context. It is also a questionable design if a single module has a lot of imports. Why a single module has so many dependencies? So, it becomes too coupled. Solution: Refactor the imports to import a common namespace. Something like ``from package import module`` and then use it like ``module.function()``. Or refactor your code and split the complex module into several ones. We do not make any differences between ``import`` and ``from ... import ...``. Configuration: This rule is configurable with ``--max-imports``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_IMPORTS` .. versionadded:: 0.1.0 """ error_template = 'Found module with too many imports: {0}' code = 201 @final class TooManyModuleMembersViolation(SimpleViolation): """ Forbids to have many classes and functions in a single module. Reasoning: Having many classes and functions in a single module is a bad thing. Soon it will be hard to read through this code and understand it. Solution: It is better to split this module into several modules or a package. We do not make any differences between classes and functions in this check. They are treated as the same unit of logic. We also do not care about functions and classes being public or not. However, methods are counted separately on a per-class basis. Configuration: This rule is configurable with ``--max-module-members``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_MODULE_MEMBERS` .. versionadded:: 0.1.0 """ error_template = 'Found too many module members: {0}' code = 202 @final class TooManyImportedNamesViolation(SimpleViolation): """ Forbids to have modules with too many imported names. Namespaces are one honking great idea -- let's do more of those! Reasoning: Having too many imported names without prefixes is quite expensive. You have to memorize all the source locations of the imports. And sometimes it is hard to remember what kind of functions and classes are already injected into your context. It is also a questionable design if a single module has a lot of imports. Why a single module has so many dependencies? So, it becomes too coupled. Solution: Refactor the imports to import a common namespace. Something like ``from package import module`` and then use it like ``module.function()``. Or refactor your code and split the complex module into several ones. Example:: # Correct: import module # 1 imported name # Wrong: from module import func1, func2, ..., funcN # N imported names We do not make any differences between ``import`` and ``from ... import ...``. Configuration: This rule is configurable with ``--max-imported-names``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_IMPORTED_NAMES` .. versionadded:: 0.12.0 """ error_template = 'Found module with too many imported names: {0}' code = 203 @final class OverusedExpressionViolation(ASTViolation): """ Forbids to have overused expressions in a module, function or method. Reasoning: Overusing expression lead to losing the parts that can and should be refactored into methods and properties of objects. Solution: Refactor expressions to be attribute, method, or a new variable. Configuration: This rule is configurable with ``--max-module-expressions``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_MODULE_EXPRESSIONS` And with ``--max-function-expressions``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_FUNCTION_EXPRESSIONS` .. versionadded:: 0.12.0 """ error_template = 'Found overused expression: {0}' code = 204 # Functions and classes: @final class TooManyLocalsViolation(ASTViolation): """ Forbids to have too many local variables in the unit of code. Reasoning: Having too many variables in a single function is a bad thing. Soon, you will find troubles to understand what this variable means. It will also become hard to name new variables. Solution: If you have too many variables in a function, you have to refactor it. What counts as a local variable? We only count variable as local in the following case: it is assigned inside the function body. We do not count variables defined inside comprehensions as local variables, since it is impossible to use them outside of the comprehension. Example:: def first_function(param): first_var = 1 def second_function(argument): second_var = 1 argument = int(argument) third_var, _ = some_call() In this example we will count as locals only several variables: 1. ``first_var``, because it is assigned inside the function's body 2. ``second_var``, because it is assigned inside the function's body 3. ``argument``, because it is reassigned inside the function's body 4. ``third_var``, because it is assigned inside the function's body Please, note that ``_`` is a special case. It is not counted as a local variable. Since by design it means: do not count me as a real variable. Configuration: This rule is configurable with ``--max-local-variables``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_LOCAL_VARIABLES` .. versionadded:: 0.1.0 """ error_template = 'Found too many local variables: {0}' code = 210 @final class TooManyArgumentsViolation(ASTViolation): """ Forbids to have too many arguments for a function or method. Reasoning: This is an indicator of a bad design. When a function requires many arguments it shows that it is required to refactor this piece of code. It also indicates that function does too many things at once. Solution: Split function into several functions. Then it will be easier to use them. Configuration: This rule is configurable with ``--max-arguments``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_ARGUMENTS` .. versionadded:: 0.1.0 """ error_template = 'Found too many arguments: {0}' code = 211 @final class TooManyReturnsViolation(ASTViolation): """ Forbids placing too many ``return`` statements into the function. Reasoning: When there are too many ``return`` keywords, functions are hard to test. They are also hard to read and hard to change and keep everything inside your head at once. Solution: Change your design. Split functions into multiple ones. Configuration: This rule is configurable with ``--max-returns``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_RETURNS` .. versionadded:: 0.1.0 """ error_template = 'Found too many return statements: {0}' code = 212 @final class TooManyExpressionsViolation(ASTViolation): """ Forbids putting too many expressions in a unit of code. Reasoning: When there are too many expressions it means that this specific function does too many things at once. It has too much logic. Solution: Split function into several functions, refactor your API. Configuration: This rule is configurable with ``--max-expressions``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_EXPRESSIONS` .. versionadded:: 0.1.0 """ error_template = 'Found too many expressions: {0}' code = 213 @final class TooManyMethodsViolation(ASTViolation): """ Forbids to have many methods in a single class. Reasoning: Having too many methods might lead to the "God object". This kind of objects can handle everything. So, in the end, your code becomes too hard to maintain and test. Solution: What to do if you have too many methods in a single class? Split this class into several classes. Then use composition or inheritance to refactor your code. This will protect you from "God object" anti-pattern. We do not make any difference between instance and class methods. We also do not care about functions and classes being public or not. We also do not count inherited methods from parents. This rule does not count the attributes of a class. Configuration: This rule is configurable with ``--max-methods``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_METHODS` .. versionadded:: 0.1.0 See also: https://en.wikipedia.org/wiki/God_object """ error_template = 'Found too many methods: {0}' code = 214 @final class TooManyBaseClassesViolation(ASTViolation): """ Restrict the maximum number of base classes. Reasoning: It is almost never possible to navigate to the desired method of a parent class when you need it with multiple mixins. It is hard to understand ``mro`` and ``super`` calls. Do not overuse this technique. Solution: Reduce the number of base classes. Use composition over inheritance. Example:: # Correct: class SomeClassName(First, Second, Mixin): ... # Wrong: class SomeClassName( FirstParentClass, SecondParentClass, ThirdParentClass, CustomClass, AddedClass, ): ... Configuration: This rule is configurable with ``--max-base-classes``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_BASE_CLASSES` .. versionadded:: 0.3.0 .. versionchanged:: 0.5.0 See also: https://en.wikipedia.org/wiki/Composition_over_inheritance """ error_template = 'Too many base classes: {0}' code = 215 @final class TooManyDecoratorsViolation(ASTViolation): """ Restrict the maximum number of decorators. Reasoning: When you are using too many decorators it means that you try to overuse the magic. You have to ask yourself: do I really know what happens inside this decorator tree? Typically, the answer will be "no". Solution: Using too many decorators typically means that you try to configure the behavior from outside of the class. Do not do that too much. Split functions or classes into multiple ones. Use higher order decorators. Configuration: This rule is configurable with ``--max-decorators``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_DECORATORS` This rule checks: functions, methods, and classes. .. versionadded:: 0.5.0 """ error_template = 'Too many decorators: {0}' code = 216 @final class TooManyAwaitsViolation(ASTViolation): """ Forbids placing too many ``await`` expressions into a function. Reasoning: When there are too many ``await`` keywords, functions are starting to get really complex. It is hard to tell where are we and what is going on. Solution: Change your design. Split functions into multiple ones. Configuration: This rule is configurable with ``--max-awaits``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_AWAITS` .. versionadded:: 0.10.0 """ error_template = 'Found too many await expressions: {0}' code = 217 @final class TooManyAssertsViolation(ASTViolation): """ Forbids placing too many ``asseert`` statements into a function. Reasoning: When there are too many ``assert`` keywords, functions are starting to get really complex. It might indicate that your tests or contracts are too big. Solution: Create rich ``assert`` statements, use higher-level contracts, or create special guard functions. Configuration: This rule is configurable with ``--max-asserts``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_ASSERTS` .. versionadded:: 0.12.0 """ error_template = 'Found too many `assert` statements: {0}' code = 218 @final class TooDeepAccessViolation(ASTViolation): """ Forbids to have consecutive expressions with too deep access level. We consider only these expressions as accesses: - ``ast.Subscript`` - ``ast.Attribute`` We do not treat ``ast.Call`` as an access, since there are a lot of call-based APIs like Django ORM, builder patterns, etc. Reasoning: Having too deep access level indicates a bad design and overcomplicated data without proper API. Solution: Split the expression into variables, functions or classes. Refactor the API for your data layout. Example:: # Correct: access level = 4 self.attr.inner.wrapper[1] # Correct: access level = 1 manager.filter().exclude().annotate().values().first() # Wrong: access level = 5 self.attr.inner.wrapper.method.call() # Wrong: access level = 5 # `obj` has access level of 2: # `.attr`, `.call` # `call()` has access level of 5: # `.other`, `[0]`, `.field`, `.type`, `.boom` obj.attr.call().other[0].field.type.boom Configuration: This rule is configurable with ``--max-access-level``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_ACCESS_LEVEL` .. versionadded:: 0.12.0 """ error_template = 'Found too deep access level: {0}' code = 219 @final class TooDeepNestingViolation(ASTViolation): """ Forbids nesting blocks too deep. Reasoning: If nesting is too deep that indicates usage of complex logic and language constructions. This means that our design is not suited to handle such construction. Solution: We need to refactor our complex construction into simpler ones. We can use new functions or different constructions. .. versionadded:: 0.1.0 .. versionchanged:: 0.5.0 """ error_template = 'Found too deep nesting: {0}' code = 220 @final class LineComplexityViolation(ASTViolation): """ Forbids to have complex lines. We are using Jones Complexity algorithm to count complexity. What is Jones Complexity? It is a simple yet powerful method to count the number of ``ast`` nodes per line. If the complexity of a single line is higher than a threshold, then an error is raised. What nodes do we count? All except the following: 1. modules 2. function and classes, since they are checked differently 3. type annotations, since they do not increase the complexity Reasoning: Having a complex line indicates that you somehow managed to put too much logic inside a single line. At some point in time, you will no longer be able to understand what this line means and what it does. Solution: Split a single line into several lines: by creating new variables, statements or functions. Note, this might trigger new complexity issues. With this technique, a single new node in a line might trigger a complex refactoring process including several modules. Configuration: This rule is configurable with ``--max-line-complexity``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_LINE_COMPLEXITY` .. versionadded:: 0.1.0 See also: https://github.com/Miserlou/JonesComplexity """ error_template = 'Found line with high Jones Complexity: {0}' code = 221 @final class TooManyConditionsViolation(ASTViolation): """ Forbids to have conditions with too many logical operators. Reasoning: When reading through the complex conditions you will fail to understand all the possible branches. And you will end up putting debug breakpoint on this line just to figure out how it works. Solution: We can reduce the complexity of a single ``if`` by doing two things: creating new variables or creating nested ``if`` statements. Both of these actions will trigger other complexity checks. We count ``and`` and ``or`` keywords as conditions. .. versionadded:: 0.1.0 .. versionchanged:: 0.5.0 """ error_template = 'Found a condition with too much logic: {0}' code = 222 @final class TooManyElifsViolation(ASTViolation): """ Forbids to use many ``elif`` branches. Reasoning: This rule is specifically important because of many ``elif`` branches indicate a complex flow in your design: you are reimplementing ``switch`` in python. Solution: There are different design patterns to use instead. For example, you can use some interface that just call a specific method without ``if``. Or separate your ``if`` into multiple functions. .. versionadded:: 0.1.0 .. versionchanged:: 0.5.0 """ error_template = 'Found too many `elif` branches: {0}' code = 223 @final class TooManyForsInComprehensionViolation(ASTViolation): """ Forbids to have too many ``for`` statement within a comprehension. Reasoning: When reading through the complex comprehension you will fail to understand it. Solution: We can reduce the complexity of comprehension by reducing the amount of ``for`` statements. Refactor your code to use several ``for`` loops, comprehensions, or different functions. Example:: # Wrong: ast_nodes = [ target for assignment in top_level_assigns for target in assignment.targets for _ in range(10) ] .. versionadded:: 0.3.0 """ error_template = 'Found a comprehension with too many `for` statements' code = 224 @final class TooManyExceptCasesViolation(ASTViolation): """ Forbids to have too many ``except`` cases in a single ``try`` clause. Reasoning: Handling too many exceptions in a single place is a good indicator of a bad design. Since this way, one controlling structure will become too complex. And you will need to test a lot of paths your application might go. Solution: We can reduce the complexity of this case by splitting it into multiple ``try`` cases, functions or using a decorator to handle different exceptions. .. versionadded:: 0.7.0 """ error_template = 'Found too many `except` cases' code = 225 @final class OverusedStringViolation(MaybeASTViolation): """ Forbids to over-use string constants. We allow to use strings without any restrictions as annotations for variables, arguments, return values, and class attributes. Reasoning: When some string is used more than several time in your code, it probably means that this string is a meaningful constant. And should be treated like one. Solution: Deduplicate you string usages by defining new functions or constants. Configuration: This rule is configurable with ``--max-string-usages``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_STRING_USAGES` .. versionadded:: 0.10.0 """ error_template = 'Found string constant over-use: {0}' code = 226 @final class TooLongYieldTupleViolation(ASTViolation): """ Forbids to yield too long tuples. Reasoning: Long yield tuples complicate generator using. This rule helps to reduce complication. Solution: Use lists of similar type or wrapper objects. .. versionadded:: 0.10.0 """ error_template = 'Found too long yield tuple: {0}' code = 227 @final class TooLongCompareViolation(ASTViolation): """ Forbids to have too long compare expressions. Reasoning: To long compare expressions indicate that there's something wrong going on in the code. Compares should not be longer than 3 or 4 items. Solution: Use several conditions, seprate variables, or functions. .. versionadded:: 0.10.0 """ error_template = 'Found too long compare' code = 228 @final class TooLongTryBodyViolation(ASTViolation): """ Forbids to have ``try`` blocks with too long bodies. Reasoning: Having too many statements inside your ``try`` block can lead to situations when some different statement raises an exception and you are not aware of it since it is not expected. Solution: Move things out of the ``try`` block or create new functions. The less lines you have in your ``try`` block - the safer you are from accidental errors. Configuration: This rule is configurable with ``--max-try-body-length``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_TRY_BODY_LENGTH` .. versionadded:: 0.12.0 """ error_template = 'Found too long ``try`` body length: {0}' code = 229 @final class TooManyPublicAttributesViolation(ASTViolation): """ Forbids to have ``try`` blocks with too long bodies. We only check static definitions in a form of ``self.public = ...``. We do not count parent attributes. We do not count properties. We do not count annotations. We do not count class attributes. Reasoning: Having too many public instance attributes means that your class is too complex in terms of coupling. Other classes and functions will rely on these concrete fields instead of better abstraction layers. Solution: Make some attributes protected. Split this class into several ones. If class is a Data Transder Object, then use ``@dataclass`` decorator. Configuration: This rule is configurable with ``--max-attributes``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_ATTRIBUTES` See also: https://en.wikipedia.org/wiki/Coupling_(computer_programming) .. versionadded:: 0.12.0 """ error_template = 'Found too many public instance attributes' code = 230 PK!G 'Q֞֞2wemake_python_styleguide/violations/consistency.py# -*- coding: utf-8 -*- """ These checks limit the Python's inconsistency. We can do the same things differently in Python. For example, there are three ways to format a string. There are several ways to write the same number. We like our code to be consistent. It is easier to bare with your code base if you follow these rules. So, we choose a single way to do things. It does not mean that we choose the best way to do it. But, we value consistency more than being 100% right. And we are ready to suffer all trade-offs that might come. Once again, these rules are highly subjective. But, we love them. .. currentmodule:: wemake_python_styleguide.violations.consistency Summary ------- .. autosummary:: :nosignatures: LocalFolderImportViolation DottedRawImportViolation UnicodeStringViolation UnderscoredNumberViolation PartialFloatViolation FormattedStringViolation RequiredBaseClassViolation MultipleIfsInComprehensionViolation ConstantCompareViolation CompareOrderViolation BadNumberSuffixViolation MultipleInCompareViolation UselessCompareViolation MissingSpaceBetweenKeywordAndParenViolation ConstantConditionViolation ObjectInBaseClassesListViolation MultipleContextManagerAssignmentsViolation ParametersIndentationViolation ExtraIndentationViolation WrongBracketPositionViolation MultilineFunctionAnnotationViolation UppercaseStringModifierViolation WrongMultilineStringViolation EmptyLineAfterCodingViolation InconsistentReturnViolation InconsistentYieldViolation ImplicitStringConcatenationViolation UselessContinueViolation UselessNodeViolation UselessExceptCaseViolation UselessOperatorsViolation InconsistentReturnVariableViolation ImplicitTernaryViolation ImplicitComplexCompareViolation ReversedComplexCompareViolation WrongLoopIterTypeViolation ExplicitStringConcatViolation MultilineConditionsViolation WrongMethodOrderViolation NumberWithMeaninglessZeroViolation PositiveExponentViolation WrongHexNumberCaseViolation ImplicitRawStringViolation BadComplexNumberSuffixViolation ZeroDivisionViolation MeaninglessNumberOperationViolation OperationSignNegationViolation Consistency checks ------------------ .. autoclass:: LocalFolderImportViolation .. autoclass:: DottedRawImportViolation .. autoclass:: UnicodeStringViolation .. autoclass:: UnderscoredNumberViolation .. autoclass:: PartialFloatViolation .. autoclass:: FormattedStringViolation .. autoclass:: RequiredBaseClassViolation .. autoclass:: MultipleIfsInComprehensionViolation .. autoclass:: ConstantCompareViolation .. autoclass:: CompareOrderViolation .. autoclass:: BadNumberSuffixViolation .. autoclass:: MultipleInCompareViolation .. autoclass:: UselessCompareViolation .. autoclass:: MissingSpaceBetweenKeywordAndParenViolation .. autoclass:: ConstantConditionViolation .. autoclass:: ObjectInBaseClassesListViolation .. autoclass:: MultipleContextManagerAssignmentsViolation .. autoclass:: ParametersIndentationViolation .. autoclass:: ExtraIndentationViolation .. autoclass:: WrongBracketPositionViolation .. autoclass:: MultilineFunctionAnnotationViolation .. autoclass:: UppercaseStringModifierViolation .. autoclass:: WrongMultilineStringViolation .. autoclass:: EmptyLineAfterCodingViolation .. autoclass:: InconsistentReturnViolation .. autoclass:: InconsistentYieldViolation .. autoclass:: ImplicitStringConcatenationViolation .. autoclass:: UselessContinueViolation .. autoclass:: UselessNodeViolation .. autoclass:: UselessExceptCaseViolation .. autoclass:: UselessOperatorsViolation .. autoclass:: InconsistentReturnVariableViolation .. autoclass:: ImplicitTernaryViolation .. autoclass:: ImplicitComplexCompareViolation .. autoclass:: ReversedComplexCompareViolation .. autoclass:: WrongLoopIterTypeViolation .. autoclass:: ExplicitStringConcatViolation .. autoclass:: MultilineConditionsViolation .. autoclass:: WrongMethodOrderViolation .. autoclass:: NumberWithMeaninglessZeroViolation .. autoclass:: PositiveExponentViolation .. autoclass:: WrongHexNumberCaseViolation .. autoclass:: ImplicitRawStringViolation .. autoclass:: BadComplexNumberSuffixViolation .. autoclass:: ZeroDivisionViolation .. autoclass:: MeaninglessNumberOperationViolation .. autoclass:: OperationSignNegationViolation """ from typing_extensions import final from wemake_python_styleguide.violations.base import ( ASTViolation, TokenizeViolation, ) @final class LocalFolderImportViolation(ASTViolation): """ Forbids to have imports relative to the current folder. Reasoning: We should pick one style and stick to it. We have decided to use the explicit one. Solution: Refactor your imports to use the absolute path. Example:: # Correct: from my_package.version import get_version # Wrong: from .version import get_version from ..drivers import MySQLDriver .. versionadded:: 0.1.0 """ error_template = 'Found local folder import' code = 300 @final class DottedRawImportViolation(ASTViolation): """ Forbids to use imports like ``import os.path``. Reasoning: There too many different ways to import something. We should pick one style and stick to it. We have decided to use the readable one. Solution: Refactor your import statement. Example:: # Correct: from os import path # Wrong: import os.path .. versionadded:: 0.1.0 """ error_template = 'Found dotted raw import: {0}' code = 301 @final class UnicodeStringViolation(TokenizeViolation): """ Forbids to use ``u`` string prefix. Reasoning: We do not need this prefix since ``python2``. But, it is still possible to find it inside the codebase. Solution: Remove this prefix. Example:: # Correct: nickname = 'sobolevn' file_contents = b'aabbcc' # Wrong: nickname = u'sobolevn' .. versionadded:: 0.1.0 """ code = 302 error_template = 'Found unicode string prefix: {0}' @final class UnderscoredNumberViolation(TokenizeViolation): """ Forbids to use underscores (``_``) in numbers. Reasoning: It is possible to write ``1000`` in three different ways: ``1_000``, ``10_00``, and ``100_0``. And it would be still the same number. Count how many ways there are to write bigger numbers. Currently, it all depends on the cultural habits of the author. We enforce a single way to write numbers: without the underscore. Solution: Numbers should be written as numbers: ``1000``. If you have a very big number with a lot of zeros, use multiplication. Example:: # Correct: phone = 88313443 million = 1000000 # Wrong: phone = 8_83_134_43 million = 100_00_00 .. versionadded:: 0.1.0 """ code = 303 error_template = 'Found underscored number: {0}' @final class PartialFloatViolation(TokenizeViolation): """ Forbids to use partial floats like ``.05`` or ``23.``. Reasoning: Partial numbers are hard to read and they can be confused with other numbers. For example, it is really easy to confuse ``0.5`` and ``.05`` when reading through the source code. Solution: Use full versions with leading and starting zeros. Example:: # Correct: half = 0.5 ten_float = 10.0 # Wrong: half = .5 ten_float = 10. .. versionadded:: 0.1.0 """ code = 304 error_template = 'Found partial float: {0}' @final class FormattedStringViolation(ASTViolation): """ Forbids to use ``f`` strings. Reasoning: ``f`` strings loses context too often and they are hard to lint. Imagine that you have a string that breaks when you move it two lines above. That's not how a string should behave. Also, they promote a bad practice: putting your logic inside the template. Solution: Use ``.format()`` with indexed params instead. See also: https://github.com/xZise/flake8-string-format Example:: # Wrong: f'Result is: {2 + 2}' # Correct: 'Result is: {0}'.format(2 + 2) 'Hey {user}! How are you?'.format(user='sobolevn') .. versionadded:: 0.1.0 """ error_template = 'Found `f` string' code = 305 @final class RequiredBaseClassViolation(ASTViolation): """ Forbids to write classes without base classes. Reasoning: We just need to decide how to do it. We need a single and unified rule about base classes. We have decided to stick to the explicit base class notation. Solution: Add a base class. Example:: # Correct: class Some(object): ... # Wrong: class Some: ... See also: https://google.github.io/styleguide/pyguide.html#39-classes .. versionadded:: 0.1.0 """ error_template = 'Found class without a base class: {0}' code = 306 @final class MultipleIfsInComprehensionViolation(ASTViolation): """ Forbids to have multiple ``if`` statements inside list comprehensions. Reasoning: It is very hard to read multiple ``if`` statements inside a list comprehension. Since it is even hard to tell all of them should pass or fail. Solution: Use a single ``if`` statement inside list comprehensions. Use ``filter()`` if you have complicated logic. Example:: # Wrong: nodes = [node for node in html if node != 'b' if node != 'i'] # Correct: nodes = [node for node in html if node not in ('b', 'i')] .. versionadded:: 0.1.0 """ error_template = 'Found list comprehension with multiple `if`s' code = 307 @final class ConstantCompareViolation(ASTViolation): """ Forbids to have compares between two literals. Reasoning: When two constants are compared it is typically an indication of a mistake, since the Boolean value of the compare, will always be the same. Solution: Remove the constant compare and any associated dead code. Example:: # Wrong: if 60 * 60 < 1000: do_something() else: do_something_else() # Correct: do_something_else() .. versionadded:: 0.3.0 """ error_template = 'Found constant compare' code = 308 @final class CompareOrderViolation(ASTViolation): """ Forbids comparision where argument doesn't come first. Reasoning: It is hard to read the code when you have to shuffle ordering of the arguments all the time. Bring consistency to the compare! Solution: Refactor your compare expression, place the argument first. Example:: # Correct: if some_x > 3: if 3 < some_x < 10: # Wrong: if 3 < some_x: .. versionadded:: 0.3.0 """ error_template = 'Found reversed compare order' code = 309 @final class BadNumberSuffixViolation(TokenizeViolation): """ Forbids to use capital ``X``, ``O``, ``B``, and ``E`` in numbers. Reasoning: Octal, hex, binary and scientific notation suffixes could be written in two possible notations: lowercase and uppercase. Which brings confusion and decreases code consistency and readability. We enforce a single way to write numbers with suffixes: suffix with lowercase chars. Solution: Octal, hex, binary and scientific notation suffixes in numbers should be written lowercase. Example:: # Correct: hex_number = 0xFF octal_number = 0o11 binary_number = 0b1001 number_with_scientific_notation = 1.5e+10 # Wrong: hex_number = 0XFF octal_number = 0O11 binary_number = 0B1001 number_with_scientific_notation = 1.5E+10 .. versionadded:: 0.3.0 """ error_template = 'Found bad number suffix: {0}' code = 310 @final class MultipleInCompareViolation(ASTViolation): """ Forbids comparision where multiple ``in`` checks. Reasoning: Using multiple ``in`` is unreadable. Solution: Refactor your compare expression to use several ``and`` conditions or separate ``if`` statements in case it is appropriate. Example:: # Correct: if item in bucket and bucket in master_list_of_buckets: if x_coord not in line and line not in square: # Wrong: if item in bucket in master_list_of_buckets: if x_cord not in line not in square: .. versionadded:: 0.3.0 .. versionchanged:: 0.10.0 """ error_template = 'Found multiple `in` compares' code = 311 @final class UselessCompareViolation(ASTViolation): """ Forbids to have compares between the same variable. Reasoning: When the same variables are compared it is typically an indication of a mistake, since the Boolean value of the compare will always be the same. Solution: Remove the same variable compare and any associated dead code. Example:: # Wrong: a = 1 if a < a: do_something() else: do_something_else() # Correct: do_something() .. versionadded:: 0.3.0 """ error_template = 'Found compare between same variable' code = 312 @final class MissingSpaceBetweenKeywordAndParenViolation(TokenizeViolation): """ Enforces to separate parenthesis from the keywords with spaces. Reasoning: Some people use ``return`` and ``yield`` keywords as functions. The same happened to good old ``print`` in Python2. Solution: Insert space symbol between keyword and open paren. Example:: # Wrong: def func(): a = 1 b = 2 del(a, b) yield(1, 2, 3) # Correct: def func(): a = 1 del (a, b) yield (1, 2, 3) .. versionadded:: 0.3.0 """ error_template = 'Found parens right after a keyword' code = 313 @final class ConstantConditionViolation(ASTViolation): """ Forbids using ``if`` statements that use invalid conditionals. Reasoning: When invalid conditional arguments are used it is typically an indication of a mistake, since the value of the conditional result will always be the same. Solution: Remove the conditional and any associated dead code. Example:: # Correct: if value is True: ... # Wrong: if True: ... .. versionadded:: 0.3.0 """ error_template = 'Conditional always evaluates to same result' code = 314 @final class ObjectInBaseClassesListViolation(ASTViolation): """ Forbids extra ``object`` in parent classes list. Reasoning: We should allow object only when we explicitly use it as a single parent class. When there is another class or there are multiple parents - we should not allow it for the consistency reasons. Solution: Remove extra ``object`` parent class from the list. Example:: # Correct: class SomeClassName(object): ... class SomeClassName(FirstParentClass, SecondParentClass): ... # Wrong: class SomeClassName(FirstParentClass, SecondParentClass, object): ... .. versionadded:: 0.3.0 """ error_template = 'Founded extra `object` in parent classes list' code = 315 @final class MultipleContextManagerAssignmentsViolation(ASTViolation): """ Forbids multiple assignment targets for context managers. Reasoning: It is hard to distinguish whether ``as`` should unpack into tuple or we are just using two context managers. Solution: Use several context managers. Or explicit brackets. Example:: # Correct: with open('') as first: with second: ... with some_context as (first, second): ... # Wrong: with open('') as first, second: ... .. versionadded:: 0.6.0 """ error_template = 'Found context manager with too many assignments' code = 316 @final class ParametersIndentationViolation(ASTViolation): """ Forbids to use incorrect parameters indentation. Reasoning: It is really easy to spoil your perfect, readable code with incorrect multi-line parameters indentation. Since it is really easy to style them in any of 100 possible ways. We enforce a strict rule about how it is possible to write these multi-line parameters. Solution: Use consistent multi-line parameters indentation. Example:: # Correct: def my_function(arg1, arg2, arg3) -> None: return None print(1, 2, 3, 4, 5, 6) def my_function( arg1, arg2, arg3, ) -> None: return None print( 1, 2, 3, 4, 5, 6, ) def my_function( arg1, arg2, arg3, ) -> None: return None print( first_variable, 2, third_value, 4, 5, last_item, ) # Special case: print('some text', 'description', [ first_variable, second_variable, third_variable, last_item, ], end='') Everything else is considered a violation. This rule checks: lists, sets, tuples, dicts, calls, functions, methods, and classes. .. versionadded:: 0.6.0 """ error_template = 'Found incorrect multi-line parameters' code = 317 @final class ExtraIndentationViolation(TokenizeViolation): """ Forbids to use extra indentation. Reasoning: You can use extra indentation for lines of code. Python allows you to do that in case you will keep the indentation level equal for this specific node. But, that's insane! Solution: We should stick to 4 spaces for an indentation block. Each next block should be indented by just 4 extra spaces. Example:: # Correct: def test(): print('test') # Wrong: def test(): print('test') .. versionadded:: 0.6.0 """ error_template = 'Found extra indentation' code = 318 @final class WrongBracketPositionViolation(TokenizeViolation): """ Forbids to have brackets in the wrong position. Reasoning: You can do bizzare things with bracket positioning in python. We require all brackets to be consistent. Solution: Place bracket on the same line, when a single line expression. Or place the bracket on a new line when a multi-line expression. Example:: # Correct: print([ 1, 2, 3, ]) print( 1, 2, ) def _annotate_brackets( tokens: List[tokenize.TokenInfo], ) -> TokenLines: ... # Wrong: print([ 1, 2, 3], ) print( 1, 2) def _annotate_brackets( tokens: List[tokenize.TokenInfo]) -> TokenLines: ... We check round, square, and curly brackets. .. versionadded:: 0.6.0 """ error_template = 'Found bracket in wrong position' code = 319 @final class MultilineFunctionAnnotationViolation(ASTViolation): """ Forbids to use multi-line function type annotations. Reasoning: Functions with multi-line type annotations are unreadable. Solution: Use type annotations that fit into a single line to annotate functions. If your annotation is too long, then use type aliases. Example:: # Correct: def create_list(length: int) -> List[int]: ... # Wrong: def create_list(length: int) -> List[ int, ]: ... This rule checks argument and return type annotations. .. versionadded:: 0.6.0 """ error_template = 'Found multi-line function type annotation' code = 320 @final class UppercaseStringModifierViolation(TokenizeViolation): """ Forbids to use uppercase string modifiers. Reasoning: String modifiers should be consistent. Solution: Use lowercase modifiers should be written in lowercase. Example:: # Correct: some_string = r'/regex/' some_bytes = b'123' # Wrong: some_string = R'/regex/' some_bytes = B'123' .. versionadded:: 0.6.0 """ error_template = 'Found uppercase string modifier: {0}' code = 321 @final class WrongMultilineStringViolation(TokenizeViolation): ''' Forbids to use triple quotes for singleline strings. Reasoning: String quotes should be consistent. Solution: Use single quotes for single-line strings. Triple quotes are only allowed for real multiline strings. Example:: # Correct: single_line = 'abc' multiline = """ one two """ # Wrong: some_string = """abc""" some_bytes = b"""123""" Docstrings are ignored from this rule. You must use triple quotes strings for docstrings. .. versionadded:: 0.7.0 ''' error_template = 'Found incorrect multi-line string' code = 322 @final class EmptyLineAfterCodingViolation(TokenizeViolation): """ Enforces to have an extra empty line after the ``coding`` comment. Reasoning: Since we use `flake8-coding `_ as a part of our linter we care about extra space after this coding comment. This is done for pure consistency. Why should we even care about this magic coding comment? For several reasons. First, explicit encoding is always better that an implicit one, different countries still use some non utf-8 encodings as a default. But, people might override it with other encodings in a comment. Do you know how much pain it can cause to you? We still know that ``python3`` uses ``utf-8`` inside. Second, some tools break because of this incorrect encoding comment. Including, ``django``, ``flake8``, and ``tokenize`` core module. It is very hard to notice these things when they happen. Solution: Add an empty line between ``coding`` magic comment and your code. Example:: # Correct: # coding: utf-8 SOME_VAR = 1 # Wrong: # coding: utf-8 SOME_VAR = 1 .. versionadded:: 0.7.0 """ error_template = ( 'Found missing empty line between `coding` magic comment and code' ) code = 323 @final class InconsistentReturnViolation(ASTViolation): """ Enforces to have consistent ``return`` statements. Rules are: 1. if any ``return`` has a value, all ``return`` nodes should have a value 2. do not place ``return`` without value at the end of a function This rule respects ``mypy`` style of placing ``return`` statements. There should be no conflict with these two checks. Reasoning: This is done for pure consistency and readability of your code. Eventually, this rule may also find some bugs in your code. Solution: Add or remove values from the ``return`` statements to make them consistent. Remove ``return`` statement from the function end. Example:: # Correct: def function(): if some: return 2 return 1 # Wrong: def function(): if some: return return 1 .. versionadded:: 0.7.0 """ error_template = 'Found inconsistent `return` statement' code = 324 @final class InconsistentYieldViolation(ASTViolation): """ Enforces to have consistent ``yield`` statements. Rules are: 1. if any ``yield`` has a value, all ``yield`` nodes should have a value This rule respects ``mypy`` style of placing ``yield`` statements. There should be no conflict with these two checks. Reasoning: This is done for pure consistency and readability of your code. Eventually, this rule may also find some bugs in your code. Solution: Add or remove values from the ``yield`` statements to make them consistent. Example:: # Correct: def function(): if some: yield 2 yield 1 # Wrong: def function(): if some: yield yield 1 .. versionadded:: 0.7.0 """ error_template = 'Found inconsistent `yield` statement' code = 325 @final class ImplicitStringConcatenationViolation(TokenizeViolation): """ Forbids to use implicit string contacatenation. Reasoning: This is error-prone, since you can possible miss a comma in a collection of string and get an implicit concatenation. And because there are different and safe ways to do the same thing it is better to use them instead. Solution: Use ``+`` or ``.format()`` to join strings. Example:: # Correct: text = 'first' + 'second' # Wrong: text = 'first' 'second' .. versionadded:: 0.7.0 """ error_template = 'Found implicit string concatenation' code = 326 @final class UselessContinueViolation(ASTViolation): """ Forbids to use meaningless ``continue`` node in loops. Reasoning: Placing this keyword in the end of any loop won't make any difference to your code. And we prefer not to have meaningless constructs in our code. Solution: Remove useless ``continue`` node from the loop. Example:: # Correct: for number in [1, 2, 3]: if number < 2: continue print(number) # Wrong: for number in [1, 2, 3]: print(number) continue .. versionadded:: 0.7.0 """ error_template = 'Found useless `continue` at the end of the loop' code = 327 @final class UselessNodeViolation(ASTViolation): """ Forbids to use meaningless nodes. Reasoning: Some nodes might be completely useless. They will literally do nothing. Sometimes they are hard to find, because this situation can be caused by a recent refactoring or just by acedent. This might be also an overuse of syntax. Solution: Remove node or make sure it makes any sense. Example:: # Wrong: for number in [1, 2, 3]: break .. versionadded:: 0.7.0 """ error_template = 'Found useless node: {0}' code = 328 @final class UselessExceptCaseViolation(ASTViolation): """ Forbids to use meaningless ``except`` cases. Reasoning: Using ``except`` cases that just reraise the same exception is error-prone. You can increase your stacktrace, silence some potential exceptions, and screw things up. It also does not make any sense to do so. Solution: Remove ``except`` case or make sure it makes any sense. Example:: # Correct: try: ... except IndexError: sentry.log() raise ValueError() # Wrong: try: ... except TypeError: raise .. versionadded:: 0.7.0 """ error_template = 'Found useless `except` case' code = 329 @final class UselessOperatorsViolation(ASTViolation): """ Forbids the use of unnecessary operators in your code. You can write: ``5.4`` and ``+5.4``. There's no need to use the second version. Similarly ``--5.4``, ``---5.4``, ``not not foo``, and ``~~42`` contain unnecessary operators. Reasoning: This is done for consistency reasons. Solution: Omit unnecessary operators. Example:: # Correct: profit = 3.33 profit = -3.33 inverse = ~5 complement = not foo # Wrong: profit = +3.33 profit = --3.33 profit = ---3.33 number = ~~42 bar = not not foo .. versionadded:: 0.8.0 """ code = 330 error_template = 'Found unnecessary operator: {0}' @final class InconsistentReturnVariableViolation(ASTViolation): """ Forbids local variable that are only used in ``return`` statements. Reasoning: This is done for consistency and more readable source code. Solution: Return the expression itself, instead of creating a temporary variable. Example:: # Correct: def some_function(): return 1 # Wrong: def some_function(): some_value = 1 return some_value .. versionadded:: 0.9.0 """ error_template = ( 'Found local variable that are only used in `return` statements' ) code = 331 @final class ImplicitTernaryViolation(ASTViolation): """ Forbids to have implicit ternary expressions. Reasoning: This is done for consistency and readability reasons. We believe that explicit ternary is better for readability. This also allows you to identify hidden conditionals in your code. Solution: Refactor to use explicit ternary, or ``if`` condition. Example:: # Correct: some = one if cond() else two # Wrong: some = cond() and one or two .. versionadded:: 0.10.0 """ code = 332 error_template = 'Found implicit ternary expression' @final class ImplicitComplexCompareViolation(ASTViolation): """ Forbids to have implicit complex compare expressions. Reasoning: Two compares in python that are joined with ``and`` operator mean that you indeed have a complex compare with tree operators. Solution: Refactor your compare without ``and`` but with the third operator. Notice, that you migth have to change the ordering. Example:: # Correct: if three < two < one: ... # Wrong: if one > two and two > three: ... .. versionadded:: 0.10.0 """ code = 333 error_template = 'Found implicit complex compare' @final class ReversedComplexCompareViolation(ASTViolation): """ Forbids to have reversed order complex compare expressions. Reasoning: Compares where comparators start from the lowest element are easier to read than one that start from the biggest one. It is also possible to write the same expression in two separate way, which is incosistent. Solution: Reverse the order, so the smallest element comes the first and the biggest one comes the last. Example:: # Correct: if three < two < one: ... # Wrong: if one > two > three: ... .. versionadded:: 0.10.0 """ code = 334 error_template = 'Found reversed complex compare' @final class WrongLoopIterTypeViolation(ASTViolation): """ Forbids to use wrong ``for`` loop iter targets. We forbid to use: - Lists and list comprehensions - Sets and set comprehensions - Dicts and dict comprehensions - Generator expressions - Empty tuples Reasoning: Using lists, dicts, and sets do not make much sense. You can use tuples instead. Using comprehensions implicitly create a two level loops, that are hard to read and deal with. Solution: Use tuples to create explicit iterables for ``for`` loops. In case you are using a comprehension, create a new variable. Example:: # Correct: for person in ('Kim', 'Nick'): ... # Wrong: for person in ['Kim', 'Nick']: ... .. versionadded:: 0.10.0 .. versionchanged:: 0.12.0 """ code = 335 error_template = 'Found incorrect `for` loop iter type' @final class ExplicitStringConcatViolation(ASTViolation): """ Forbids explicit string concat in favour of ``.format`` method. However, we still allow multiline string concat as a way to write long stirngs that does not fit the 80-chars rule. Reasoning: When formating strings one must use ``.format`` and not any other formatting methods like ``%``, ``+``, or ``f``. This is done for consistency reasons. Solution: Join strings together if you can, or use ``.format`` method. Example:: # Correct: x = 'ab: {0}'.format(some_data) # Wrong: x = 'a' + 'b: ' + some_data .. versionadded:: 0.12.0 """ code = 336 error_template = 'Found explicit string concat' @final class MultilineConditionsViolation(ASTViolation): """ Forbids multiline conditions. Reasoning: This way of writing conditions hides the inner complexity this line has. And it decreases readability of the code. Solution: Divide multiline conditions to some ``if`` condition. Or use variables. Example:: # Correct: if isinstance(node.test, ast.UnaryOp): if isinstance(node.test.op, ast.Not): ... # Wrong: if isinstance(node.test, ast.UnaryOp) and isinstance( node.test.op, ast.Not, ): ... .. versionadded:: 0.9.0 .. versionchanged:: 0.11.0 """ error_template = 'Found multiline conditions' code = 337 previous_codes = {465} @final class WrongMethodOrderViolation(ASTViolation): """ Forbids to have incorrect order of methods inside a class. We follow the same ordering: - ``__new__`` - ``__init__`` - public and megic methods - protected methods - private methods (we discourage to use them) We follow "Newspaper order" when the most important things come the first. Reasoning: It is hard to read classes which API declarations is bloated with implementation details. We need to see the important stuff first, then we can go deeper in case we are interested. Solution: Reorder methods inside your class to match our format. .. versionadded:: 0.12.0 """ error_template = 'Found incorrect order of methods in a class' code = 338 @final class NumberWithMeaninglessZeroViolation(TokenizeViolation): """ Forbids to use meaningless zeros. We discorauge using meaningless zeros in float, binary, octal, hex, and expanentional numbers. Reasoning: There are ~infinitive ways to write these numbers by adding meaningless leading zeros to the number itself. ``0b1`` is the same as ``0b01`` and ``0b001``. How a language can be called consistent if you can write numbers in a infinite ways? It hurts readability and understanding of your code. Solution: Remove meaningless leading zeros. Example:: # Correct: numbers = [1.5, 0b1, 0o2, 0x5, 10e10] # Wrong: numbers = [1.50, 0b00000001, 0o0002, 0x05, 10e010] .. versionadded:: 0.12.0 """ error_template = 'Found number with meaningless zeros: {0}' code = 339 @final class PositiveExponentViolation(TokenizeViolation): """ Forbids to extra ``+`` signs in the exponent. Reasoning: Positive exponent is positive by default, there's no need to write an extra ``+`` sign. We enforce consistency with this rule. Solution: Remove meaningless ``+`` sign from the exponent. Example:: # Correct: number = 1e1 + 1e-1 # Wrong: number = 1e+1 .. versionadded:: 0.12.0 """ error_template = 'Found exponent number with positive exponent: {0}' code = 340 @final class WrongHexNumberCaseViolation(TokenizeViolation): """ Forbids use lower-case letters as hex numbers. Reasoning: One can write ``0xA`` and ``0xa`` which is inconsistent. This rule enforces upper-case letters in hex numbers. Solution: Use upper-case letters in hex numbers. Example:: # Correct: number = 0xABCDEF # Wrong: number = 0xabcdef .. versionadded:: 0.12.0 """ error_template = 'Found wrong hex number case: {0}' code = 341 @final class ImplicitRawStringViolation(TokenizeViolation): r""" Forbids to use ``\\`` escape sequences inside regular strings. Reasoning: It is hard to read escape sequencse inside regular strings, because they use ``\\`` double backslash for a single character escape. Solution: Use raw strings ``r''`` to rewrite the escape sequence with a ``\`` single backslash. Example:: # Correct: escaped = [r'\n', '\n'] # Wrong: escaped = '\\n' .. versionadded:: 0.12.0 """ error_template = 'Found implicit raw string: {0}' code = 342 @final class BadComplexNumberSuffixViolation(TokenizeViolation): """ Forbids to use uppercase complex number suffix. Reasoning: Numbers should be consistent. Solution: Use lowercase suffix for imaginary part. Example:: # Correct: complex_number = 1j # Wrong: complex_number = 1J .. versionadded:: 0.12.0 """ error_template = 'Found wrong complex number suffix: {0}' code = 343 @final class ZeroDivisionViolation(ASTViolation): """ Forbids to explicitly divide by zero. Reasoning: This will just throw ``ZeroDivisoionError`` in case that's what you need: just throw it. No need to use undefined meth behaviours. Or it might be just a typo / mistake, then fix it. Solution: Use ``ZeroDivisoionError`` or fix your number not to be ``0``. Example:: # Correct: raise ZeroDivisoionError() # Wrong: 1 / 0 .. versionadded:: 0.12.0 """ error_template = 'Found explicit zero division' code = 344 @final class MeaninglessNumberOperationViolation(ASTViolation): """ Forbids to use meaningless math opeartions with ``0`` and ``1``. Reasoning: Adding and substracting zero does not change the value. There's no need to do that. Multipling by zero is also redundunt: it can be replaced with explicit ``0`` assign. Multiplying and dividing by ``1`` is also meaningless. Solution: Remove useless zero operaionts. Example:: # Correct: number = 1 zero = 0 one = 1 # Wrong: number = 1 + 0 * 1 zero = some * 0 / 1 one = some ** 0 ** 1 .. versionadded:: 0.12.0 """ error_template = 'Found meaningless number operation' code = 345 @final class OperationSignNegationViolation(ASTViolation): """ Forbids to have double minus operations. Reasoning: Having two operations is harder than having just one. Two negations are harder than one positive expression. Two negations equal to one positive expression. Positive and negative equal to one negative. Solution: Replace double minus operation to a single one with plus. Replace 'plus-minus' operation to a single one with minus. Example:: # Correct: number = 3 + 1 number += 6 number -= 2 # Wrong: number = 3 - -1 number -= -6 number += -2 .. versionadded:: 0.12.0 """ error_template = 'Found wrong operation sign' code = 346 PK!HH-wemake_python_styleguide/violations/naming.py# -*- coding: utf-8 -*- """ Naming is hard! It is, in fact, one of the two hardest problems. These checks are required to make your application easier to read and understand by multiple people over the long period of time. Naming convention ----------------- Our naming convention tries to cover all possible cases. It is partially automated with this linter, but: - Some rules are still WIP - Some rules will never be automated, code reviews to the rescue! General ~~~~~~~ - Use only ``ASCII`` chars for names - Do not use transliteration from any other languages, translate names instead - Use clear names, do not use words that do not mean anything like ``obj`` - Use names of an appropriate length: not too short, not too long - Protected members should use underscore as the first char - Private names with two leading underscores are not allowed - If you need to explicitly state that the variable is unused, prefix it with ``_`` or just use ``_`` as a name - Do not use variables that are stated to be unused, rename them when actually using them - Do not define unused variables unless you are unpacking other values as well - Do not use multiple underscores (``__``) to create unused variables - Whenever you want to name your variable similar to a keyword or builtin, use trailing ``_`` - Do not use consecutive underscores - When writing abbreviations in ``UpperCase`` capitalize all letters: ``HTTPAddress`` - When writing abbreviations in ``snake_case`` use lowercase: ``http_address`` - When writing numbers in ``snake_case`` do not use extra ``_`` before numbers as in ``http2_protocol`` Packages ~~~~~~~~ - Packages must use ``snake_case`` - One word for a package is the most preferable name Modules ~~~~~~~ - Modules must use ``snake_case`` - Module names must not overuse magic names - Module names must be valid Python identifiers Classes ~~~~~~~ - Classes must use ``UpperCase`` - Python's built-in classes, however, are typically lowercase words - Exception classes must end with ``Error`` Instance attributes ~~~~~~~~~~~~~~~~~~~ - Instance attributes must use ``snake_case`` with no exceptions Class attributes ~~~~~~~~~~~~~~~~ - Class attributes must use ``snake_case`` with no exceptions - Enum fields also must use ``snamek_case`` Functions and methods ~~~~~~~~~~~~~~~~~~~~~ - Functions and methods must use ``snake_case`` with no exceptions Method and function arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Instance methods must have their first argument named ``self`` - Class methods must have their first argument named ``cls`` - Metaclass methods must have their first argument named ``mcs`` - Python's ``*args`` and ``**kwargs`` should be default names when just passing these values to some other method/function, unless you want to use these values in place, then name them explicitly - Keyword-only arguments must be separated from other arguments with ``*`` Global (module level) variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Global variables must use ``CONSTANT_CASE`` - Unless other is required by the API, example: ``urlpatterns`` in Django Variables ~~~~~~~~~ - Variables must use ``snake_case`` with no exceptions - When a variable is unused it must be prefixed with an underscore: ``_user`` Type aliases ~~~~~~~~~~~~ - Must use ``UpperCase`` as real classes - Must not contain word ``type`` in its name - Generic types should be called ``TT`` or ``KT`` or ``VT`` - Covariant and contravariant types should be marked with ``Cov`` and ``Contra`` suffixes, in this case, one letter can be dropped: ``TCov`` and ``KContra`` .. currentmodule:: wemake_python_styleguide.violations.naming Summary ------- .. autosummary:: :nosignatures: WrongModuleNameViolation WrongModuleMagicNameViolation WrongModuleNamePatternViolation WrongVariableNameViolation TooShortNameViolation PrivateNameViolation SameAliasImportViolation UnderscoredNumberNameViolation UpperCaseAttributeViolation ConsecutiveUnderscoresInNameViolation ReservedArgumentNameViolation TooLongNameViolation UnicodeNameViolation TrailingUnderscoreViolation UnusedVariableIsUsedViolation UnusedVariableIsDefinedViolation WrongUnusedVariableNameViolation Module names ------------ .. autoclass:: WrongModuleNameViolation .. autoclass:: WrongModuleMagicNameViolation .. autoclass:: WrongModuleNamePatternViolation General names ------------- .. autoclass:: WrongVariableNameViolation .. autoclass:: TooShortNameViolation .. autoclass:: PrivateNameViolation .. autoclass:: SameAliasImportViolation .. autoclass:: UnderscoredNumberNameViolation .. autoclass:: UpperCaseAttributeViolation .. autoclass:: ConsecutiveUnderscoresInNameViolation .. autoclass:: ReservedArgumentNameViolation .. autoclass:: TooLongNameViolation .. autoclass:: UnicodeNameViolation .. autoclass:: TrailingUnderscoreViolation .. autoclass:: UnusedVariableIsUsedViolation .. autoclass:: UnusedVariableIsDefinedViolation .. autoclass:: WrongUnusedVariableNameViolation """ from typing_extensions import final from wemake_python_styleguide.violations.base import ( ASTViolation, MaybeASTViolation, SimpleViolation, ) @final class WrongModuleNameViolation(SimpleViolation): """ Forbids to use blacklisted module names. Reasoning: Some module names are not expressive enough. It is hard to tell what you can find inside the ``utils.py`` module. Solution: Rename your module, reorganize the contents. See :py:data:`~wemake_python_styleguide.constants.MODULE_NAMES_BLACKLIST` for the full list of bad module names. Example:: # Correct: github.py views.py # Wrong: utils.py helpers.py .. versionadded:: 0.1.0 """ error_template = 'Found wrong module name' code = 100 @final class WrongModuleMagicNameViolation(SimpleViolation): """ Forbids to use any magic names except whitelisted ones. Reasoning: Do not fall in love with magic. There's no good reason to use magic names when you can use regular names. See :py:data:`~wemake_python_styleguide.constants.MAGIC_MODULE_NAMES_WHITELIST` for the full list of allowed magic module names. Example:: # Correct: __init__.py __main__.py # Wrong: __version__.py .. versionadded:: 0.1.0 """ error_template = 'Found wrong module magic name' code = 101 @final class WrongModuleNamePatternViolation(SimpleViolation): """ Forbids to use module names that do not match our pattern. Reasoning: Module names must be valid python identifiers. And just like the variable names - module names should be consistent. Ideally, they should follow the same rules. For ``python`` world it is common to use ``snake_case`` notation. We use :py:data:`~wemake_python_styleguide.constants.MODULE_NAME_PATTERN` to validate the module names. Example:: # Correct: __init__.py some_module_name.py test12.py # Wrong: _some.py MyModule.py 0001_migration.py .. versionadded:: 0.1.0 """ error_template = 'Found incorrect module name pattern' code = 102 # General names: @final class WrongVariableNameViolation(ASTViolation): """ Forbids to have blacklisted variable names. Reasoning: We have found some names that are not expressive enough. However, they appear in the code more than often. All names that we forbid to use could be improved. Solution: Try to use a more specific name instead. If you really want to use any of the names from the list, add a prefix or suffix to it. It will serve you well. See :py:data:`~wemake_python_styleguide.constants.VARIABLE_NAMES_BLACKLIST` for the full list of blacklisted variable names. Example:: # Correct: html_node_item = None # Wrong: item = None .. versionadded:: 0.1.0 """ error_template = 'Found wrong variable name: {0}' code = 110 @final class TooShortNameViolation(MaybeASTViolation): """ Forbids to have too short variable or module names. Reasoning: It is hard to understand what the variable means and why it is used, if its name is too short. Solution: Think of another name. Give more context to it. This rule checks: modules, variables, attributes, functions, methods, and classes. We do not count trailing and leading underscores when calculating length. Example:: # Correct: x_coordinate = 1 abscissa = 2 # Wrong: x = 1 y = 2 Configuration: This rule is configurable with ``--min-name-length``. Default: :str:`wemake_python_styleguide.options.defaults.MIN_NAME_LENGTH` .. versionadded:: 0.1.0 .. versionchanged:: 0.4.0 .. versionchanged:: 0.12.0 """ error_template = 'Found too short name: {0}' code = 111 @final class PrivateNameViolation(MaybeASTViolation): """ Forbids to have private name pattern. Reasoning: Private is not private in ``python``. So, why should we pretend it is? This might lead to some serious design flaws. Solution: Rename your variable or method to be protected. Think about your design, why do you want to make it private? Are there any other ways to achieve what you want? This rule checks: modules, variables, attributes, functions, and methods. Example:: # Correct: def _collect_coverage(self): ... # Wrong: def __collect_coverage(self): ... .. versionadded:: 0.1.0 .. versionchanged:: 0.4.0 """ error_template = 'Found private name pattern: {0}' code = 112 @final class SameAliasImportViolation(ASTViolation): """ Forbids to use the same alias as the original name in imports. Reasoning: Why would you even do this in the first place? Example:: # Correct: from os import path # Wrong: from os import path as path .. versionadded:: 0.1.0 """ error_template = 'Found same alias import: {0}' code = 113 @final class UnderscoredNumberNameViolation(MaybeASTViolation): """ Forbids to have names with underscored numbers pattern. Reasoning: This is done for consistency in naming. Solution: Do not put an underscore between text and numbers, that is confusing. Rename your variable or modules do not include underscored numbers. This rule checks: modules, variables, attributes, functions, method, and classes. Please, note that putting an underscore that replaces ``-`` in some names between numbers are fine, example: ``ISO-123-456`` would become ``iso123_456``. Example:: # Correct: star_wars_episode2 = 'awesome!' iso123_456 = 'some data' # Wrong: star_wars_episode_2 = 'not so awesome' iso_123_456 = 'some data' .. versionadded:: 0.3.0 .. versionchanged:: 0.4.0 """ error_template = 'Found underscored name pattern: {0}' code = 114 @final class UpperCaseAttributeViolation(ASTViolation): """ Forbids to use anything but ``snake_case`` for naming class attributes. Reasoning: Constants with upper-case names belong on a module level. Solution: Move your constants to the module level. Rename your variables so that they conform to ``snake_case`` convention. Example:: # Correct: MY_MODULE_CONSTANT = 1 class A(object): my_attribute = 42 # Wrong: class A(object): MY_CONSTANT = 42 .. versionadded:: 0.3.0 """ error_template = 'Found upper-case constant in a class: {0}' code = 115 @final class ConsecutiveUnderscoresInNameViolation(MaybeASTViolation): """ Forbids to use more than one consecutive underscore in variable names. Reasoning: This is done to gain extra readability. This naming rule already exists for module names. Example:: # Correct: some_value = 5 __magic__ = 5 # Wrong: some__value = 5 This rule checks: modules, variables, attributes, functions, and methods. .. versionadded:: 0.3.0 .. versionchanged:: 0.4.0 """ error_template = 'Found consecutive underscores name: {0}' code = 116 @final class ReservedArgumentNameViolation(ASTViolation): """ Forbids to name your variables as ``self``, ``cls``, and ``mcs``. Reasoning: These names are special, they should only be used as first arguments inside methods. Example:: # Correct: class Test(object): def __init__(self): ... # Wrong: cls = 5 lambda self: self + 12 This rule checks: functions and methods. Having any reserved names in ``lambda`` functions is not allowed. .. versionadded:: 0.5.0 """ error_template = 'Found name reserved for first argument: {0}' code = 117 @final class TooLongNameViolation(MaybeASTViolation): """ Forbids to have long short variable or module names. Reasoning: Too long names are unreadable. It is better to use a shorter alternative. Long names also indicate that this variable is too complex, maybe it may require some documentation. Solution: Think of another name. Give less context to it. This rule checks: modules, variables, attributes, functions, methods, and classes. Example:: # Correct: total_price = 25 average_age = 45 # Wrong: final_price_after_fifteen_percent_sales_tax_and_gratuity = 30 total_age_of_all_participants_in_the_survey_divided_by_twelve = 2 Configuration: This rule is configurable with ``--max-name-length``. Default: :str:`wemake_python_styleguide.options.defaults.MAX_NAME_LENGTH` .. versionadded:: 0.5.0 """ error_template = 'Found too long name: {0}' code = 118 @final class UnicodeNameViolation(MaybeASTViolation): """ Forbids to use unicode names. Reasoning: This should be forbidden for sanity, readability, and writability. Solution: Rename your entities so that they contain only ASCII symbols. This rule checks: modules, variables, attributes, functions, methods, and classes. Example:: # Correct: some_variable = 'Text with russian: русский язык' # Wrong: переменная = 42 some_變量 = '' .. versionadded:: 0.5.0 """ error_template = 'Found unicode name: {0}' code = 119 @final class TrailingUnderscoreViolation(ASTViolation): """ Forbids to use trailing ``_`` for names that do not need it. Reasoning: We use trailing underscore for a reason: to indicate that this name shadows a built-in or keyword. So, when overusing this feature for general names: it just harms readability of your program. Solution: Rename your variable not to contain trailing underscores. This rule checks: variables, attributes, functions, methods, and classes. Example:: # Correct: class_ = SomeClass list_ = [] # Wrong: some_variable_ = 1 .. versionadded:: 0.7.0 """ error_template = 'Found regular name with trailing underscore: {0}' code = 120 @final class UnusedVariableIsUsedViolation(ASTViolation): """ Forbids to have use variables that are marked as unused. We discourage using ``_`` at all and variables that start with ``_`` only inside functions and methods as local variables. Reasoning: Sometimes you start to use new logic in your functions, and you start to use variables that once were marked as unused. But, you have not renamed them for some reason. And now you have a lot of confusion: the variable is marked as unused, but you are using it. Why? What's going on? Solution: Rename your variable to be a regular variable without a leading underscore. Example:: # Correct: def function(): first = 15 return first + 10 # Wrong: def function(): _first = 15 return _first + 10 This rule checks: functions, methods, and ``lambda`` functions. .. versionadded:: 0.7.0 .. versionchanged:: 0.12.0 """ error_template = 'Found usage of a variable marked as unused: {0}' code = 121 @final class UnusedVariableIsDefinedViolation(ASTViolation): """ Forbids to define explicit unused variables. Reasoning: While it is ok to define unused variables when you have to, like when unpacking a tuple, it is totally not ok to define explicit unusued variables in cases like assignment, function return, exception handling, or context managers. Why do you need this explicitly unused variables? Solution: Remove all unused variables definition. Example:: # Correct: my_function() first, _second = some_tuple() print(first) # Wrong: _ = my_function() _first, _second = some_tuple() This rule checks: assigns, context managers, except clauses. .. versionadded:: 0.12.0 """ error_template = 'Found all unused variables definition: {0}' code = 122 @final class WrongUnusedVariableNameViolation(ASTViolation): """ Forbids to define unused variables with multiple underscores. Reasoning: We only use ``_`` as a special definition for an unused variable. Other variables are hard to read. It is unclear why would one use it. Solution: Rename unused variables to ``_`` or give it some more context with an explicit name: ``_context``. Example:: # Correct: some_element, _next_element, _ = some_tuple() some_element, _, _ = some_tuple() some_element, _ = some_tuple() # Wrong: some_element, _, __ = some_tuple() .. versionadded:: 0.12.0 """ error_template = 'Found wrong unused variable name: {0}' code = 123 PK!E|55*wemake_python_styleguide/violations/oop.py# -*- coding: utf-8 -*- """ These checks ensures that you use Python's version of OOP correctly. There are different gotchas in Python to write beatiful classes and using objects correctly. That's the place we collect these kind of rules. .. currentmodule:: wemake_python_styleguide.violations.oop Summary ------- .. autosummary:: :nosignatures: BuiltinSubclassViolation ShadowedClassAttributeViolation StaticMethodViolation BadMagicMethodViolation WrongClassBodyContentViolation MethodWithoutArgumentsViolation WrongBaseClassViolation WrongSlotsViolation WrongSuperCallViolation DirectMagicAttributeAccessViolation AsyncMagicMethodViolation YieldMagicMethodViolation UselessOverwrittenMethodViolation Respect your objects -------------------- .. autoclass:: BuiltinSubclassViolation .. autoclass:: ShadowedClassAttributeViolation .. autoclass:: StaticMethodViolation .. autoclass:: BadMagicMethodViolation .. autoclass:: WrongClassBodyContentViolation .. autoclass:: MethodWithoutArgumentsViolation .. autoclass:: WrongBaseClassViolation .. autoclass:: WrongSlotsViolation .. autoclass:: WrongSuperCallViolation .. autoclass:: DirectMagicAttributeAccessViolation .. autoclass:: AsyncMagicMethodViolation .. autoclass:: YieldMagicMethodViolation .. autoclass:: UselessOverwrittenMethodViolation """ from typing_extensions import final from wemake_python_styleguide.violations.base import ASTViolation @final class BuiltinSubclassViolation(ASTViolation): """ Forbids to subclass lowercase builtins. We forbid to subclass builtins like ``int``, ``str``, ``bool``, etc. We allow to subclass ``object`` and ``type``, warnings, and exceptions. See :py:data:`~wemake_python_styleguide.constants.ALLOWED_BUILTIN_CLASSES` for the whole list of whitelisted names. Reasoning: It is almost never a good idea (unless you do something sneaky) to subclass primitive builtins. Solution: Use custom objects around some wrapper. Use magic methods to emulate the desired behaviour. Example:: # Correct: class Some(object): ... class MyValueException(ValueError): ... # Wrong: class MyInt(int): ... .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found subclassing a builtin: {0}' code = 600 previous_codes = {426} @final class ShadowedClassAttributeViolation(ASTViolation): """ Forbids to shadow class level attributes with instance level attributes. Reasoning: This way you will have two attributes inside your ``__mro__`` chain: one from instance and one from class. It might cause errors. Needless to say, that this is just pointless to do so. Solution: Use either class attributes or instance attributes. Use ``ClassVar`` type on fields that are declared as class attributes. Note, that we cannot find shadowed attributes that are defined in parent classes. That's where ``ClassVar`` is required for ``mypy`` to check it for you. Example:: # Correct: from typing import ClassVar class First(object): field: ClassVar[int] = 1 class Second(object): field: int def __init__(self) -> None: self.field = 1 # Wrong: class Some(object): field = 1 def __init__(self) -> None: self.field = 1 .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found shadowed class attribute: {0}' code = 601 previous_codes = {427} @final class StaticMethodViolation(ASTViolation): """ Forbids to use ``@staticmethod`` decorator. Reasoning: Static methods are not required to be inside the class. Because they even do not have access to the current instance. Solution: Use instance methods, ``@classmethod``, or functions instead. .. versionadded:: 0.1.0 .. versionchanged:: 0.11.0 """ error_template = 'Found using `@staticmethod`' code = 602 previous_codes = {433} @final class BadMagicMethodViolation(ASTViolation): """ Forbids to use some magic methods. Reasoning: We forbid to use magic methods related to the forbidden language parts. Likewise, we forbid to use ``del`` keyword, so we forbid to use all magic methods related to it. Solution: Refactor your code to use custom methods instead. It will give more context to your app. See :py:data:`~wemake_python_styleguide.constants.MAGIC_METHODS_BLACKLIST` for the full blacklist of the magic methods. .. versionadded:: 0.1.0 .. versionchanged:: 0.11.0 See also: https://www.youtube.com/watch?v=F6u5rhUQ6dU """ error_template = 'Found using restricted magic method: {0}' code = 603 previous_codes = {434} @final class WrongClassBodyContentViolation(ASTViolation): """ Forbids to use incorrect nodes inside ``class`` definitions. Reasoning: Python allows us to have conditions, context managers, and even infinite loops inside ``class`` definitions. On the other hand, only methods, attributes, and docstrings make sense. So, we discourage using anything except these nodes in class bodies. Solution: If you have complex logic inside your class definition, most likely that you do something wrong. There are different options to refactor this mess. You can try metaclasses, decorators, builders, and other patterns. Example:: # Wrong: class Test(object): for _ in range(10): print('What?!') We also allow some nested classes, check out :class:`NestedClassViolation` for more information. .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 """ error_template = 'Found incorrect node inside `class` body' code = 604 previous_codes = {452} @final class MethodWithoutArgumentsViolation(ASTViolation): """ Forbids to have methods without any arguments. Reasoning: Methods without arguments are allowed to be defined, but almost impossible to use. Furthermore, they don't have an access to ``self``, so can not access the inner state of the object. It might be an intentional design or just a typo. Solution: Move any methods with arguments to raw functions. Or just add an argument if it is actually required. Example:: # Correct: class Test(object): def method(self): ... # Wrong: class Test(object): def method(): ... .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 """ error_template = 'Found method without arguments: {0}' code = 605 previous_codes = {453} @final class WrongBaseClassViolation(ASTViolation): """ Forbids to have anything else than a class as a base class. We only check base classes and not keywords. They can be anything you need. Reasoning: In Python you can specify anything in the base classes slot. In runtime this expression will be evaluated and executed. We need to prevent dirty hacks in this field. Solution: Use only attributes, names, and types to be your base classes. Use ``annotation`` future import in case you use strings in base classes. Example:: # Correct: class Test(module.ObjectName, MixinName, keyword=True): ... class GenericClass(Generic[ValueType]): ... # Wrong: class Test((lambda: object)()): ... .. versionadded:: 0.7.0 .. versionchanged:: 0.7.1 .. versionchanged:: 0.11.0 .. versionchanged:: 0.12.0 """ error_template = 'Found incorrect base class' code = 606 previous_codes = {454} @final class WrongSlotsViolation(ASTViolation): """ Forbids to have incorrect ``__slots__`` definition. Things that this rule checks: - That ``__slots__`` is a tuple, name, attribute, star, or call - That ``__slots__`` do not have duplicates - That ``__slots__`` do not have empty strings or invalid python names Reasoning: ``__slots__`` is a very special attribute. It completely changes your class. So, we need to be careful with it. We should not allow anything rather than tuples to define slots, we also need to check that fields defined in ``__slots__`` are unique. Solution: Use tuples with unique elements to define ``__slots__`` attribute. Use ``snake_case`` to define attributes in ``__slots__``. Example:: # Correct: class Test(object): __slots__ = ('field1', 'field2') class Other(Test): __slots__ = (*Test.__slots__, 'child') # Wrong: class Test(object): __slots__ = ['field1', 'field2', 'field2'] Note, that we do ignore all complex expressions for this field. So, we only check raw literals. .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 .. versionchanged:: 0.12.0 """ error_template = 'Found incorrect `__slots__` syntax' code = 607 previous_codes = {455} @final class WrongSuperCallViolation(ASTViolation): """ Forbids to use ``super()`` with parameters or outside of methods. Reasoning: ``super()`` is a very special function. It implicitly relies on the context where it is used and parameters passed to it. So, we should be very careful with parameters and context. Solution: Use ``super()`` without arguments and only inside methods. Example:: # Correct: super().__init__() # Wrong: super(ClassName, self).__init__() .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 """ error_template = 'Found incorrect `super()` call: {0}' code = 608 previous_codes = {456} @final class DirectMagicAttributeAccessViolation(ASTViolation): """ Forbids to use direct magic attributes and methods. Reasoning: When using direct magic attributes or method it means that you are doing something wrong. Magic methods are not suited to be directly called or accessed. Solution: Use special syntax constructs that will call underlying magic methods. Example:: # Correct: super().__init__() # Wrong: 2..__truediv__(2) d.__delitem__('a') Note, that it is possible to use direct magic attributes with ``self``, ``cls``, and ``super()`` as base names. We allow this because a lot of internal logic relies on these methods. .. versionadded:: 0.8.0 .. versionchanged:: 0.11.0 """ error_template = 'Found direct magic attribute usage: {0}' code = 609 previous_codes = {462} @final class AsyncMagicMethodViolation(ASTViolation): """ Forbids to make some magic methods async. We allow to make ``__anext__``, ``__aenter__``, ``__aexit__`` async. We also allow custom magic methods to be async. See :py:data:`~wemake_python_styleguide.constants.ASYNC_MAGIC_METHODS_BLACKLIST` for the whole list of blacklisted async magic methods. Reasoning: Defining the magic methods as async which are not supposed to be async would not work as expected. Solution: Do not make this magic method async. Example:: # Correct: class Test(object): def __lt__(self, other): ... # Wrong: class Test(object): async def __lt__(self, other): ... See also: https://docs.python.org/3/reference/datamodel.html .. versionadded:: 0.12.0 """ error_template = 'Found forbidden `async` magic method usage: {0}' code = 610 @final class YieldMagicMethodViolation(ASTViolation): """ Forbids to use ``yield`` inside of several magic methods. We allow to make ``__iter__`` a generator. See :py:data:`~wemake_python_styleguide.constants.YIELD_MAGIC_METHODS_BLACKLIST` for the whole list of blacklisted generator magic methods. Reasoning: Python's datamodel is strict. You cannot make generators from random magic methods. This rule enforces it. Solution: Remove ``yield`` from a magic method or rename it to be a custom method. Example:: # Correct: class Example(object): def __init__(self): ... # Wrong: class Example(object): def __init__(self): yield 10 See also: https://docs.python.org/3/reference/datamodel.html .. versionadded:: 0.3.0 .. versionchanged:: 0.11.0 .. versionchanged:: 0.12.0 """ error_template = 'Found forbidden `yield` magic method usage' code = 611 previous_codes = {439, 435} @final class UselessOverwrittenMethodViolation(ASTViolation): """ Forbids to have useless overwritten methods. Reasoning: Overwriting method without any changes does not have any positive impact. Solution: Do not overwrite method in case you do not want to do any changes inside it. Example:: # Correct: class Test(Base): ... # Wrong: class Test(object): def method(self, argument): return super().method(argument) .. versionadded:: 0.12.0 """ error_template = 'Found useless overwritten method: {0}' code = 612 PK!S?$PP2wemake_python_styleguide/violations/refactoring.py# -*- coding: utf-8 -*- """ These checks ensure that you don't have patterns that can be refactored. There are so many ways of doing the same thing in Python. Here we collect know patterns that can be rewritten into much easier or just more pythonic version. .. currentmodule:: wemake_python_styleguide.violations.refactoring Summary ------- .. autosummary:: :nosignatures: UselessLoopElseViolation UselessFinallyViolation SimplifiableIfViolation UselessReturningElseViolation NegatedConditionsViolation NestedTryViolation UselessLambdaViolation UselessLenCompareViolation NotOperatorWithCompareViolation NestedTernaryViolation WrongInCompareTypeViolation UnmergedIsinstanceCallsViolation WrongIsinstanceWithTupleViolation ImplicitElifViolation ImplicitInConditionViolation OpenWithoutContextManagerViolation TypeCompareViolation PointlessStarredViolation ImplicitEnumerateViolation ImplicitSumViolation FalsyConstantCompareViolation WrongIsCompareViolation Refactoring opportunities ------------------------- .. autoclass:: UselessLoopElseViolation .. autoclass:: UselessFinallyViolation .. autoclass:: SimplifiableIfViolation .. autoclass:: UselessReturningElseViolation .. autoclass:: NegatedConditionsViolation .. autoclass:: NestedTryViolation .. autoclass:: UselessLambdaViolation .. autoclass:: UselessLenCompareViolation .. autoclass:: NotOperatorWithCompareViolation .. autoclass:: NestedTernaryViolation .. autoclass:: WrongInCompareTypeViolation .. autoclass:: UnmergedIsinstanceCallsViolation .. autoclass:: WrongIsinstanceWithTupleViolation .. autoclass:: ImplicitElifViolation .. autoclass:: ImplicitInConditionViolation .. autoclass:: OpenWithoutContextManagerViolation .. autoclass:: TypeCompareViolation .. autoclass:: PointlessStarredViolation .. autoclass:: ImplicitEnumerateViolation .. autoclass:: ImplicitSumViolation .. autoclass:: FalsyConstantCompareViolation .. autoclass:: WrongIsCompareViolation """ from typing_extensions import final from wemake_python_styleguide.violations.base import ( ASTViolation, TokenizeViolation, ) @final class UselessLoopElseViolation(ASTViolation): """ Forbids to use ``else`` without ``break`` in a loop. We use the same logic for ``for`` and ``while`` loops. Reasoning: When there's no ``break`` keyword in loop's body it means that ``else`` will always be called. This rule will reduce complexity, improve readability, and protect from possible errors. Solution: Refactor your ``else`` case logic to be inside the loop's body. Or right after it. Example:: # Correct: for letter in 'abc': if letter == 'b': break else: print('"b" is not found') for letter in 'abc': print(letter) print('always called') # Wrong: for letter in 'abc': print(letter) else: print('always called') .. versionadded:: 0.3.0 .. versionchanged:: 0.11.0 """ error_template = 'Found `else` in a loop without `break`' code = 500 previous_codes = {436} @final class UselessFinallyViolation(ASTViolation): """ Forbids to use ``finally`` in ``try`` block without ``except`` block. Reasoning: This rule will reduce complexity and improve readability. Solution: Refactor your ``try`` logic. Replace the ``try-finally`` statement with a ``with`` statement. Example:: # Correct: with open("filename") as f: f.write(...) # Wrong: try: f = open("filename") f.write(...) finally: f.close() .. versionadded:: 0.3.0 .. versionchanged:: 0.11.0 """ error_template = 'Found `finally` in `try` block without `except`' code = 501 previous_codes = {437} @final class SimplifiableIfViolation(ASTViolation): """ Forbids to have simplifiable ``if`` conditions. Reasoning: This complex construction can cause frustration among other developers. It is longer, more verbose, and more complex. Solution: Use ``bool()`` to convert test values to boolean values. Or just leave it as it is in case when your test already returns a boolean value. Use can also use ``not`` keyword to switch boolean values. Example:: # Correct: my_bool = bool(some_call()) other_value = 8 if some_call() else None # Wrong: my_bool = True if some_call() else False We only check ``if`` nodes where ``True`` and ``False`` values are used. We check both ``if`` nodes and ``if`` expressions. .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 """ error_template = 'Found simplifiable `if` condition' code = 502 previous_codes = {451} @final class UselessReturningElseViolation(ASTViolation): """ Forbids to use useless ``else`` cases in returning functions. We check single ``if`` statements that all contain ``return`` or ``raise`` or ``break`` statements with this rule. We do not check ``if`` statements with ``elif`` cases. Reasoning: Using extra ``else`` creates a situation when the whole node could and should be dropped without any changes in logic. So, we prefer to have less code than more code. Solution: Remove useless ``else`` case. Example:: # Correct: def some_function(): if some_call(): return 'yeap' return 'nope' # Wrong: def some_function(): if some_call(): raise ValueError('yeap') else: raise ValueError('nope') .. versionadded:: 0.7.0 .. versionchanged:: 0.11.0 """ error_template = 'Found useless returning `else` statement' code = 503 previous_codes = {457} @final class NegatedConditionsViolation(ASTViolation): """ Forbids to use negated conditions together with ``else`` clause. Reasoning: It easier to read and name regular conditions. Not negated ones. Solution: Move actions from the negated ``if`` condition to the ``else`` condition. Example:: # Correct: if some == 1: ... else: ... if not some: ... # Wrong: if not some: ... else: ... .. versionadded:: 0.8.0 .. versionchanged:: 0.11.0 """ error_template = 'Found negated condition' code = 504 previous_codes = {463} @final class NestedTryViolation(ASTViolation): """ Forbids to use nested ``try`` blocks. Notice, we check all possible slots for ``try`` block: 1. the ``try`` block itself 2. all ``except`` cases 3. ``else`` case 4. and ``finally`` case Reasoning: Nesting ``try`` blocks indicates that something really bad happens to your logic. Why does it require two separate exception handlers? It is a perfect case to refactor your code. Solution: Collapse two exception handlers together. Or create a separate function that will handle this second nested case. Example:: # Wrong: try: try: ... except SomeException: ... except SomeOtherException: ... try: ... except SomeOtherException: try: ... except SomeException: ... .. versionadded:: 0.8.0 .. versionchanged:: 0.11.0 """ error_template = 'Found nested `try` block' code = 505 previous_codes = {464} @final class UselessLambdaViolation(ASTViolation): """ Forbids to define useless proxy ``lambda`` expressions. Reasoning: Sometimes developers tend to overuse ``lambda`` expressions and they wrap code that can be passed as is, without extra wrapping. The code without extra ``lambda`` is easier to read and is more performant. Solution: Remove wrapping ``lambda`` declaration, use just the internal function. Example:: # Correct: numbers = map(int, ['1', '2']) # Wrong: numbers = map(lambda string: int(string), ['1', '2']) .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found useless lambda declaration' code = 506 previous_codes = {467} @final class UselessLenCompareViolation(ASTViolation): """ Forbids to have unpythonic zero-length compare. Note, that we allow to check arbitrary length, like ``len(arr) == 3``. Reasoning: Python's structures like dicts, lists, sets, and tuples all have ``__bool__`` method to checks their length. So, there's no point in wrapping them into ``len(...)`` and checking that it is bigger that ``0`` or less then ``1``, etc. Solution: Remove extra ``len()`` call. Example:: # Correct: if some_array or not other_array or len(third_array) == 1: ... # Wrong: if len(some_array) > 0 or len(other_array) < 1: ... .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found useless `len()` compare' code = 507 previous_codes = {468} @final class NotOperatorWithCompareViolation(ASTViolation): """ Forbids to use ``not`` with compare expressions. Reasoning: This version of ``not`` operator is unreadable. Solution: Refactor the expression without ``not`` operator. Change the compare signs. Example:: # Correct: if x <= 5: ... # Wrong: if not x > 5: ... .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found incorrect `not` with compare usage' code = 508 previous_codes = {470} @final class NestedTernaryViolation(ASTViolation): """ Forbids to nest ternary expressions in some places. Note, that we restrict to nest ternary expressions inside: - ``if`` conditions - boolean and binary operations like ``and`` or ``+`` - unary operators Reasoning: Nesting ternary in random places can lead to very hard debug and testing problems. Solution: Refactor the ternary expression to be either a new variable, or nested ``if`` statement, or a new function. Example:: # Correct: some = x if cond() else y # Wrong: if x if cond() else y: ... .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found incorrectly nested ternary' code = 509 previous_codes = {472} @final class WrongInCompareTypeViolation(ASTViolation): """ Forbids to use ``in`` with static containers except ``set`` nodes. We enforce people to use sets as a static containers. You can also use variables, calls, methods, etc. Dynamic values are not checked. Reasoning: Using static ``list``, ``tuple``, or ``dict`` elements to check that some element is inside the container is a bad practice. Because we need to iterate all over the container to find the element. Sets are the best suit for this task. Moreover, it makes your code consistent. Solution: Use ``set`` elements or comprehensions to check that something is contained in a container. Example:: # Correct: print(needle in {'one', 'two'}) # Wrong: print(needle in ['one', 'two']) .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found `in` used with a non-set container' code = 510 previous_codes = {473} @final class UnmergedIsinstanceCallsViolation(ASTViolation): """ Forbids to multiple ``isinstance`` calls with the same variable. Reasoning: The best practice is to use ``isinstance`` with tuple as the second argument, instead of multiple conditions joined with ``or``. Solution: Use tuple of types as the second argument. Example:: # Correct: isinstance(some, (int, float)) # Wrong: isinstance(some, int) or isinstance(some, float) See also: https://docs.python.org/3/library/functions.html#isinstance .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = ( 'Found separate `isinstance` calls that can be merged for: {0}' ) code = 511 previous_codes = {474} @final class WrongIsinstanceWithTupleViolation(ASTViolation): """ Forbids to multiple ``isinstance`` calls with tuples of a single item. Reasoning: There's no need to use tuples with single elements. You can use single variables or tuples with multiple elements. Solution: Use tuples with multiple elements or a single varaible. Example:: # Correct: isinstance(some, (int, float)) isisntance(some, int) # Wrong: isinstance(some, (int, )) See: https://docs.python.org/3/library/functions.html#isinstance .. versionadded:: 0.10.0 .. versionchanged:: 0.11.0 """ error_template = 'Found `isinstance` call with a single element tuple' code = 512 previous_codes = {475} @final class ImplicitElifViolation(TokenizeViolation): """ Forbids to have implicit ``elif`` conditions. Reasoning: Nested ``if`` in ``else`` cases are bad for readability because of the nesting level. Solution: Use ``elif`` on the same level. Example:: # Correct: if some: ... elif other: ... # Wrong: if some: ... else: if other: ... .. versionadded:: 0.12.0 """ error_template = 'Found implicit `elif` condition' code = 513 @final class ImplicitInConditionViolation(ASTViolation): """ Forbids to use multiple equality compare with the same variable name. Reasoning: Using double+ equality compare with ``or`` or double+ non-equality compare with ``and`` indicates that you have implicit ``in`` or ``not in`` condition. It is just hidden from you. Solution: Refactor compares to use ``in`` or ``not in`` clauses. Example:: # Correct: print(some in {'first', 'second'}) print(some not in {'first', 'second'}) # Wrong: print(some == 'first' or some == 'second') print(some != 'first' and some != 'second') .. versionadded:: 0.10.0 .. versionchanged:: 0.12.0 """ code = 514 error_template = 'Found implicit `in` condition' previous_codes = {336} @final class OpenWithoutContextManagerViolation(ASTViolation): """ Forbids to use ``open()`` with a context manager. Reasoning: When you ``open()`` something, you need to close it. When using a context manager - it is automatically done for you. When not using it - you might find yourself in a situation when file is not closed and is not accessable anymore. Solution: Refactor ``open()`` call to use ``with``. Example:: # Correct: with open(filename) as file_obj: ... # Wrong: file_obj = open(filename) .. versionadded:: 0.12.0 """ code = 515 error_template = 'Found `open()` used without a context manager' @final class TypeCompareViolation(ASTViolation): """ Forbids to compare types with ``type()`` function. Reasoning: When you compare types with ``type()`` function call it means that you break polymorphism and dissallow child classes of a node to work here. That's incorrect. Solution: Use ``isinstance`` to compare types. Example:: # Correct: print(something, type(something)) # Wrong: if type(something) == int: ... .. versionadded:: 0.12.0 """ code = 516 error_template = 'Found `type()` used to compare types' @final class PointlessStarredViolation(ASTViolation): """ Forbids to have useless starred expressions. Reasoning: Using starred expression with constants is useless. This piece of code can be rewritten to be flat. Eg.: ``print(*[1, 2, 3])`` is ``print(1, 2, 3)``. Solution: Refactor your code not to use starred expressions with ``list``, ``dict``, ``tuple``, and ``set`` constants. Use regular argument passing instead. Example:: # Correct: my_list = [1, 2, 3, *other_iterable] # Wrong: print(*[1, 2, 3], **{{}}) .. versionadded:: 0.12.0 """ code = 517 error_template = 'Found pointless starred expression' @final class ImplicitEnumerateViolation(ASTViolation): """ Forbids to have implicit ``enumerate()`` calls. Reasoning: Using ``range(len(...))`` is not pythonic. Python uses collection iterators, not index-based loops. Solution: Use ``enumerate(...)`` instead of ``range(len(...))``. Example:: # Correct: for index, person in enumerate(people): ... # Wrong: for index in range(len(people)): ... See also: https://docs.python.org/3/library/functions.html#enumerate .. versionadded:: 0.12.0 """ code = 518 error_template = 'Found implicit `enumerate()` call' @final class ImplicitSumViolation(ASTViolation): """ Forbids to have implicit ``sum()`` calls. When summing types different from numbers, you might need to provide the second argument to the ``sum`` function: ``sum([[1], [2], [3]], [])`` You might also use ``str.join`` to join iterable of strings. Reasoning: Using ``for`` loops with ``+=`` assign inside indicates that you iteratively sum things inside your collection. That's what ``sum()`` builtin function does. Solution: Use ``sum(...)`` instead of a loop with ``+=`` operation. Example:: # Correct: sum_result = sum(get_elements()) # Wrong: sum_result = 0 for to_sum in get_elements(): sum_result += to_sum See also: https://docs.python.org/3/library/functions.html#sum https://docs.python.org/3/library/stdtypes.html#str.join .. versionadded:: 0.12.0 """ code = 519 error_template = 'Found implicit `sum()` call' @final class FalsyConstantCompareViolation(ASTViolation): """ Forbids to compare with explicit falsy constants. We allow to compare with falsy numbers, strings, booleans, ``None``. We disallow complex constants like tuple, dicts, and lists. Reasoning: When comparing ``something`` with explicit falsy constants what we really mean is ``not something``. Solution: Use ``not`` with your variable. Fix your data types. Example:: # Correct: if not my_check: ... if some_other is None: ... if some_num == 0: ... # Wrong: if my_check == []: ... .. versionadded:: 0.12.0 """ code = 520 error_template = 'Found compare with falsy constant' @final class WrongIsCompareViolation(ASTViolation): """ Forbids to compare values with constants using ``is`` or ``is not``. However, we allow to compare with ``None`` and booleans. Reasoning: ``is`` compares might not do what you want them to do. Firstly, they check for the same object, not equality. Secondly, they behave unexpectedly even with the simple values like ``257``. Solution: Use ``==`` to compare with constants. Example:: # Correct: if my_check == [1, 2, 3]: ... # Wrong: if my_check is [1, 2, 3]: ... See also: https://stackoverflow.com/a/33130014/4842742 .. versionadded:: 0.12.0 """ code = 521 error_template = 'Found wrong `is` compare' PK!uh-wemake_python_styleguide/visitors/__init__.py# -*- coding: utf-8 -*- PK!uh1wemake_python_styleguide/visitors/ast/__init__.py# -*- coding: utf-8 -*- PK!U4wemake_python_styleguide/visitors/ast/annotations.py# -*- coding: utf-8 -*- import ast from typing_extensions import final from wemake_python_styleguide.types import AnyFunctionDef from wemake_python_styleguide.violations.consistency import ( MultilineFunctionAnnotationViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias @final @alias('visit_any_function', ( 'visit_FunctionDef', 'visit_AsyncFunctionDef', )) class WrongAnnotationVisitor(BaseNodeVisitor): """Ensures that annotations are used correctly.""" def visit_any_function(self, node: AnyFunctionDef) -> None: """ Checks return type annotations. Raises: MultilineFunctionAnnotationViolation """ self._check_return_annotation(node) self.generic_visit(node) def visit_arg(self, node: ast.arg) -> None: """ Checks arguments annotations. Raises: MultilineFunctionAnnotationViolation """ self._check_arg_annotation(node) self.generic_visit(node) def _check_arg_annotation(self, node: ast.arg) -> None: for sub_node in ast.walk(node): lineno = getattr(sub_node, 'lineno', None) if lineno and lineno != node.lineno: self.add_violation(MultilineFunctionAnnotationViolation(node)) return def _check_return_annotation(self, node: AnyFunctionDef) -> None: if not node.returns: return for sub_node in ast.walk(node.returns): lineno = getattr(sub_node, 'lineno', None) if lineno and lineno != node.returns.lineno: self.add_violation(MultilineFunctionAnnotationViolation(node)) return PK! vK3wemake_python_styleguide/visitors/ast/attributes.py# -*- coding: utf-8 -*- import ast from typing import ClassVar, FrozenSet from typing_extensions import final from wemake_python_styleguide.logic.naming import access from wemake_python_styleguide.violations.best_practices import ( ProtectedAttributeViolation, ) from wemake_python_styleguide.violations.oop import ( DirectMagicAttributeAccessViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor @final class WrongAttributeVisitor(BaseNodeVisitor): """Ensures that attributes are used correctly.""" _allowed_to_use_protected: ClassVar[FrozenSet[str]] = frozenset(( 'self', 'cls', 'mcs', )) _allowed_magic_attributes: ClassVar[FrozenSet[str]] = frozenset(( '__class__', '__name__', '__qualname__', '__doc__', )) def visit_Attribute(self, node: ast.Attribute) -> None: """ Checks the `Attribute` node. Raises: ProtectedAttributeViolation DirectMagicAttributeAccessViolation """ self._check_protected_attribute(node) self._check_magic_attribute(node) self.generic_visit(node) def _is_super_called(self, node: ast.Call) -> bool: if isinstance(node.func, ast.Name): if node.func.id == 'super': return True return False def _ensure_attribute_type(self, node: ast.Attribute, exception) -> None: if isinstance(node.value, ast.Name): if node.value.id in self._allowed_to_use_protected: return if isinstance(node.value, ast.Call): if self._is_super_called(node.value): return self.add_violation(exception(node, text=node.attr)) def _check_protected_attribute(self, node: ast.Attribute) -> None: if access.is_protected(node.attr): self._ensure_attribute_type(node, ProtectedAttributeViolation) def _check_magic_attribute(self, node: ast.Attribute) -> None: if access.is_magic(node.attr): if node.attr not in self._allowed_magic_attributes: self._ensure_attribute_type( node, DirectMagicAttributeAccessViolation, ) PK!Ru++/wemake_python_styleguide/visitors/ast/blocks.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from typing import ClassVar, DefaultDict, List, Set, Union, cast from typing_extensions import final from wemake_python_styleguide.compat.aliases import ForNodes, WithNodes from wemake_python_styleguide.logic.naming.name_nodes import ( flat_variable_names, ) from wemake_python_styleguide.logic.nodes import get_context, get_parent from wemake_python_styleguide.logic.scopes import ( BlockScope, OuterScope, extract_names, ) from wemake_python_styleguide.logic.walk import is_contained_by from wemake_python_styleguide.types import ( AnyAssign, AnyFor, AnyFunctionDef, AnyImport, AnyNodes, AnyWith, ) from wemake_python_styleguide.violations.best_practices import ( BlockAndLocalOverlapViolation, ControlVarUsedAfterBlockViolation, OuterScopeShadowingViolation, ) from wemake_python_styleguide.visitors import base, decorators #: That's how we represent contexts for control variables. _BlockVariables = DefaultDict[ ast.AST, DefaultDict[str, List[ast.AST]], ] @final @decorators.alias('visit_named_nodes', ( 'visit_FunctionDef', 'visit_AsyncFunctionDef', 'visit_ClassDef', 'visit_ExceptHandler', )) @decorators.alias('visit_any_for', ( 'visit_For', 'visit_AsyncFor', )) @decorators.alias('visit_locals', ( 'visit_Assign', 'visit_AnnAssign', 'visit_arg', )) class BlockVariableVisitor(base.BaseNodeVisitor): """ This visitor is used to detect variables that are reused for blocks. Check out this example: .. code:: exc = 7 try: ... except Exception as exc: # reusing existing variable ... Please, do not modify. This is fragile and complex. """ # Blocks: def visit_named_nodes(self, node: AnyFunctionDef) -> None: """ Visits block nodes that have ``.name`` property. Raises: BlockAndLocalOverlapViolation """ names = {node.name} if node.name else set() self._scope(node, names, is_local=False) self._outer_scope(node, names) self.generic_visit(node) def visit_any_for(self, node: AnyFor) -> None: """ Collects block nodes from loop definitions. Raises: BlockAndLocalOverlapViolation """ names = extract_names(node.target) self._scope(node, names, is_local=False) self._outer_scope(node, names) self.generic_visit(node) def visit_alias(self, node: ast.alias) -> None: """ Visits aliases from ``import`` and ``from ... import`` block nodes. Raises: BlockAndLocalOverlapViolation """ parent = cast(AnyImport, get_parent(node)) import_name = {node.asname} if node.asname else {node.name} self._scope(parent, import_name, is_local=False) self._outer_scope(parent, import_name) self.generic_visit(node) def visit_withitem(self, node: ast.withitem) -> None: """ Visits ``with`` and ``async with`` declarations. Raises: BlockAndLocalOverlapViolation """ if node.optional_vars: parent = cast(AnyWith, get_parent(node)) names = extract_names(node.optional_vars) self._scope(parent, names, is_local=False) self._outer_scope(parent, names) self.generic_visit(node) # Locals: def visit_locals(self, node: Union[AnyAssign, ast.arg]) -> None: """ Visits local variable definitions and function arguments. Raises: BlockAndLocalOverlapViolation """ if isinstance(node, ast.arg): names = {node.arg} else: names = set(flat_variable_names([node])) self._scope(node, names, is_local=True) self._outer_scope(node, names) self.generic_visit(node) # Utils: def _scope( self, node: ast.AST, names: Set[str], *, is_local: bool, ) -> None: scope = BlockScope(node) shadow = scope.shadowing(names, is_local=is_local) if shadow: self.add_violation( BlockAndLocalOverlapViolation(node, text=', '.join(shadow)), ) scope.add_to_scope(names, is_local=is_local) def _outer_scope(self, node: ast.AST, names: Set[str]) -> None: scope = OuterScope(node) shadow = scope.shadowing(names) if shadow: self.add_violation( OuterScopeShadowingViolation(node, text=', '.join(shadow)), ) scope.add_to_scope(names) @final @decorators.alias('visit_any_for', ( 'visit_For', 'visit_AsyncFor', )) class AfterBlockVariablesVisitor(base.BaseNodeVisitor): """Visitor that ensures that block variables are not used after block.""" _block_nodes: ClassVar[AnyNodes] = ( ast.ExceptHandler, *ForNodes, *WithNodes, ) def __init__(self, *args, **kwargs) -> None: """We need to store complex data about variable usages.""" super().__init__(*args, **kwargs) self._block_variables: _BlockVariables = defaultdict( lambda: defaultdict(list), ) def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None: """Visit exception names definition.""" if node.name: self._add_to_scope(node, {node.name}) self.generic_visit(node) def visit_any_for(self, node: AnyFor) -> None: """Visit loops.""" self._add_to_scope(node, extract_names(node.target)) self.generic_visit(node) def visit_withitem(self, node: ast.withitem) -> None: """Visits ``with`` and ``async with`` declarations.""" if node.optional_vars: self._add_to_scope( cast(AnyWith, get_parent(node)), extract_names(node.optional_vars), ) self.generic_visit(node) # Variable usages: def visit_Name(self, node: ast.Name) -> None: """ Check variable usages. Raises: ControlVarUsedAfterBlockViolation """ if isinstance(node.ctx, ast.Load): self._check_variable_usage(node) self.generic_visit(node) # Utils: def _add_to_scope(self, node: ast.AST, names: Set[str]) -> None: context = cast(ast.AST, get_context(node)) for var_name in names: self._block_variables[context][var_name].append(node) def _check_variable_usage(self, node: ast.Name) -> None: context = cast(ast.AST, get_context(node)) blocks = self._block_variables[context][node.id] if all(is_contained_by(node, block) for block in blocks): return self.add_violation( ControlVarUsedAfterBlockViolation(node, text=node.id), ) PK! X#X#1wemake_python_styleguide/visitors/ast/builtins.py# -*- coding: utf-8 -*- import ast from collections import Counter, Hashable, defaultdict from contextlib import suppress from typing import ( ClassVar, DefaultDict, Iterable, List, Optional, Sequence, Union, ) from typing_extensions import final from wemake_python_styleguide import constants from wemake_python_styleguide.compat.aliases import FunctionNodes from wemake_python_styleguide.logic import safe_eval, source from wemake_python_styleguide.logic.naming.name_nodes import extract_name from wemake_python_styleguide.logic.operators import ( get_parent_ignoring_unary, unwrap_starred_node, unwrap_unary_node, ) from wemake_python_styleguide.types import AnyFor, AnyNodes, AnyWith from wemake_python_styleguide.violations import consistency from wemake_python_styleguide.violations.best_practices import ( MagicNumberViolation, MultipleAssignmentsViolation, NonUniqueItemsInHashViolation, UnhashableTypeInHashViolation, WrongUnpackingViolation, ) from wemake_python_styleguide.visitors import base, decorators @final class WrongStringVisitor(base.BaseNodeVisitor): """Restricts several string usages.""" def visit_JoinedStr(self, node: ast.JoinedStr) -> None: """ Restricts to use ``f`` strings. Raises: FormattedStringViolation """ self.add_violation(consistency.FormattedStringViolation(node)) self.generic_visit(node) @final class MagicNumberVisitor(base.BaseNodeVisitor): """Checks magic numbers used in the code.""" _allowed_parents: ClassVar[AnyNodes] = ( ast.Assign, ast.AnnAssign, # Constructor usages: *FunctionNodes, ast.arguments, # Primitives: ast.List, ast.Dict, ast.Set, ast.Tuple, ) def visit_Num(self, node: ast.Num) -> None: """ Checks numbers not to be magic constants inside the code. Raises: MagicNumberViolation """ self._check_is_magic(node) self.generic_visit(node) def _check_is_magic(self, node: ast.Num) -> None: parent = get_parent_ignoring_unary(node) if isinstance(parent, self._allowed_parents): return if node.n in constants.MAGIC_NUMBERS_WHITELIST: return if isinstance(node.n, int) and node.n <= constants.NON_MAGIC_MODULO: return self.add_violation(MagicNumberViolation(node, text=str(node.n))) @final @decorators.alias('visit_any_for', ( 'visit_For', 'visit_AsyncFor', )) @decorators.alias('visit_any_with', ( 'visit_With', 'visit_AsyncWith', )) class WrongAssignmentVisitor(base.BaseNodeVisitor): """Visits all assign nodes.""" def visit_any_with(self, node: AnyWith) -> None: """ Checks assignments inside context managers to be correct. Raises: WrongUnpackingViolation """ for withitem in node.items: if isinstance(withitem.optional_vars, ast.Tuple): self._check_unpacking_targets( node, withitem.optional_vars.elts, ) self.generic_visit(node) def visit_comprehension(self, node: ast.comprehension) -> None: """ Checks comprehensions for the correct assignments. Raises: WrongUnpackingViolation """ if isinstance(node.target, ast.Tuple): self._check_unpacking_targets(node.target, node.target.elts) self.generic_visit(node) def visit_any_for(self, node: AnyFor) -> None: """ Checks assignments inside ``for`` loops to be correct. Raises: WrongUnpackingViolation """ if isinstance(node.target, ast.Tuple): self._check_unpacking_targets(node, node.target.elts) self.generic_visit(node) def visit_Assign(self, node: ast.Assign) -> None: """ Checks assignments to be correct. We do not check ``AnnAssign`` here, because it does not have problems that we check. Raises: MultipleAssignmentsViolation WrongUnpackingViolation """ self._check_assign_targets(node) if isinstance(node.targets[0], ast.Tuple): self._check_unpacking_targets(node, node.targets[0].elts) self.generic_visit(node) def _check_assign_targets(self, node: ast.Assign) -> None: if len(node.targets) > 1: self.add_violation(MultipleAssignmentsViolation(node)) def _check_unpacking_targets( self, node: ast.AST, targets: Iterable[ast.AST], ) -> None: for target in targets: target_name = extract_name(target) if target_name is None: # it means, that non name node was used self.add_violation(WrongUnpackingViolation(node)) @final class WrongCollectionVisitor(base.BaseNodeVisitor): """Ensures that collection definitions are correct.""" _elements_in_sets: ClassVar[AnyNodes] = ( ast.Str, ast.Bytes, ast.Num, ast.NameConstant, ast.Name, ) _unhashable_types: ClassVar[AnyNodes] = ( ast.List, ast.ListComp, ast.Set, ast.SetComp, ast.Dict, ast.DictComp, ast.GeneratorExp, ) _elements_to_eval: ClassVar[AnyNodes] = ( ast.Num, ast.Str, ast.Bytes, ast.NameConstant, ast.Tuple, ast.List, ast.Set, ast.Dict, # Since python3.8 `BinOp` only works for complex numbers: # https://github.com/python/cpython/pull/4035/files # https://bugs.python.org/issue31778 ast.BinOp, # Only our custom `eval` function can eval names safely: ast.Name, ) def visit_Set(self, node: ast.Set) -> None: """ Ensures that set literals do not have any duplicate items. Raises: NonUniqueItemsInHashViolation UnhashableTypeInHashViolation """ self._check_set_elements(node, node.elts) self._check_unhashable_elements(node.elts) self.generic_visit(node) def visit_Dict(self, node: ast.Dict) -> None: """ Ensures that dict literals do not have any duplicate keys. Raises: NonUniqueItemsInHashViolation UnhashableTypeInHashViolation """ self._check_set_elements(node, node.keys) self._check_unhashable_elements(node.keys) self.generic_visit(node) def _check_unhashable_elements( self, keys_or_elts: Sequence[ast.AST], ) -> None: for set_item in keys_or_elts: if isinstance(set_item, self._unhashable_types): self.add_violation(UnhashableTypeInHashViolation(set_item)) def _check_set_elements( self, node: Union[ast.Set, ast.Dict], keys_or_elts: Sequence[Optional[ast.AST]], ) -> None: elements: List[str] = [] element_values = [] for set_item in keys_or_elts: if set_item is None: continue # happens for `{**a}` real_item = unwrap_unary_node(set_item) if isinstance(real_item, self._elements_in_sets): # Similar look: node_repr = source.node_to_string(set_item) elements.append(node_repr.strip().strip('(').strip(')')) real_item = unwrap_starred_node(real_item) # Non-constant nodes raise ValueError, # unhashables raise TypeError: with suppress(ValueError, TypeError): # Similar value: element_values.append( safe_eval.literal_eval_with_names( real_item, ) if isinstance( real_item, self._elements_to_eval, ) else set_item, ) self._report_set_elements(node, elements, element_values) def _report_set_elements( self, node: Union[ast.Set, ast.Dict], elements: List[str], element_values, ) -> None: for look_element, look_count in Counter(elements).items(): if look_count > 1: self.add_violation( NonUniqueItemsInHashViolation(node, text=look_element), ) return value_counts: DefaultDict[Hashable, int] = defaultdict(int) for value_element in element_values: real_value = value_element if isinstance( # Lists, sets, and dicts are not hashable: value_element, Hashable, ) else str(value_element) value_counts[real_value] += 1 if value_counts[real_value] > 1: self.add_violation( NonUniqueItemsInHashViolation(node, text=value_element), ) PK!O;440wemake_python_styleguide/visitors/ast/classes.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from typing import ClassVar, DefaultDict, FrozenSet, List, Optional, Tuple from typing_extensions import final from wemake_python_styleguide import constants, types from wemake_python_styleguide.compat.aliases import AssignNodes, FunctionNodes from wemake_python_styleguide.compat.functions import get_assign_targets from wemake_python_styleguide.logic import ( classes, functions, nodes, prop_access, source, strings, walk, ) from wemake_python_styleguide.logic.arguments import function_args, super_args from wemake_python_styleguide.logic.naming import access, name_nodes from wemake_python_styleguide.violations import best_practices as bp from wemake_python_styleguide.violations import consistency, oop from wemake_python_styleguide.visitors import base, decorators @final class WrongClassVisitor(base.BaseNodeVisitor): """ This class is responsible for restricting some ``class`` anti-patterns. Here we check for stylistic issues and design patterns. """ _allowed_body_nodes: ClassVar[types.AnyNodes] = ( *FunctionNodes, ast.ClassDef, # we allow some nested classes *AssignNodes, # fields and annotations ) def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Checking class definitions. Raises: RequiredBaseClassViolation ObjectInBaseClassesListViolation WrongBaseClassViolation WrongClassBodyContentViolation BuiltinSubclassViolation """ self._check_base_classes_count(node) self._check_base_classes(node) self._check_wrong_body_nodes(node) self.generic_visit(node) def _check_base_classes_count(self, node: ast.ClassDef) -> None: if not node.bases: self.add_violation( consistency.RequiredBaseClassViolation(node, text=node.name), ) def _check_base_classes(self, node: ast.ClassDef) -> None: for base_name in node.bases: if not self._is_correct_base_class(base_name): self.add_violation(oop.WrongBaseClassViolation(base_name)) continue id_attr = getattr(base_name, 'id', None) if id_attr == 'BaseException': self.add_violation(bp.BaseExceptionSubclassViolation(node)) elif id_attr == 'object' and len(node.bases) >= 2: self.add_violation( consistency.ObjectInBaseClassesListViolation( node, text=id_attr, ), ) elif classes.is_forbidden_super_class(id_attr): self.add_violation( oop.BuiltinSubclassViolation(node, text=id_attr), ) def _check_wrong_body_nodes(self, node: ast.ClassDef) -> None: for sub_node in node.body: if isinstance(sub_node, self._allowed_body_nodes): continue if strings.is_doc_string(sub_node): continue self.add_violation(oop.WrongClassBodyContentViolation(sub_node)) def _is_correct_base_class(self, base_class: ast.AST) -> bool: if isinstance(base_class, ast.Name): return True elif isinstance(base_class, ast.Attribute): return all( isinstance(sub_node, (ast.Name, ast.Attribute)) for sub_node in prop_access.parts(base_class) ) elif isinstance(base_class, ast.Subscript): parts = list(prop_access.parts(base_class)) subscripts = list(filter( lambda part: isinstance(part, ast.Subscript), parts, )) correct_items = all( isinstance(sub_node, (ast.Name, ast.Attribute, ast.Subscript)) for sub_node in parts ) return len(subscripts) == 1 and correct_items return False @final @decorators.alias('visit_any_function', ( 'visit_FunctionDef', 'visit_AsyncFunctionDef', )) class WrongMethodVisitor(base.BaseNodeVisitor): """Visits functions, but treats them as methods.""" _staticmethod_names: ClassVar[FrozenSet[str]] = frozenset(( 'staticmethod', )) def visit_any_function(self, node: types.AnyFunctionDef) -> None: """ Checking class methods: async and regular. Raises: StaticMethodViolation BadMagicMethodViolation YieldMagicMethodViolation MethodWithoutArgumentsViolation AsyncMagicMethodViolation UselessOverwrittenMethodViolation """ self._check_decorators(node) self._check_bound_methods(node) self._check_method_contents(node) self.generic_visit(node) def _check_decorators(self, node: types.AnyFunctionDef) -> None: for decorator in node.decorator_list: decorator_name = getattr(decorator, 'id', None) if decorator_name in self._staticmethod_names: self.add_violation(oop.StaticMethodViolation(node)) def _check_bound_methods(self, node: types.AnyFunctionDef) -> None: node_context = nodes.get_context(node) if not isinstance(node_context, ast.ClassDef): return if not functions.get_all_arguments(node): self.add_violation( oop.MethodWithoutArgumentsViolation(node, text=node.name), ) if node.name in constants.MAGIC_METHODS_BLACKLIST: self.add_violation( oop.BadMagicMethodViolation(node, text=node.name), ) is_async = isinstance(node, ast.AsyncFunctionDef) if is_async and access.is_magic(node.name): if node.name in constants.ASYNC_MAGIC_METHODS_BLACKLIST: self.add_violation( oop.AsyncMagicMethodViolation(node, text=node.name), ) self._check_useless_overwritten_methods( node, class_name=node_context.name, ) def _check_method_contents(self, node: types.AnyFunctionDef) -> None: if node.name in constants.YIELD_MAGIC_METHODS_BLACKLIST: if walk.is_contained(node, (ast.Yield, ast.YieldFrom)): self.add_violation(oop.YieldMagicMethodViolation(node)) def _get_call_stmt_of_useless_method( self, node: types.AnyFunctionDef, ) -> Optional[ast.Call]: # consider next body as possible candidate of useless method: # 1) Optional[docstring] # 2) return statement with call statements_number = len(node.body) if statements_number > 2 or statements_number == 0: return None if statements_number == 2: if not strings.is_doc_string(node.body[0]): return None return_stmt = node.body[-1] if not isinstance(return_stmt, ast.Return): return None call_stmt = return_stmt.value if not isinstance(call_stmt, ast.Call): return None return call_stmt def _check_useless_overwritten_methods( self, node: types.AnyFunctionDef, class_name: str, ) -> None: if node.decorator_list: # any decorator can change logic # and make this overwrite useful return call_stmt = self._get_call_stmt_of_useless_method(node) if call_stmt is None or not isinstance(call_stmt.func, ast.Attribute): return attribute = call_stmt.func defined_method_name = node.name if defined_method_name != attribute.attr: return if not super_args.is_ordinary_super_call(attribute.value, class_name): return if not function_args.is_call_matched_by_arguments(node, call_stmt): return self.add_violation( oop.UselessOverwrittenMethodViolation( node, text=defined_method_name, ), ) @final @decorators.alias('visit_any_assign', ( 'visit_Assign', 'visit_AnnAssign', )) class WrongSlotsVisitor(base.BaseNodeVisitor): """Visits class attributes.""" _whitelisted_slots_nodes: ClassVar[types.AnyNodes] = ( ast.Tuple, ast.Attribute, ast.Subscript, ast.Name, ast.Call, ) def visit_any_assign(self, node: types.AnyAssign) -> None: """ Checks all assigns that have correct context. Raises: WrongSlotsViolation """ self._check_slots(node) self.generic_visit(node) def _contains_slots_assign(self, node: types.AnyAssign) -> bool: for target in get_assign_targets(node): if isinstance(target, ast.Name) and target.id == '__slots__': return True return False def _count_slots_items( self, node: types.AnyAssign, elements: ast.Tuple, ) -> None: fields: DefaultDict[str, List[ast.AST]] = defaultdict(list) for tuple_item in elements.elts: slot_name = self._slot_item_name(tuple_item) if not slot_name: self.add_violation(oop.WrongSlotsViolation(tuple_item)) return fields[slot_name].append(tuple_item) for slots in fields.values(): if not self._are_correct_slots(slots) or len(slots) > 1: self.add_violation(oop.WrongSlotsViolation(node)) return def _check_slots(self, node: types.AnyAssign) -> None: if not isinstance(nodes.get_context(node), ast.ClassDef): return if not self._contains_slots_assign(node): return if not isinstance(node.value, self._whitelisted_slots_nodes): self.add_violation(oop.WrongSlotsViolation(node)) return if isinstance(node.value, ast.Tuple): self._count_slots_items(node, node.value) def _slot_item_name(self, node: ast.AST) -> Optional[str]: if isinstance(node, ast.Str): return node.s if isinstance(node, ast.Starred): return source.node_to_string(node) return None def _are_correct_slots(self, slots: List[ast.AST]) -> bool: return all( slot.s.isidentifier() for slot in slots if isinstance(slot, ast.Str) ) @final class ClassAttributeVisitor(base.BaseNodeVisitor): """Finds incorrect class attributes.""" def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Checks that class attributes are correct. Raises: ShadowedClassAttributeViolation """ self._check_attributes_shadowing(node) self.generic_visit(node) def _get_attributes( self, node: ast.ClassDef, ) -> Tuple[List[types.AnyAssign], List[ast.Attribute]]: class_attributes = [] instance_attributes = [] for child in ast.walk(node): if isinstance(child, ast.Attribute): if isinstance(child.ctx, ast.Store): instance_attributes.append(child) if isinstance(child, AssignNodes): if nodes.get_context(child) == node and child.value: class_attributes.append(child) return class_attributes, instance_attributes def _check_attributes_shadowing(self, node: ast.ClassDef) -> None: class_attributes, instance_attributes = self._get_attributes(node) class_attribute_names = set( name_nodes.flat_variable_names(class_attributes), ) for instance_attr in instance_attributes: if instance_attr.attr in class_attribute_names: self.add_violation( oop.ShadowedClassAttributeViolation( instance_attr, text=instance_attr.attr, ), ) @final class ClassMethodOrderVisitor(base.BaseNodeVisitor): """Checks that all methods inside the class are ordered correctly.""" def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Ensures that class has correct method order. Raises: WrongMethodOrderViolation """ self._check_method_order(node) self.generic_visit(node) def _check_method_order(self, node: ast.ClassDef) -> None: method_nodes: List[str] = [] for subnode in ast.walk(node): if isinstance(subnode, FunctionNodes): if nodes.get_context(subnode) == node: method_nodes.append(subnode.name) ideal = sorted(method_nodes, key=self._ideal_order, reverse=True) for existing_order, ideal_order in zip(method_nodes, ideal): if existing_order != ideal_order: self.add_violation(consistency.WrongMethodOrderViolation(node)) return def _ideal_order(self, first: str) -> int: if first == '__new__': return 4 # highest priority if first == '__init__': return 3 if access.is_protected(first): return 1 if access.is_private(first): return 0 # lowest priority return 2 # public and magic methods PK!ot|* 2 21wemake_python_styleguide/visitors/ast/compares.py# -*- coding: utf-8 -*- import ast from typing import ClassVar, List, Optional, Sequence from typing_extensions import final from wemake_python_styleguide.compat.aliases import AssignNodes from wemake_python_styleguide.compat.functions import get_assign_targets from wemake_python_styleguide.logic import ( compares, functions, ifs, nodes, operators, source, ) from wemake_python_styleguide.logic.naming.name_nodes import is_same_variable from wemake_python_styleguide.types import AnyIf, AnyNodes from wemake_python_styleguide.violations.best_practices import ( HeterogenousCompareViolation, ) from wemake_python_styleguide.violations.consistency import ( CompareOrderViolation, ConstantCompareViolation, ConstantConditionViolation, MultipleInCompareViolation, ReversedComplexCompareViolation, UselessCompareViolation, ) from wemake_python_styleguide.violations.refactoring import ( FalsyConstantCompareViolation, NestedTernaryViolation, NotOperatorWithCompareViolation, SimplifiableIfViolation, UselessLenCompareViolation, WrongInCompareTypeViolation, WrongIsCompareViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias def _is_correct_len(sign: ast.cmpop, comparator: ast.AST) -> bool: """This is a helper function to tell what calls to ``len()`` are valid.""" if isinstance(operators.unwrap_unary_node(comparator), ast.Num): numeric_value = ast.literal_eval(comparator) if numeric_value == 0: return False if numeric_value == 1: return not isinstance(sign, (ast.GtE, ast.Lt)) return True @final class CompareSanityVisitor(BaseNodeVisitor): """Restricts the incorrect compares.""" _wrong_in_comparators: ClassVar[AnyNodes] = ( ast.List, ast.ListComp, ast.Dict, ast.DictComp, ast.Tuple, ast.GeneratorExp, ) def visit_Compare(self, node: ast.Compare) -> None: """ Ensures that compares are written correctly. Raises: ConstantCompareViolation MultipleInCompareViolation UselessCompareViolation UselessLenCompareViolation HeterogenousCompareViolation ReversedComplexCompareViolation WrongInCompareTypeViolation """ self._check_literal_compare(node) self._check_useless_compare(node) self._check_in_compare(node) self._check_unpythonic_compare(node) self._check_heterogenous_operators(node) self._check_reversed_complex_compare(node) self.generic_visit(node) def _check_literal_compare(self, node: ast.Compare) -> None: last_was_literal = nodes.is_literal(node.left) for comparator in node.comparators: next_is_literal = nodes.is_literal(comparator) if last_was_literal and next_is_literal: self.add_violation(ConstantCompareViolation(node)) break last_was_literal = next_is_literal def _check_useless_compare(self, node: ast.Compare) -> None: last_variable = node.left for next_variable in node.comparators: if is_same_variable(last_variable, next_variable): self.add_violation(UselessCompareViolation(node)) break last_variable = next_variable def _check_in_compare(self, node: ast.Compare) -> None: in_nodes = (ast.In, ast.NotIn) count = sum(1 for op in node.ops if isinstance(op, in_nodes)) if count > 1: self.add_violation(MultipleInCompareViolation(node)) for op, comp in zip(node.ops, node.comparators): if not isinstance(op, in_nodes): continue if not isinstance(comp, self._wrong_in_comparators): continue self.add_violation(WrongInCompareTypeViolation(comp)) def _check_unpythonic_compare(self, node: ast.Compare) -> None: all_nodes = [node.left, *node.comparators] for index, compare in enumerate(all_nodes): if not isinstance(compare, ast.Call): continue if functions.given_function_called(compare, {'len'}): ps = index - len(all_nodes) + 1 if not _is_correct_len(node.ops[ps], node.comparators[ps]): self.add_violation(UselessLenCompareViolation(node)) def _check_heterogenous_operators(self, node: ast.Compare) -> None: if len(node.ops) == 1: return prototype = compares.get_similar_operators(node.ops[0]) for op in node.ops: if not isinstance(op, prototype): self.add_violation(HeterogenousCompareViolation(node)) break def _check_reversed_complex_compare(self, node: ast.Compare) -> None: if len(node.ops) != 2: return is_less = all( isinstance(op, (ast.Gt, ast.GtE)) for op in node.ops ) if not is_less: return self.add_violation(ReversedComplexCompareViolation(node)) @final class WrongConstantCompareVisitor(BaseNodeVisitor): """Restricts incorrect compares with constants.""" _forbidden_for_is: ClassVar[AnyNodes] = ( ast.List, ast.ListComp, ast.Dict, ast.DictComp, ast.Tuple, ast.GeneratorExp, ast.Set, ast.SetComp, # We allow `ast.NameConstant` ast.Num, ast.Bytes, ast.Str, ) def visit_Compare(self, node: ast.Compare) -> None: """ Visits compare with constants. Raises: FalsyConstantCompareViolation """ self._check_constant(node.ops[0], node.left) self._check_is_constant_comprare(node.ops[0], node.left) for op, comparator in zip(node.ops, node.comparators): self._check_constant(op, comparator) self._check_is_constant_comprare(op, comparator) self.generic_visit(node) def _check_constant(self, op: ast.cmpop, comparator: ast.expr) -> None: if not isinstance(op, (ast.Eq, ast.NotEq, ast.Is, ast.IsNot)): return if not isinstance(comparator, (ast.List, ast.Dict, ast.Tuple)): return length = len(comparator.keys) if isinstance( comparator, ast.Dict, ) else len(comparator.elts) if not length: self.add_violation(FalsyConstantCompareViolation(comparator)) def _check_is_constant_comprare( self, op: ast.cmpop, comparator: ast.expr, ) -> None: if not isinstance(op, (ast.Is, ast.IsNot)): return unwrapped = operators.unwrap_unary_node(comparator) if isinstance(unwrapped, self._forbidden_for_is): self.add_violation(WrongIsCompareViolation(comparator)) @final class WrongComparisionOrderVisitor(BaseNodeVisitor): """Restricts comparision where argument doesn't come first.""" _allowed_left_nodes: ClassVar[AnyNodes] = ( ast.Name, ast.Call, ast.Attribute, ast.Subscript, ast.Await, ) _special_cases: ClassVar[AnyNodes] = ( ast.In, ast.NotIn, ) def visit_Compare(self, node: ast.Compare) -> None: """ Forbids comparision where argument doesn't come first. Raises: CompareOrderViolation """ self._check_ordering(node) self.generic_visit(node) def _is_special_case(self, node: ast.Compare) -> bool: """ Operators ``in`` and ``not in`` are special cases. Why? Because it is perfectly fine to use something like: .. code:: python if 'key' in some_dict: ... This should not be an issue. When there are multiple special operators it is still a separate issue. """ return isinstance(node.ops[0], self._special_cases) def _is_left_node_valid(self, left: ast.AST) -> bool: if isinstance(left, self._allowed_left_nodes): return True if isinstance(left, ast.BinOp): left_node = self._is_left_node_valid(left.left) right_node = self._is_left_node_valid(left.right) return left_node or right_node return False def _has_wrong_nodes_on_the_right( self, comparators: Sequence[ast.AST], ) -> bool: for right in comparators: if isinstance(right, self._allowed_left_nodes): return True if isinstance(right, ast.BinOp): return self._has_wrong_nodes_on_the_right([ right.left, right.right, ]) return False def _check_ordering(self, node: ast.Compare) -> None: if self._is_left_node_valid(node.left): return if self._is_special_case(node): return if len(node.comparators) > 1: return if not self._has_wrong_nodes_on_the_right(node.comparators): return self.add_violation(CompareOrderViolation(node)) @final @alias('visit_any_if', ( 'visit_If', 'visit_IfExp', )) class WrongConditionalVisitor(BaseNodeVisitor): """Finds wrong conditional arguments.""" _forbidden_nodes: ClassVar[AnyNodes] = ( # Constants: ast.Num, ast.Str, ast.Bytes, ast.NameConstant, # Collections: ast.List, ast.Set, ast.Dict, ast.Tuple, ) _forbidden_expression_parents: ClassVar[AnyNodes] = ( ast.If, ast.BoolOp, ast.BinOp, ast.UnaryOp, ast.Compare, ) def visit_any_if(self, node: AnyIf) -> None: """ Ensures that ``if`` nodes are using valid conditionals. Raises: ConstantConditionViolation SimplifiableIfViolation NestedTernaryViolation """ if isinstance(node, ast.If): self._check_simplifiable_if(node) else: self._check_simplifiable_ifexpr(node) self._check_nested_ifexpr(node) self._check_constant_condition(node) self.generic_visit(node) def _is_simplifiable_assign( self, node_body: List[ast.stmt], ) -> Optional[str]: wrong_length = len(node_body) != 1 if wrong_length or not isinstance(node_body[0], AssignNodes): return None if not isinstance(node_body[0].value, ast.NameConstant): return None if node_body[0].value.value is None: return None targets = get_assign_targets(node_body[0]) if len(targets) != 1: return None return source.node_to_string(targets[0]) def _check_constant_condition(self, node: AnyIf) -> None: real_node = operators.unwrap_unary_node(node.test) if isinstance(real_node, self._forbidden_nodes): self.add_violation(ConstantConditionViolation(node)) def _check_simplifiable_if(self, node: ast.If) -> None: if not ifs.has_elif(node) and not ifs.root_if(node): body_var = self._is_simplifiable_assign(node.body) else_var = self._is_simplifiable_assign(node.orelse) if body_var and body_var == else_var: self.add_violation(SimplifiableIfViolation(node)) def _check_simplifiable_ifexpr(self, node: ast.IfExp) -> None: conditions = set() if isinstance(node.body, ast.NameConstant): conditions.add(node.body.value) if isinstance(node.orelse, ast.NameConstant): conditions.add(node.orelse.value) if conditions == {True, False}: self.add_violation(SimplifiableIfViolation(node)) def _check_nested_ifexpr(self, node: ast.IfExp) -> None: parent = nodes.get_parent(node) if isinstance(parent, self._forbidden_expression_parents): self.add_violation(NestedTernaryViolation(node)) @final class UnaryCompareVisitor(BaseNodeVisitor): """Checks that unary compare operators are used correctly.""" def visit_UnaryOp(self, node: ast.UnaryOp) -> None: """ Finds bad `not` usages. Raises: NotOperatorWithCompareViolation """ self._check_incorrect_not(node) self.generic_visit(node) def _check_incorrect_not(self, node: ast.UnaryOp) -> None: if not isinstance(node.op, ast.Not): return if isinstance(node.operand, ast.Compare): self.add_violation(NotOperatorWithCompareViolation(node)) PK!uh<wemake_python_styleguide/visitors/ast/complexity/__init__.py# -*- coding: utf-8 -*- PK!CǑ:wemake_python_styleguide/visitors/ast/complexity/access.py# -*- coding: utf-8 -*- import ast from itertools import takewhile from typing import Set, cast from typing_extensions import final from wemake_python_styleguide.logic.prop_access import parts from wemake_python_styleguide.types import AnyAccess from wemake_python_styleguide.violations.complexity import ( TooDeepAccessViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor @final class AccessVisitor(BaseNodeVisitor): """Counts access number for expressions.""" def __init__(self, *args, **kwargs) -> None: """Keeps visited accesses to not visit them again.""" super().__init__(*args, **kwargs) self._visited_accesses: Set[AnyAccess] = set() def visit_Subscript(self, node: ast.Attribute) -> None: """ Checks subscript access number. Raises: TooDeepAccessViolation """ self._check_consecutive_access_number(node) self.generic_visit(node) def visit_Attribute(self, node: ast.Subscript) -> None: """ Checks attribute access number. Raises: TooDeepAccessViolation """ self._check_consecutive_access_number(node) self.generic_visit(node) def _is_any_access(self, node: ast.AST) -> bool: return isinstance(node, (ast.Attribute, ast.Subscript)) def _check_consecutive_access_number(self, node: AnyAccess) -> None: if node in self._visited_accesses: return consecutive_access = cast(Set[AnyAccess], set(takewhile( self._is_any_access, parts(node), ))) self._visited_accesses.update(consecutive_access) access_number = len(consecutive_access) if access_number > self.options.max_access_level: self.add_violation( TooDeepAccessViolation( node, text=str(access_number), ), ) PK!)Lz;wemake_python_styleguide/visitors/ast/complexity/classes.py# -*- coding: utf-8 -*- import ast from typing_extensions import final from wemake_python_styleguide.logic import walk from wemake_python_styleguide.logic.naming import access from wemake_python_styleguide.violations.complexity import ( TooManyBaseClassesViolation, TooManyPublicAttributesViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor @final class ClassComplexityVisitor(BaseNodeVisitor): """Checks class complexity.""" def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Checking class definitions. Raises: TooManyBaseClassesViolation TooManyPublicAttributesViolation """ self._check_base_classes(node) self._check_public_attributes(node) self.generic_visit(node) def _check_base_classes(self, node: ast.ClassDef) -> None: if len(node.bases) > self.options.max_base_classes: self.add_violation( TooManyBaseClassesViolation(node, text=str(len(node.bases))), ) def _check_public_attributes(self, node: ast.ClassDef) -> None: attributes = walk.get_subnodes_by_type(node, ast.Attribute) self_public_attrs = filter(_is_public_instance_attr_def, attributes) attrs_count = len(list(self_public_attrs)) if attrs_count > self.options.max_attributes: self.add_violation( TooManyPublicAttributesViolation(node, text=str(attrs_count)), ) def _is_public_instance_attr_def(node: ast.Attribute) -> bool: return ( isinstance(node.ctx, ast.Store) and access.is_public(node.attr) and isinstance(node.value, ast.Name) and node.value.id == 'self' ) PK!z*)):wemake_python_styleguide/visitors/ast/complexity/counts.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from typing import ClassVar, DefaultDict, List, Union from typing_extensions import final from wemake_python_styleguide.constants import MAX_LEN_YIELD_TUPLE from wemake_python_styleguide.logic.functions import is_method from wemake_python_styleguide.logic.nodes import get_parent from wemake_python_styleguide.types import AnyFunctionDef, AnyImport from wemake_python_styleguide.violations.complexity import ( TooLongCompareViolation, TooLongTryBodyViolation, TooLongYieldTupleViolation, TooManyConditionsViolation, TooManyDecoratorsViolation, TooManyElifsViolation, TooManyExceptCasesViolation, TooManyImportedNamesViolation, TooManyImportsViolation, TooManyMethodsViolation, TooManyModuleMembersViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias ConditionNodes = Union[ast.If, ast.While, ast.IfExp] ModuleMembers = Union[AnyFunctionDef, ast.ClassDef] @final @alias('visit_module_members', ( 'visit_ClassDef', 'visit_AsyncFunctionDef', 'visit_FunctionDef', )) class ModuleMembersVisitor(BaseNodeVisitor): """Counts classes and functions in a module.""" def __init__(self, *args, **kwargs) -> None: """Creates a counter for tracked metrics.""" super().__init__(*args, **kwargs) self._public_items_count = 0 def visit_module_members(self, node: ModuleMembers) -> None: """ Counts the number of ModuleMembers in a single module. Raises: TooManyModuleMembersViolation """ self._check_decorators_count(node) self._check_members_count(node) self.generic_visit(node) def _check_members_count(self, node: ModuleMembers) -> None: """This method increases the number of module members.""" is_real_method = is_method(getattr(node, 'function_type', None)) if isinstance(get_parent(node), ast.Module) and not is_real_method: self._public_items_count += 1 def _check_decorators_count(self, node: ModuleMembers) -> None: number_of_decorators = len(node.decorator_list) if number_of_decorators > self.options.max_decorators: self.add_violation( TooManyDecoratorsViolation( node, text=str(number_of_decorators), ), ) def _post_visit(self) -> None: if self._public_items_count > self.options.max_module_members: self.add_violation( TooManyModuleMembersViolation( text=str(self._public_items_count), ), ) @final @alias('visit_any_import', ( 'visit_ImportFrom', 'visit_Import', )) class ImportMembersVisitor(BaseNodeVisitor): """Counts imports in a module.""" def __init__(self, *args, **kwargs) -> None: """Creates a counter for tracked metrics.""" super().__init__(*args, **kwargs) self._imports_count = 0 self._imported_names_count = 0 def visit_any_import(self, node: AnyImport) -> None: """ Counts the number of ``import`` and ``from ... import ...``. Raises: TooManyImportsViolation TooManyImportedNamesViolation """ self._imports_count += 1 self._imported_names_count += len(node.names) self.generic_visit(node) def _check_imports_count(self) -> None: if self._imports_count > self.options.max_imports: self.add_violation( TooManyImportsViolation(text=str(self._imports_count)), ) def _check_imported_names_count(self) -> None: if self._imported_names_count > self.options.max_imported_names: violation = TooManyImportedNamesViolation( text=str(self._imported_names_count), ) self.add_violation( violation, ) def _post_visit(self) -> None: self._check_imports_count() self._check_imported_names_count() @final @alias('visit_any_function', ( 'visit_FunctionDef', 'visit_AsyncFunctionDef', )) class MethodMembersVisitor(BaseNodeVisitor): """Counts methods in a single class.""" def __init__(self, *args, **kwargs) -> None: """Creates a counter for tracked methods in different classes.""" super().__init__(*args, **kwargs) self._methods: DefaultDict[ast.ClassDef, int] = defaultdict(int) def visit_any_function(self, node: AnyFunctionDef) -> None: """ Counts the number of methods in a single class. Raises: TooManyMethodsViolation """ self._check_method(node) self.generic_visit(node) def _check_method(self, node: AnyFunctionDef) -> None: parent = get_parent(node) if isinstance(parent, ast.ClassDef): self._methods[parent] += 1 def _post_visit(self) -> None: for node, count in self._methods.items(): if count > self.options.max_methods: self.add_violation( TooManyMethodsViolation(node, text=str(count)), ) @final class ConditionsVisitor(BaseNodeVisitor): """Checks booleans for condition counts.""" #: Maximum number of conditions in a single ``if`` or ``while`` statement. _max_conditions: ClassVar[int] = 4 #: Maximum number of compare nodes in a single expression. _max_compares: ClassVar[int] = 2 def visit_BoolOp(self, node: ast.BoolOp) -> None: """ Counts the number of conditions. Raises: TooManyConditionsViolation """ self._check_conditions(node) self.generic_visit(node) def visit_Compare(self, node: ast.Compare) -> None: """ Counts the number of compare parts. Raises: TooLongCompareViolation """ self._check_compares(node) self.generic_visit(node) def _count_conditions(self, node: ast.BoolOp) -> int: counter = 0 for condition in node.values: if isinstance(condition, ast.BoolOp): counter += self._count_conditions(condition) else: counter += 1 return counter def _check_conditions(self, node: ast.BoolOp) -> None: conditions_count = self._count_conditions(node) if conditions_count > self._max_conditions: self.add_violation( TooManyConditionsViolation(node, text=str(conditions_count)), ) def _check_compares(self, node: ast.Compare) -> None: is_all_equals = all(isinstance(op, ast.Eq) for op in node.ops) is_all_notequals = all(isinstance(op, ast.NotEq) for op in node.ops) can_be_longer = is_all_notequals or is_all_equals treshold = self._max_compares if can_be_longer: treshold += 1 if len(node.ops) > treshold: self.add_violation( TooLongCompareViolation(node, text=str(len(node.ops))), ) @final class ElifVisitor(BaseNodeVisitor): """Checks the number of ``elif`` cases inside conditions.""" #: Maximum number of `elif` blocks in a single `if` condition: _max_elifs: ClassVar[int] = 3 def __init__(self, *args, **kwargs) -> None: """Creates internal ``elif`` counter.""" super().__init__(*args, **kwargs) self._if_children: DefaultDict[ast.If, List[ast.If]] = defaultdict( list, ) def visit_If(self, node: ast.If) -> None: """ Checks condition not to reimplement switch. Raises: TooManyElifsViolation """ self._check_elifs(node) self.generic_visit(node) def _get_root_if_node(self, node: ast.If) -> ast.If: for root, children in self._if_children.items(): if node in children: return root return node def _update_if_child(self, root: ast.If, node: ast.If) -> None: if node is not root: self._if_children[root].append(node) self._if_children[root].extend(node.orelse) # type: ignore def _check_elifs(self, node: ast.If) -> None: has_elif = all( isinstance(if_node, ast.If) for if_node in node.orelse ) if has_elif: root = self._get_root_if_node(node) self._update_if_child(root, node) def _post_visit(self): for root, children in self._if_children.items(): real_children_length = len(set(children)) if real_children_length > self._max_elifs: self.add_violation( TooManyElifsViolation(root, text=str(real_children_length)), ) @final class TryExceptVisitor(BaseNodeVisitor): """Visits all try/except nodes to ensure that they are not too complex.""" #: Maximum number of ``except`` cases in a single ``try`` clause. _max_except_cases: ClassVar[int] = 3 def visit_Try(self, node: ast.Try) -> None: """ Ensures that try/except is correct. Raises: TooManyExceptCasesViolation TooLongTryBodyViolation """ self._check_except_count(node) self._check_try_body_length(node) self.generic_visit(node) def _check_except_count(self, node: ast.Try) -> None: if len(node.handlers) > self._max_except_cases: self.add_violation(TooManyExceptCasesViolation(node)) def _check_try_body_length(self, node: ast.Try) -> None: if len(node.body) > self.options.max_try_body_length: self.add_violation( TooLongTryBodyViolation(node, text=str(len(node.body))), ) @final class YieldTupleVisitor(BaseNodeVisitor): """Finds too long ``tuples`` in ``yield`` expressions.""" def visit_Yield(self, node: ast.Yield) -> None: """ Helper to get all ``yield`` nodes in a function at once. Raises: TooLongYieldTupleViolation """ self._check_yield_values(node) self.generic_visit(node) def _check_yield_values(self, node: ast.Yield) -> None: if isinstance(node.value, ast.Tuple): yield_list = [tup_item for tup_item in node.value.elts] if len(yield_list) > MAX_LEN_YIELD_TUPLE: self.add_violation( TooLongYieldTupleViolation( node, text=str(len(yield_list)), ), ) PK!}}<wemake_python_styleguide/visitors/ast/complexity/function.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from typing import ClassVar, DefaultDict, Dict, List, Tuple, Type, Union from typing_extensions import final from wemake_python_styleguide.logic import functions from wemake_python_styleguide.logic.naming import access from wemake_python_styleguide.logic.nodes import get_parent from wemake_python_styleguide.types import ( AnyFunctionDef, AnyFunctionDefAndLambda, AnyNodes, ) from wemake_python_styleguide.violations.base import BaseViolation from wemake_python_styleguide.violations.complexity import ( TooManyArgumentsViolation, TooManyAssertsViolation, TooManyAwaitsViolation, TooManyExpressionsViolation, TooManyLocalsViolation, TooManyReturnsViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias _FunctionCounter = DefaultDict[AnyFunctionDef, int] _FunctionCounterWithLambda = DefaultDict[AnyFunctionDefAndLambda, int] _AnyFunctionCounter = Union[_FunctionCounter, _FunctionCounterWithLambda] _CheckRule = Tuple[_AnyFunctionCounter, int, Type[BaseViolation]] _NodeTypeHandler = Dict[ Union[type, Tuple[type, ...]], _FunctionCounter, ] @final class _ComplexityCounter(object): """Helper class to encapsulate logic from the visitor.""" _not_contain_locals: ClassVar[AnyNodes] = ( ast.comprehension, ) def __init__(self) -> None: self.awaits: _FunctionCounter = defaultdict(int) # noqa: WPS204 self.arguments: _FunctionCounterWithLambda = defaultdict(int) self.asserts: _FunctionCounter = defaultdict(int) self.returns: _FunctionCounter = defaultdict(int) self.expressions: _FunctionCounter = defaultdict(int) self.variables: DefaultDict[AnyFunctionDef, List[str]] = defaultdict( list, ) def check_arguments_count(self, node: AnyFunctionDefAndLambda) -> None: """Checks the number of the arguments in a function.""" self.arguments[node] = len(functions.get_all_arguments(node)) def check_function_complexity(self, node: AnyFunctionDef) -> None: """ In this function we iterate all the internal body's node. We check different complexity metrics based on these internals. """ for body_item in node.body: for sub_node in ast.walk(body_item): self._check_sub_node(node, sub_node) def _update_variables( self, function: AnyFunctionDef, variable_def: ast.Name, ) -> None: """ Increases the counter of local variables. What is treated as a local variable? Check ``TooManyLocalsViolation`` documentation. """ function_variables = self.variables[function] if variable_def.id not in function_variables: if access.is_unused(variable_def.id): return if isinstance(get_parent(variable_def), self._not_contain_locals): return function_variables.append(variable_def.id) def _check_sub_node( self, node: AnyFunctionDef, sub_node: ast.AST, ) -> None: if isinstance(sub_node, ast.Name): if isinstance(sub_node.ctx, ast.Store): self._update_variables(node, sub_node) error_counters: _NodeTypeHandler = { ast.Return: self.returns, ast.Expr: self.expressions, ast.Await: self.awaits, ast.Assert: self.asserts, } for types, counter in error_counters.items(): if isinstance(sub_node, types): counter[node] += 1 @final @alias('visit_any_function', ( 'visit_AsyncFunctionDef', 'visit_FunctionDef', )) class FunctionComplexityVisitor(BaseNodeVisitor): """ This class checks for complexity inside functions. This includes: 1. Number of arguments 2. Number of `return` statements 3. Number of expressions 4. Number of local variables """ def __init__(self, *args, **kwargs) -> None: """Creates a counter for tracked metrics.""" super().__init__(*args, **kwargs) self._counter = _ComplexityCounter() def visit_any_function(self, node: AnyFunctionDef) -> None: """ Checks function's internal complexity. Raises: TooManyExpressionsViolation TooManyReturnsViolation TooManyLocalsViolation TooManyArgumentsViolation TooManyAwaitsViolation """ self._counter.check_arguments_count(node) self._counter.check_function_complexity(node) self.generic_visit(node) def visit_Lambda(self, node: ast.Lambda) -> None: """ Checks lambda function's internal complexity. Raises: TooManyArgumentsViolation """ self._counter.check_arguments_count(node) self.generic_visit(node) def _check_function_internals(self) -> None: for var_node, variables in self._counter.variables.items(): if len(variables) > self.options.max_local_variables: self.add_violation( TooManyLocalsViolation(var_node, text=str(len(variables))), ) for exp_node, expressions in self._counter.expressions.items(): if expressions > self.options.max_expressions: self.add_violation( TooManyExpressionsViolation( exp_node, text=str(expressions), ), ) def _check_function_signature(self) -> None: for counter, limit, violation in self._function_checks(): for node, count_result in counter.items(): if count_result > limit: self.add_violation(violation(node, text=str(count_result))) def _function_checks(self) -> List[_CheckRule]: return [ ( self._counter.arguments, self.options.max_arguments, TooManyArgumentsViolation, ), ( self._counter.returns, self.options.max_returns, TooManyReturnsViolation, ), ( self._counter.awaits, self.options.max_awaits, TooManyAwaitsViolation, ), ( self._counter.asserts, self.options.max_asserts, TooManyAssertsViolation, ), ] def _post_visit(self) -> None: self._check_function_signature() self._check_function_internals() PK!=wU 9wemake_python_styleguide/visitors/ast/complexity/jones.py# -*- coding: utf-8 -*- """ Jones Complexity to count inline complexity. Based on the original `jones-complexity` project: https://github.com/Miserlou/JonesComplexity Original project is licensed under MIT. """ import ast from collections import defaultdict from statistics import median from typing import DefaultDict, List from typing_extensions import final from wemake_python_styleguide.compat.aliases import FunctionNodes from wemake_python_styleguide.violations.complexity import ( JonesScoreViolation, LineComplexityViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor @final class JonesComplexityVisitor(BaseNodeVisitor): """ This visitor is used to find complex lines in the code. Calculates the number of AST nodes per line of code. Also calculates the median nodes/line score. Then compares these numbers to the given tressholds. Some nodes are ignored because there's no sense in analyzing them. Some nodes like type annotations are not affecting line complexity, so we do not count them. """ _ignored_nodes = ( ast.ClassDef, *FunctionNodes, ) def __init__(self, *args, **kwargs) -> None: """Initializes line number counter.""" super().__init__(*args, **kwargs) self._lines: DefaultDict[int, List[ast.AST]] = defaultdict(list) self._to_ignore: List[ast.AST] = [] def visit(self, node: ast.AST) -> None: """ Visits all nodes, sums the number of nodes per line. Then calculates the median value of all line results. Raises: JonesScoreViolation LineComplexityViolation """ line_number = getattr(node, 'lineno', None) is_ignored = isinstance(node, self._ignored_nodes) if line_number is not None and not is_ignored: if not self._maybe_ignore_child(node): self._lines[line_number].append(node) self.generic_visit(node) def _post_visit(self) -> None: """ Triggers after the whole module was processed. Checks each line for its complexity, compares it to the tresshold. We also calculate the final Jones score for the whole module. """ for line_nodes in self._lines.values(): complexity = len(line_nodes) if complexity > self.options.max_line_complexity: self.add_violation(LineComplexityViolation( line_nodes[0], text=str(complexity), )) node_counts = [len(nodes) for nodes in self._lines.values()] total_count = median(node_counts) if node_counts else 0 if total_count > self.options.max_jones_score: self.add_violation(JonesScoreViolation(text=str(total_count))) def _maybe_ignore_child(self, node: ast.AST) -> bool: if isinstance(node, ast.AnnAssign): self._to_ignore.append(node.annotation) return node in self._to_ignore PK!I֊a a :wemake_python_styleguide/visitors/ast/complexity/nested.py# -*- coding: utf-8 -*- import ast from typing_extensions import final from wemake_python_styleguide.compat.aliases import FunctionNodes from wemake_python_styleguide.constants import ( NESTED_CLASSES_WHITELIST, NESTED_FUNCTIONS_WHITELIST, ) from wemake_python_styleguide.logic.nodes import get_context, get_parent from wemake_python_styleguide.logic.walk import is_child_of from wemake_python_styleguide.types import AnyFunctionDef from wemake_python_styleguide.violations.best_practices import ( NestedClassViolation, NestedFunctionViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias @final @alias('visit_any_function', ( 'visit_FunctionDef', 'visit_AsyncFunctionDef', )) class NestedComplexityVisitor(BaseNodeVisitor): """ Checks that structures are not nested. We disallow to use nested functions and nested classes. Because flat is better than nested. We allow to nest function inside classes, that's called methods. """ def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Used to find nested classes in other classes and functions. Uses ``NESTED_CLASSES_WHITELIST`` to respect some nested classes. Raises: NestedClassViolation """ self._check_nested_classes(node) self.generic_visit(node) def visit_any_function(self, node: AnyFunctionDef) -> None: """ Used to find nested functions. Uses ``NESTED_FUNCTIONS_WHITELIST`` to respect some nested functions. Raises: NestedFunctionViolation """ self._check_nested_function(node) self.generic_visit(node) def visit_Lambda(self, node: ast.Lambda) -> None: """ Used to find nested ``lambda`` functions. Raises: NestedFunctionViolation """ self._check_nested_lambdas(node) self.generic_visit(node) def _check_nested_function(self, node: AnyFunctionDef) -> None: is_inside_function = isinstance(get_context(node), FunctionNodes) is_direct = isinstance(get_parent(node), FunctionNodes) is_bad = is_direct and node.name not in NESTED_FUNCTIONS_WHITELIST if is_bad or (is_inside_function and not is_direct): self.add_violation(NestedFunctionViolation(node, text=node.name)) def _check_nested_classes(self, node: ast.ClassDef) -> None: parent_context = get_context(node) is_inside_class = isinstance(parent_context, ast.ClassDef) is_bad = is_inside_class and node.name not in NESTED_CLASSES_WHITELIST is_inside_function = isinstance(parent_context, FunctionNodes) if is_bad or is_inside_function: self.add_violation(NestedClassViolation(node, text=node.name)) def _check_nested_lambdas(self, node: ast.Lambda) -> None: is_direct = isinstance(get_context(node), ast.Lambda) is_deep = is_child_of(node, ast.Lambda) if is_direct or is_deep: self.add_violation(NestedFunctionViolation(node, text='lambda')) PK!{:wemake_python_styleguide/visitors/ast/complexity/offset.py# -*- coding: utf-8 -*- import ast from typing import ClassVar from typing_extensions import final from wemake_python_styleguide.violations.complexity import ( TooDeepNestingViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias @final @alias('visit_line_expression', ( 'visit_Try', 'visit_ExceptHandler', 'visit_For', 'visit_With', 'visit_While', 'visit_If', 'visit_Raise', 'visit_Return', 'visit_Continue', 'visit_Break', 'visit_Assign', 'visit_Expr', 'visit_Pass', 'visit_ClassDef', 'visit_FunctionDef', 'visit_AsyncFor', 'visit_AsyncWith', 'visit_AsyncFunctionDef', )) class OffsetVisitor(BaseNodeVisitor): """Checks offset values for several nodes.""" #: Maximum number of blocks to nest different structures: _max_offset_blocks: ClassVar[int] = 5 def visit_line_expression(self, node: ast.AST) -> None: """ Checks statement's offset. We check only several nodes, because other nodes might have different offsets, which is fine. For example, ``ast.Name`` node has inline offset, which can take values from ``0`` to ``~80``. But ``Name`` node is allowed to behave like so. So, we only check nodes that represent "all liners". Raises: TooDeepNestingViolation """ self._check_offset(node) self.generic_visit(node) def _check_offset(self, node: ast.AST) -> None: offset = getattr(node, 'col_offset', 0) if offset > self._max_offset_blocks * 4: self.add_violation(TooDeepNestingViolation(node, text=str(offset))) PK!^<wemake_python_styleguide/visitors/ast/complexity/overuses.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from typing import ClassVar, DefaultDict, List, Union from typing_extensions import final from wemake_python_styleguide.compat.aliases import FunctionNodes from wemake_python_styleguide.constants import SPECIAL_ARGUMENT_NAMES_WHITELIST from wemake_python_styleguide.logic import nodes, source, walk from wemake_python_styleguide.types import AnyNodes from wemake_python_styleguide.violations import complexity from wemake_python_styleguide.visitors import base _Expressions = DefaultDict[str, List[ast.AST]] _FunctionExpressions = DefaultDict[ast.AST, _Expressions] _Annotated = Union[ast.arg, ast.AnnAssign] _AnnNodes = (ast.AnnAssign, ast.arg) @final class StringOveruseVisitor(base.BaseNodeVisitor): """Restricts several string usages.""" def __init__(self, *args, **kwargs) -> None: """Inits the counter for constants.""" super().__init__(*args, **kwargs) self._string_constants: DefaultDict[str, int] = defaultdict(int) def visit_Str(self, node: ast.Str) -> None: """ Restricts to over-use string constants. Raises: OverusedStringViolation """ self._check_string_constant(node) self.generic_visit(node) def _check_string_constant(self, node: ast.Str) -> None: parent = nodes.get_parent(node) if isinstance(parent, _AnnNodes) and parent.annotation == node: return # it is argument or variable annotation if isinstance(parent, FunctionNodes) and parent.returns == node: return # it is return annotation self._string_constants[node.s] += 1 def _post_visit(self) -> None: for string, usage_count in self._string_constants.items(): if usage_count > self.options.max_string_usages: self.add_violation( complexity.OverusedStringViolation(text=string or "''"), ) @final class ExpressionOveruseVisitor(base.BaseNodeVisitor): """Finds overused expressions.""" _expressions: ClassVar[AnyNodes] = ( # We do not treat `ast.Attribute`s as expressions # because they are too widely used. That's a compromise. ast.Assert, ast.BoolOp, ast.BinOp, ast.Call, ast.Compare, ast.Starred, ast.Subscript, ast.UnaryOp, ast.Lambda, ast.DictComp, ast.Dict, ast.List, ast.ListComp, ast.Tuple, ast.GeneratorExp, ast.Set, ast.SetComp, ) _msg: ClassVar[str] = '{0}; used {1} times' def __init__(self, *args, **kwargs) -> None: """We need to track expression usage in functions and modules.""" super().__init__(*args, **kwargs) self._module_expressions: _Expressions = defaultdict(list) self._function_expressions: _FunctionExpressions = defaultdict( lambda: defaultdict(list), ) def visit(self, node: ast.AST) -> None: """ Visits all nodes in a module to find overused values. Raises: OverusedExpressionViolation """ if isinstance(node, self._expressions): self._add_expression(node) self.generic_visit(node) def _add_expression(self, node: ast.AST) -> None: ignore_predicates = [ self._is_decorator, self._is_self_method, self._is_annotation, # We use this predicate because classes have quite complex # DSL to be created: like django-orm, attrs, and dataclasses. # And these DSLs are built using attributes and calls. _is_class_context, ] if any(ignore(node) for ignore in ignore_predicates): return source_code = source.node_to_string(node) self._module_expressions[source_code].append(node) maybe_function = walk.get_closest_parent(node, FunctionNodes) if maybe_function is not None: self._function_expressions[maybe_function][source_code].append( node, ) def _is_decorator( self, node: ast.AST, ) -> bool: parent = walk.get_closest_parent(node, FunctionNodes) if isinstance(parent, FunctionNodes) and parent.decorator_list: return any( node == decorator or walk.is_contained_by(node, decorator) for decorator in parent.decorator_list ) return False def _is_self_method(self, node: ast.AST) -> bool: if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute): if isinstance(node.func.value, ast.Name): if node.func.value.id in SPECIAL_ARGUMENT_NAMES_WHITELIST: return True return False def _is_annotation(self, node: ast.AST) -> bool: typed_assign = walk.get_closest_parent( node, (ast.AnnAssign, ast.arg), ) if isinstance(typed_assign, _AnnNodes) and typed_assign.annotation: is_same_node = node == typed_assign.annotation is_child_annotation = walk.is_contained_by( node, typed_assign.annotation, ) return is_same_node or is_child_annotation return False def _post_visit(self) -> None: for mod_source, module_nodes in self._module_expressions.items(): if len(module_nodes) > self.options.max_module_expressions: self.add_violation( complexity.OverusedExpressionViolation( module_nodes[0], text=self._msg.format(mod_source, len(module_nodes)), ), ) for function_contexts in self._function_expressions.values(): for src, function_nodes in function_contexts.items(): if len(function_nodes) > self.options.max_function_expressions: self.add_violation( complexity.OverusedExpressionViolation( function_nodes[0], text=self._msg.format(src, len(function_nodes)), ), ) def _is_class_context(node: ast.AST) -> bool: return isinstance(nodes.get_context(node), ast.ClassDef) PK!##3wemake_python_styleguide/visitors/ast/conditions.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from functools import reduce from typing import ClassVar, DefaultDict, Dict, List, Set, Type from typing_extensions import final from wemake_python_styleguide.logic import ifs, source from wemake_python_styleguide.logic.compares import CompareBounds from wemake_python_styleguide.logic.functions import given_function_called from wemake_python_styleguide.logic.nodes import get_parent from wemake_python_styleguide.types import AnyIf, AnyNodes from wemake_python_styleguide.violations.best_practices import ( SameElementsInConditionViolation, ) from wemake_python_styleguide.violations.consistency import ( ImplicitComplexCompareViolation, ImplicitTernaryViolation, MultilineConditionsViolation, ) from wemake_python_styleguide.violations.refactoring import ( ImplicitInConditionViolation, NegatedConditionsViolation, UnmergedIsinstanceCallsViolation, UselessLenCompareViolation, UselessReturningElseViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor def _duplicated_isinstance_call(node: ast.BoolOp) -> List[str]: counter: DefaultDict[str, int] = defaultdict(int) for call in node.values: if not isinstance(call, ast.Call) or len(call.args) != 2: continue if not given_function_called(call, {'isinstance'}): continue isinstance_object = source.node_to_string(call.args[0]) counter[isinstance_object] += 1 return [ node_name for node_name, count in counter.items() if count > 1 ] def _get_duplicate_names(variables: List[Set[str]]): return reduce( lambda acc, element: acc.intersection(element), variables, ) @final class IfStatementVisitor(BaseNodeVisitor): """Checks single and consecutive ``if`` statement nodes.""" #: Nodes that break or return the execution flow. _returning_nodes: ClassVar[AnyNodes] = ( ast.Break, ast.Raise, ast.Return, ast.Continue, ) def __init__(self, *args, **kwargs) -> None: """We need to store visited ``if`` not to dublicate violations.""" super().__init__(*args, **kwargs) self._visited_ifs: Set[ast.If] = set() def visit_If(self, node: ast.If) -> None: """ Checks ``if`` nodes. Raises: UselessReturningElseViolation NegatedConditionsViolation MultilineConditionsViolation UselessLenCompareViolation """ self._check_negated_conditions(node) self._check_useless_else(node) self._check_multiline_conditions(node) self._check_useless_len(node) self.generic_visit(node) def visit_IfExp(self, node: ast.IfExp) -> None: """ Checks ``if`` expressions. Raises: UselessLenCompareViolation """ self._check_useless_len(node) self.generic_visit(node) def _check_negated_conditions(self, node: ast.If) -> None: if not node.orelse: return if isinstance(node.test, ast.UnaryOp): if isinstance(node.test.op, ast.Not): self.add_violation(NegatedConditionsViolation(node)) elif isinstance(node.test, ast.Compare): if any(isinstance(elem, ast.NotEq) for elem in node.test.ops): self.add_violation(NegatedConditionsViolation(node)) def _check_multiline_conditions(self, node: ast.If) -> None: """Checks multiline conditions ``if`` statement nodes.""" start_lineno = getattr(node, 'lineno', None) for sub_nodes in ast.walk(node.test): sub_lineno = getattr(sub_nodes, 'lineno', None) if sub_lineno is not None and sub_lineno > start_lineno: self.add_violation(MultilineConditionsViolation(node)) break def _check_useless_else(self, node: ast.If) -> None: real_ifs = [] for chained_if in ifs.chain(node): if isinstance(chained_if, ast.If): if chained_if in self._visited_ifs: return self._visited_ifs.update({chained_if}) real_ifs.append(chained_if) continue previous_has_returns = all( ifs.has_nodes( self._returning_nodes, real_if.body, ) for real_if in real_ifs ) current_has_returns = ifs.has_nodes( self._returning_nodes, chained_if, ) if previous_has_returns and current_has_returns: self.add_violation( UselessReturningElseViolation(chained_if[0]), ) def _check_useless_len(self, node: AnyIf) -> None: if isinstance(node.test, ast.Call): if given_function_called(node.test, {'len'}): self.add_violation(UselessLenCompareViolation(node)) @final class BooleanConditionVisitor(BaseNodeVisitor): """Ensures that boolean conditions are correct.""" def __init__(self, *args, **kwargs) -> None: """We need to store some bool nodes not to visit them twice.""" super().__init__(*args, **kwargs) self._same_nodes: List[ast.BoolOp] = [] self._isinstance_calls: List[ast.BoolOp] = [] def visit_BoolOp(self, node: ast.BoolOp) -> None: """ Checks that ``and`` and ``or`` conditions are correct. Raises: SameElementsInConditionViolation UnmergedIsinstanceCallsViolation """ self._check_same_elements(node) self._check_isinstance_calls(node) self.generic_visit(node) def _get_all_names( self, node: ast.BoolOp, ) -> List[str]: # We need to make sure that we do not visit # one chained `BoolOp` elements twice: self._same_nodes.append(node) names = [] for operand in node.values: if isinstance(operand, ast.BoolOp): names.extend(self._get_all_names(operand)) else: names.append(source.node_to_string(operand)) return names def _check_same_elements(self, node: ast.BoolOp) -> None: if node in self._same_nodes: return # We do not visit nested `BoolOp`s twice. operands = self._get_all_names(node) if len(set(operands)) != len(operands): self.add_violation(SameElementsInConditionViolation(node)) def _check_isinstance_calls(self, node: ast.BoolOp) -> None: if not isinstance(node.op, ast.Or): return for var_name in _duplicated_isinstance_call(node): self.add_violation( UnmergedIsinstanceCallsViolation(node, text=var_name), ) @final class ImplicitBoolPatternsVisitor(BaseNodeVisitor): """Is used to find implicit patterns that are formed by boolops.""" def visit_BoolOp(self, node: ast.BoolOp) -> None: """ Checks that ``and`` and ``or`` do not form implicit anti-patterns. Raises: ImplicitTernaryViolation ImplicitComplexCompareViolation ImplicitInConditionViolation """ self._check_implicit_in(node) self._check_implicit_ternary(node) self._check_implicit_complex_compare(node) self.generic_visit(node) def _check_implicit_in(self, node: ast.BoolOp) -> None: allowed_ops: Dict[Type[ast.boolop], Type[ast.cmpop]] = { ast.And: ast.NotEq, ast.Or: ast.Eq, } variables: List[Set[str]] = [] for compare in node.values: if not isinstance(compare, ast.Compare) or len(compare.ops) != 1: return if not isinstance(compare.ops[0], allowed_ops[node.op.__class__]): return variables.append({source.node_to_string(compare.left)}) for duplicate in _get_duplicate_names(variables): self.add_violation( ImplicitInConditionViolation(node, text=duplicate), ) def _check_implicit_ternary(self, node: ast.BoolOp) -> None: if isinstance(get_parent(node), ast.BoolOp): return if not isinstance(node.op, ast.Or): return if len(node.values) != 2: return if not isinstance(node.values[0], ast.BoolOp): return is_implicit_ternary = ( len(node.values[0].values) == 2 and not isinstance(node.values[1], ast.BoolOp) and isinstance(node.values[0].op, ast.And) and not isinstance(node.values[0].values[1], ast.BoolOp) ) if is_implicit_ternary: self.add_violation(ImplicitTernaryViolation(node)) def _check_implicit_complex_compare(self, node: ast.BoolOp) -> None: if not isinstance(node.op, ast.And): return if not CompareBounds(node).is_valid(): self.add_violation(ImplicitComplexCompareViolation(node)) PK!T3wemake_python_styleguide/visitors/ast/exceptions.py# -*- coding: utf-8 -*- import ast from collections import Counter from typing import ClassVar, List, Tuple from typing_extensions import final from wemake_python_styleguide.logic import source from wemake_python_styleguide.logic.walk import is_contained from wemake_python_styleguide.types import AnyNodes from wemake_python_styleguide.violations.best_practices import ( BaseExceptionViolation, DuplicateExceptionViolation, TryExceptMultipleReturnPathViolation, ) from wemake_python_styleguide.violations.consistency import ( UselessExceptCaseViolation, ) from wemake_python_styleguide.violations.refactoring import ( NestedTryViolation, UselessFinallyViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor def _find_returing_nodes( node: ast.Try, bad_returning_nodes: AnyNodes, ) -> Tuple[bool, bool, bool, bool]: try_has = any( # TODO: also check ast.Break is_contained(line, bad_returning_nodes) for line in node.body ) except_has = any( is_contained(except_handler, bad_returning_nodes) for except_handler in node.handlers ) else_has = any( is_contained(line, bad_returning_nodes) for line in node.orelse ) finally_has = any( is_contained(line, bad_returning_nodes) for line in node.finalbody ) return try_has, except_has, else_has, finally_has @final class WrongTryExceptVisitor(BaseNodeVisitor): """Responsible for examining ``try`` and friends.""" _base_exception: ClassVar[str] = 'BaseException' _bad_returning_nodes: ClassVar[AnyNodes] = ( ast.Return, ast.Raise, ast.Break, ) def visit_Try(self, node: ast.Try) -> None: """ Used for find finally in try blocks without except. Raises: UselessFinallyViolation DuplicateExceptionViolation TryExceptMultipleReturnPathViolation """ self._check_if_needs_except(node) self._check_duplicate_exceptions(node) self._check_return_path(node) self.generic_visit(node) def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None: """ Checks all ``ExceptionHandler`` nodes. Raises: BaseExceptionViolation UselessExceptCaseViolation """ self._check_useless_except(node) self._check_exception_type(node) self.generic_visit(node) def _check_if_needs_except(self, node: ast.Try) -> None: if node.finalbody and not node.handlers: self.add_violation(UselessFinallyViolation(node)) def _check_exception_type(self, node: ast.ExceptHandler) -> None: exception_name = node.type if exception_name is None: return exception_id = getattr(exception_name, 'id', None) if exception_id == self._base_exception: self.add_violation(BaseExceptionViolation(node)) def _check_duplicate_exceptions(self, node: ast.Try) -> None: exceptions: List[str] = [] for exc_handler in node.handlers: # There might be complex things hidden inside an exception type, # so we want to get the string representation of it: if isinstance(exc_handler.type, ast.Name): exceptions.append(source.node_to_string(exc_handler.type)) elif isinstance(exc_handler.type, ast.Tuple): exceptions.extend([ source.node_to_string(node) for node in exc_handler.type.elts ]) for exc_name, count in Counter(exceptions).items(): if count > 1: self.add_violation( DuplicateExceptionViolation(node, text=exc_name), ) def _check_return_path(self, node: ast.Try) -> None: try_has, except_has, else_has, finally_has = _find_returing_nodes( node, self._bad_returning_nodes, ) if finally_has and (try_has or except_has or else_has): self.add_violation(TryExceptMultipleReturnPathViolation(node)) return if else_has and try_has: self.add_violation(TryExceptMultipleReturnPathViolation(node)) def _check_useless_except(self, node: ast.ExceptHandler) -> None: if len(node.body) != 1: return body = node.body[0] if not isinstance(body, ast.Raise): return if isinstance(body.exc, ast.Call): return if isinstance(body.exc, ast.Name) and node.name: if body.exc.id != node.name: return self.add_violation(UselessExceptCaseViolation(node)) @final class NestedTryBlocksVisitor(BaseNodeVisitor): """Ensures that there are no nested ``try`` blocks.""" def visit_Try(self, node: ast.Try) -> None: """ Visits all try nodes in the tree. Raises: NestedTryViolation """ self._check_nested_try(node) self.generic_visit(node) def _check_nested_try(self, node: ast.Try) -> None: for sub_node in ast.walk(node): if isinstance(sub_node, ast.Try) and sub_node is not node: self.add_violation(NestedTryViolation(sub_node)) PK![**2wemake_python_styleguide/visitors/ast/functions.py# -*- coding: utf-8 -*- import ast from typing import ClassVar, Dict, List, Optional, Union from typing_extensions import final from wemake_python_styleguide.compat.aliases import FunctionNodes from wemake_python_styleguide.constants import FUNCTIONS_BLACKLIST from wemake_python_styleguide.logic import ( exceptions, functions, nodes, operators, prop_access, walk, ) from wemake_python_styleguide.logic.arguments import function_args from wemake_python_styleguide.logic.naming import access from wemake_python_styleguide.types import AnyFunctionDef, AnyNodes from wemake_python_styleguide.violations.best_practices import ( BooleanPositionalArgumentViolation, ComplexDefaultValueViolation, StopIterationInsideGeneratorViolation, WrongFunctionCallViolation, ) from wemake_python_styleguide.violations.naming import ( UnusedVariableIsUsedViolation, ) from wemake_python_styleguide.violations.oop import WrongSuperCallViolation from wemake_python_styleguide.violations.refactoring import ( ImplicitEnumerateViolation, OpenWithoutContextManagerViolation, TypeCompareViolation, UselessLambdaViolation, WrongIsinstanceWithTupleViolation, ) from wemake_python_styleguide.visitors import base, decorators LocalVariable = Union[ast.Name, ast.ExceptHandler] @final class WrongFunctionCallVisitor(base.BaseNodeVisitor): """ Responsible for restricting some dangerous function calls. All these functions are defined in ``FUNCTIONS_BLACKLIST``. """ def visit_Call(self, node: ast.Call) -> None: """ Used to find ``FUNCTIONS_BLACKLIST`` calls. Raises: BooleanPositionalArgumentViolation WrongFunctionCallViolation WrongSuperCallViolation WrongIsinstanceWithTupleViolation """ self._check_wrong_function_called(node) self._check_boolean_arguments(node) self._check_super_call(node) self._check_isinstance_call(node) self.generic_visit(node) def _check_wrong_function_called(self, node: ast.Call) -> None: function_name = functions.given_function_called( node, FUNCTIONS_BLACKLIST, ) if function_name: self.add_violation( WrongFunctionCallViolation(node, text=function_name), ) def _check_boolean_arguments(self, node: ast.Call) -> None: for arg in node.args: if isinstance(arg, ast.NameConstant): # We do not check for `None` values here: if arg.value is True or arg.value is False: self.add_violation( BooleanPositionalArgumentViolation( arg, text=str(arg.value), ), ) def _ensure_super_context(self, node: ast.Call) -> None: parent_context = nodes.get_context(node) if isinstance(parent_context, FunctionNodes): grand_context = nodes.get_context(parent_context) if isinstance(grand_context, ast.ClassDef): return self.add_violation( WrongSuperCallViolation(node, text='not inside method'), ) def _ensure_super_arguments(self, node: ast.Call) -> None: if node.args or node.keywords: self.add_violation( WrongSuperCallViolation(node, text='remove arguments'), ) def _check_super_call(self, node: ast.Call) -> None: function_name = functions.given_function_called(node, {'super'}) if function_name: self._ensure_super_context(node) self._ensure_super_arguments(node) def _check_isinstance_call(self, node: ast.Call) -> None: function_name = functions.given_function_called(node, {'isinstance'}) if not function_name or len(node.args) != 2: return if isinstance(node.args[1], ast.Tuple): if len(node.args[1].elts) == 1: self.add_violation(WrongIsinstanceWithTupleViolation(node)) @final class WrongFunctionCallContextVisitior(base.BaseNodeVisitor): """Ensure that we call several functions in the correct context.""" def visit_Call(self, node: ast.Call) -> None: """ Visits function calls to find wrong contexts. Raises: OpenWithoutContextManagerViolation TypeCompareViolation ImplicitEnumerateViolation """ self._check_open_call_context(node) self._check_type_compare(node) self._check_range_len(node) self.generic_visit(node) def _check_open_call_context(self, node: ast.Call) -> None: function_name = functions.given_function_called(node, {'open'}) if not function_name: return if isinstance(nodes.get_parent(node), ast.withitem): # We do not care about `with` or `async with` - both are fine. return self.add_violation(OpenWithoutContextManagerViolation(node)) def _check_type_compare(self, node: ast.Call) -> None: function_name = functions.given_function_called(node, {'type'}) if not function_name: return if isinstance(nodes.get_parent(node), ast.Compare): self.add_violation(TypeCompareViolation(node)) def _check_range_len(self, node: ast.Call) -> None: function_name = functions.given_function_called(node, {'range'}) if not function_name: return is_one_argument_range = ( len(node.args) == 1 and isinstance(node.args[0], ast.Call) and functions.given_function_called(node.args[0], {'len'}) ) is_two_arguments_range = ( len(node.args) in {2, 3} and isinstance(node.args[1], ast.Call) and functions.given_function_called(node.args[1], {'len'}) ) if is_one_argument_range or is_two_arguments_range: self.add_violation(ImplicitEnumerateViolation(node)) @final @decorators.alias('visit_any_function', ( 'visit_AsyncFunctionDef', 'visit_FunctionDef', )) class FunctionDefinitionVisitor(base.BaseNodeVisitor): """Responsible for checking function internals.""" _allowed_default_value_types: ClassVar[AnyNodes] = ( ast.Name, ast.Attribute, ast.Str, ast.NameConstant, ast.Tuple, ast.Bytes, ast.Num, ast.Ellipsis, ) def visit_any_function(self, node: AnyFunctionDef) -> None: """ Checks regular, lambda, and async functions. Raises: UnusedVariableIsUsedViolation ComplexDefaultValueViolation StopIterationInsideGeneratorViolation """ self._check_argument_default_values(node) self._check_unused_variables(node) self._check_generator(node) self.generic_visit(node) def _check_used_variables( self, local_variables: Dict[str, List[LocalVariable]], ) -> None: for varname, usages in local_variables.items(): for node in usages: if access.is_protected(varname): self.add_violation( UnusedVariableIsUsedViolation(node, text=varname), ) def _maybe_update_variable( self, sub_node: LocalVariable, var_name: str, local_variables: Dict[str, List[LocalVariable]], ) -> None: if var_name in local_variables: if access.is_unused(var_name): # We check unused variable usage in a different place: # see `visitors/ast/naming.py` return local_variables[var_name].append(sub_node) return is_name_def = isinstance( sub_node, ast.Name, ) and isinstance( sub_node.ctx, ast.Store, ) if is_name_def or isinstance(sub_node, ast.ExceptHandler): local_variables[var_name] = [] def _get_variable_name(self, node: LocalVariable) -> Optional[str]: if isinstance(node, ast.Name): return node.id return getattr(node, 'name', None) def _check_unused_variables(self, node: AnyFunctionDef) -> None: local_variables: Dict[str, List[LocalVariable]] = {} for body_item in node.body: for sub_node in ast.walk(body_item): if not isinstance(sub_node, (ast.Name, ast.ExceptHandler)): continue var_name = self._get_variable_name(sub_node) if not var_name: continue self._maybe_update_variable( sub_node, var_name, local_variables, ) self._check_used_variables(local_variables) def _check_argument_default_values(self, node: AnyFunctionDef) -> None: for arg in node.args.defaults: real_arg = operators.unwrap_unary_node(arg) parts = prop_access.parts(real_arg) if isinstance( real_arg, ast.Attribute, ) else [real_arg] for part in parts: if not isinstance(part, self._allowed_default_value_types): self.add_violation(ComplexDefaultValueViolation(arg)) return def _check_generator(self, node: AnyFunctionDef) -> None: if not functions.is_generator(node): return for sub_node in walk.get_subnodes_by_type(node, ast.Raise): if exceptions.get_exception_name(sub_node) == 'StopIteration': self.add_violation( StopIterationInsideGeneratorViolation(sub_node), ) @final class UselessLambdaDefinitionVisitor(base.BaseNodeVisitor): """This visitor is used specifically for ``lambda`` functions.""" def visit_Lambda(self, node: ast.Lambda) -> None: """ Checks if ``lambda`` functions are defined correctly. Raises: UselessLambdaViolation """ self._check_useless_lambda(node) self.generic_visit(node) def _check_useless_lambda(self, node: ast.Lambda) -> None: if not isinstance(node.body, ast.Call): return if not isinstance(node.body.func, ast.Name): # We do not track method (attr) calls, since it might me complex. return if node.args.defaults or list(filter(None, node.args.kw_defaults)): # It means that `lambda` has defaults in args, # we cannot be sure that these defaults are the same # as in the call def, ignoring it. # `kw_defaults` can have [None, ...] items. return if not function_args.is_call_matched_by_arguments(node, node.body): return self.add_violation(UselessLambdaViolation(node)) PK!#0wemake_python_styleguide/visitors/ast/imports.py# -*- coding: utf-8 -*- import ast from itertools import chain from typing import Callable from typing_extensions import final from wemake_python_styleguide.constants import FUTURE_IMPORTS_WHITELIST from wemake_python_styleguide.logic import imports, nodes from wemake_python_styleguide.logic.naming import access from wemake_python_styleguide.types import AnyImport from wemake_python_styleguide.violations.base import BaseViolation from wemake_python_styleguide.violations.best_practices import ( FutureImportViolation, NestedImportViolation, ProtectedModuleViolation, ) from wemake_python_styleguide.violations.consistency import ( DottedRawImportViolation, LocalFolderImportViolation, ) from wemake_python_styleguide.violations.naming import SameAliasImportViolation from wemake_python_styleguide.visitors.base import BaseNodeVisitor ErrorCallback = Callable[[BaseViolation], None] # TODO: alias and move @final class _ImportsValidator(object): """Utility class to separate logic from the visitor.""" def __init__(self, error_callback: ErrorCallback) -> None: self._error_callback = error_callback def check_nested_import(self, node: AnyImport) -> None: parent = nodes.get_parent(node) if parent is not None and not isinstance(parent, ast.Module): self._error_callback(NestedImportViolation(node)) def check_local_import(self, node: ast.ImportFrom) -> None: if node.level != 0: self._error_callback(LocalFolderImportViolation(node)) def check_future_import(self, node: ast.ImportFrom) -> None: if node.module == '__future__': for alias in node.names: if alias.name not in FUTURE_IMPORTS_WHITELIST: self._error_callback( FutureImportViolation(node, text=alias.name), ) def check_dotted_raw_import(self, node: ast.Import) -> None: for alias in node.names: if '.' in alias.name: self._error_callback( DottedRawImportViolation(node, text=alias.name), ) def check_alias(self, node: AnyImport) -> None: for alias in node.names: if alias.asname == alias.name: self._error_callback( SameAliasImportViolation(node, text=alias.name), ) def check_protected_import(self, node: AnyImport) -> None: import_names = [alias.name for alias in node.names] for name in chain(imports.get_import_parts(node), import_names): if access.is_protected(name): self._error_callback(ProtectedModuleViolation(node)) @final class WrongImportVisitor(BaseNodeVisitor): """Responsible for finding wrong imports.""" def __init__(self, *args, **kwargs) -> None: """Creates a checker for tracked violations.""" super().__init__(*args, **kwargs) self._validator = _ImportsValidator(self.add_violation) def visit_Import(self, node: ast.Import) -> None: """ Used to find wrong ``import`` statements. Raises: SameAliasImportViolation DottedRawImportViolation NestedImportViolation """ self._validator.check_nested_import(node) self._validator.check_dotted_raw_import(node) self._validator.check_alias(node) self._validator.check_protected_import(node) self.generic_visit(node) def visit_ImportFrom(self, node: ast.ImportFrom) -> None: """ Used to find wrong ``from ... import ...`` statements. Raises: SameAliasImportViolation NestedImportViolation LocalFolderImportViolation FutureImportViolation """ self._validator.check_local_import(node) self._validator.check_nested_import(node) self._validator.check_future_import(node) self._validator.check_alias(node) self._validator.check_protected_import(node) self.generic_visit(node) PK!e++1wemake_python_styleguide/visitors/ast/keywords.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from contextlib import suppress from typing import ClassVar, Dict, List, Tuple, Type, Union from typing_extensions import final from wemake_python_styleguide.compat.aliases import FunctionNodes from wemake_python_styleguide.logic import walk from wemake_python_styleguide.logic.exceptions import get_exception_name from wemake_python_styleguide.logic.nodes import get_context, get_parent from wemake_python_styleguide.logic.variables import ( is_valid_block_variable_definition, ) from wemake_python_styleguide.types import AnyFunctionDef, AnyNodes, AnyWith from wemake_python_styleguide.violations.best_practices import ( ContextManagerVariableDefinitionViolation, RaiseNotImplementedViolation, WrongKeywordConditionViolation, WrongKeywordViolation, ) from wemake_python_styleguide.violations.consistency import ( InconsistentReturnVariableViolation, InconsistentReturnViolation, InconsistentYieldViolation, MultipleContextManagerAssignmentsViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias NamesAndReturns = Tuple[ Dict[str, List[ast.Name]], Dict[str, ast.Return], ] ReturningViolations = Union[ Type[InconsistentReturnViolation], Type[InconsistentYieldViolation], ] @final class WrongRaiseVisitor(BaseNodeVisitor): """Finds wrong ``raise`` keywords.""" def visit_Raise(self, node: ast.Raise) -> None: """ Checks how ``raise`` keyword is used. Raises: RaiseNotImplementedViolation """ self._check_exception_type(node) self.generic_visit(node) def _check_exception_type(self, node: ast.Raise) -> None: exception_name = get_exception_name(node) if exception_name == 'NotImplemented': self.add_violation(RaiseNotImplementedViolation(node)) @final @alias('visit_any_function', ( 'visit_FunctionDef', 'visit_AsyncFunctionDef', )) class ConsistentReturningVisitor(BaseNodeVisitor): """Finds incorrect and inconsistent ``return`` and ``yield`` nodes.""" def visit_Return(self, node: ast.Return) -> None: """ Checks ``return`` statements for consistency. Raises: InconsistentReturnViolation """ self._check_last_return_in_function(node) self.generic_visit(node) def visit_any_function(self, node: AnyFunctionDef) -> None: """ Helper to get all ``return`` and ``yield`` nodes in a function at once. Raises: InconsistentReturnViolation InconsistentYieldViolation """ self._check_return_values(node) self._check_yield_values(node) self.generic_visit(node) def _check_last_return_in_function(self, node: ast.Return) -> None: parent = get_parent(node) if not isinstance(parent, FunctionNodes): return returns = len(list(filter( lambda return_node: return_node.value is not None, walk.get_subnodes_by_type(parent, ast.Return), ))) last_value_return = ( len(parent.body) > 1 and returns < 2 and isinstance(node.value, ast.NameConstant) and node.value.value is None ) if node.value is None or last_value_return: self.add_violation(InconsistentReturnViolation(node)) def _iterate_returning_values( self, node: AnyFunctionDef, returning_type, # mypy is not ok with this type declaration violation: ReturningViolations, ): returns: List[ast.Return] = [] has_values = False for sub_node in ast.walk(node): if isinstance(sub_node, returning_type): if sub_node.value: has_values = True returns.append(sub_node) for return_node in returns: if not return_node.value and has_values: self.add_violation(violation(return_node)) def _check_return_values(self, node: AnyFunctionDef) -> None: self._iterate_returning_values( node, ast.Return, InconsistentReturnViolation, ) def _check_yield_values(self, node: AnyFunctionDef) -> None: self._iterate_returning_values( node, ast.Yield, InconsistentYieldViolation, ) @final class WrongKeywordVisitor(BaseNodeVisitor): """Finds wrong keywords.""" _forbidden_keywords: ClassVar[AnyNodes] = ( ast.Pass, ast.Delete, ast.Global, ast.Nonlocal, ) def visit(self, node: ast.AST) -> None: """ Used to find wrong keywords. Raises: WrongKeywordViolation """ self._check_keyword(node) self.generic_visit(node) def _check_keyword(self, node: ast.AST) -> None: if isinstance(node, self._forbidden_keywords): if isinstance(node, ast.Delete): message = 'del' else: message = node.__class__.__qualname__.lower() self.add_violation(WrongKeywordViolation(node, text=message)) @final @alias('visit_any_with', ( 'visit_With', 'visit_AsyncWith', )) class WrongContextManagerVisitor(BaseNodeVisitor): """Checks context managers.""" def visit_withitem(self, node: ast.withitem) -> None: """ Checks that all variables inside context managers defined correctly. Raises: ContextManagerVariableDefinitionViolation """ self._check_variable_definitions(node) self.generic_visit(node) def visit_any_with(self, node: AnyWith) -> None: """ Checks the number of assignments for context managers. Raises: MultipleContextManagerAssignmentsViolation """ self._check_target_assignment(node) self.generic_visit(node) def _check_target_assignment(self, node: AnyWith): if len(node.items) > 1: self.add_violation( MultipleContextManagerAssignmentsViolation(node), ) def _check_variable_definitions(self, node: ast.withitem) -> None: if node.optional_vars is None: return if not is_valid_block_variable_definition(node.optional_vars): self.add_violation( ContextManagerVariableDefinitionViolation(get_parent(node)), ) @final @alias('visit_return_variable', ( 'visit_AsyncFunctionDef', 'visit_FunctionDef', )) class ConsistentReturningVariableVisitor(BaseNodeVisitor): """Finds variables that are only used in `return` statements.""" _checking_nodes: ClassVar[AnyNodes] = ( ast.Assign, ast.AnnAssign, ast.AugAssign, ast.Return, ast.Name, ) def visit_return_variable(self, node: AnyFunctionDef) -> None: """ Helper to get all ``return`` variables in a function at once. Raises: InconsistentReturnVariableViolation """ self._check_variables_for_return(node) self.generic_visit(node) def _get_assign_node_variables(self, node: List[ast.AST]) -> List[str]: assign = [] for sub_node in node: if isinstance(sub_node, ast.Assign): if isinstance(sub_node.targets[0], ast.Name): assign.append(sub_node.targets[0].id) if isinstance(sub_node, ast.AnnAssign): if isinstance(sub_node.target, ast.Name): assign.append(sub_node.target.id) return assign def _get_name_nodes_variable( self, node: List[ast.AST], ) -> Dict[str, List[ast.Name]]: names: Dict[str, List[ast.Name]] = defaultdict(list) for sub_node in node: if isinstance(sub_node, ast.Name): if isinstance(sub_node.ctx, ast.Load): names[sub_node.id].append(sub_node) if isinstance(sub_node, ast.AugAssign): if isinstance(sub_node.target, ast.Name): variable_name = sub_node.target.id names[variable_name].append(sub_node.target) return names def _get_return_node_variables( self, node: List[ast.AST], ) -> NamesAndReturns: returns: Dict[str, List[ast.Name]] = defaultdict(list) return_sub_nodes: Dict[str, ast.Return] = defaultdict() for sub_node in node: if isinstance(sub_node, ast.Return): if isinstance(sub_node.value, ast.Name): variable_name = sub_node.value.id returns[variable_name].append(sub_node.value) return_sub_nodes[variable_name] = sub_node return returns, return_sub_nodes def _is_correct_return_node( self, node: AnyFunctionDef, sub_node: ast.AST, ) -> bool: if get_context(sub_node) != node: return False return isinstance(sub_node, self._checking_nodes) def _check_variables_for_return(self, node: AnyFunctionDef) -> None: nodes = list( filter( lambda sub: self._is_correct_return_node(node, sub), ast.walk(node), ), ) assign = self._get_assign_node_variables(nodes) names = self._get_name_nodes_variable(nodes) returns, return_sub_nodes = self._get_return_node_variables(nodes) returns = {name: returns[name] for name in returns if name in assign} self._check_for_violations(names, return_sub_nodes, returns) def _check_for_violations(self, names, return_sub_nodes, returns) -> None: for variable_name in returns: if not set(names[variable_name]) - set(returns[variable_name]): self.add_violation( InconsistentReturnVariableViolation( return_sub_nodes[variable_name], ), ) @final class ConstantKeywordVisitor(BaseNodeVisitor): """Visits keyword definitions to detect contant conditions.""" def visit_While(self, node: ast.While) -> None: """ Visits ``while`` keyword and tests that loop will execute. Raises: WrongKeywordConditionViolation """ self._check_condition(node.test) self.generic_visit(node) def visit_Assert(self, node: ast.Assert) -> None: """ Visits ``assert`` keyword and tests that condition is correct. Raises: WrongKeywordConditionViolation """ self._check_condition(node.test) self.generic_visit(node) def _check_condition(self, condition: ast.AST) -> None: with suppress(ValueError): constant = ast.literal_eval(condition) if not bool(constant): self.add_violation( WrongKeywordConditionViolation( condition, text=str(constant) if constant != '' else '""', ), ) PK!vR.wemake_python_styleguide/visitors/ast/loops.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from typing import ClassVar, DefaultDict, List, Optional, Union from typing_extensions import final from wemake_python_styleguide.compat.aliases import ForNodes from wemake_python_styleguide.logic.nodes import get_parent from wemake_python_styleguide.logic.operators import unwrap_unary_node from wemake_python_styleguide.logic.variables import ( is_valid_block_variable_definition, ) from wemake_python_styleguide.logic.walk import is_contained from wemake_python_styleguide.types import AnyFor, AnyNodes from wemake_python_styleguide.violations.best_practices import ( LambdaInsideLoopViolation, LoopVariableDefinitionViolation, YieldInComprehensionViolation, ) from wemake_python_styleguide.violations.complexity import ( TooManyForsInComprehensionViolation, ) from wemake_python_styleguide.violations.consistency import ( MultipleIfsInComprehensionViolation, UselessContinueViolation, WrongLoopIterTypeViolation, ) from wemake_python_styleguide.violations.refactoring import ( ImplicitSumViolation, UselessLoopElseViolation, ) from wemake_python_styleguide.visitors import base, decorators _AnyLoop = Union[AnyFor, ast.While] _AnyComprehension = Union[ ast.ListComp, ast.DictComp, ast.SetComp, ast.GeneratorExp, ] @final @decorators.alias('visit_any_comprehension', ( 'visit_ListComp', 'visit_DictComp', 'visit_SetComp', 'visit_GeneratorExp', )) class WrongComprehensionVisitor(base.BaseNodeVisitor): """Checks comprehensions for correctness.""" _max_ifs: ClassVar[int] = 1 _max_fors: ClassVar[int] = 2 def __init__(self, *args, **kwargs) -> None: """Creates a counter for tracked metrics.""" super().__init__(*args, **kwargs) self._fors: DefaultDict[ast.AST, int] = defaultdict(int) def visit_comprehension(self, node: ast.comprehension) -> None: """ Finds multiple ``if`` and ``for`` nodes inside the comprehension. Raises: MultipleIfsInComprehensionViolation TooManyForsInComprehensionViolation """ self._check_ifs(node) self._check_fors(node) self.generic_visit(node) def visit_any_comprehension(self, node: _AnyComprehension) -> None: """ Finds incorrect patterns inside comprehensions. Raises: YieldInComprehensionViolation """ self._check_contains_yield(node) self.generic_visit(node) def _check_ifs(self, node: ast.comprehension) -> None: if len(node.ifs) > self._max_ifs: # We are trying to fix line number in the report, # since `comprehension` does not have this property. parent = get_parent(node) or node self.add_violation(MultipleIfsInComprehensionViolation(parent)) def _check_fors(self, node: ast.comprehension) -> None: parent = get_parent(node) self._fors[parent] = len(parent.generators) # type: ignore def _check_contains_yield(self, node: _AnyComprehension) -> None: for sub_node in ast.walk(node): if isinstance(sub_node, ast.Yield): self.add_violation(YieldInComprehensionViolation(node)) def _post_visit(self) -> None: for node, for_count in self._fors.items(): if for_count > self._max_fors: self.add_violation(TooManyForsInComprehensionViolation(node)) @final @decorators.alias('visit_any_loop', ( 'visit_For', 'visit_While', 'visit_AsyncFor', )) class WrongLoopVisitor(base.BaseNodeVisitor): """Responsible for examining loops.""" def visit_any_loop(self, node: _AnyLoop) -> None: """ Checks ``for`` and ``while`` loops. Raises: UselessLoopElseViolation LambdaInsideLoopViolation """ self._check_loop_needs_else(node) self._check_lambda_inside_loop(node) self._check_useless_continue(node) self.generic_visit(node) def _does_loop_contain_node( # TODO: move, reuse in annotations.py self, loop: Optional[_AnyLoop], to_check: ast.Break, ) -> bool: if loop is None: return False for inner_node in ast.walk(loop): # We are checking this specific node, not just any `break`: if to_check is inner_node: return True return False def _has_break(self, node: _AnyLoop) -> bool: closest_loop = None for subnode in ast.walk(node): if isinstance(subnode, (*ForNodes, ast.While)): if subnode is not node: closest_loop = subnode if isinstance(subnode, ast.Break): is_nested_break = self._does_loop_contain_node( closest_loop, subnode, ) if not is_nested_break: return True return False def _check_loop_needs_else(self, node: _AnyLoop) -> None: if node.orelse and not self._has_break(node): self.add_violation(UselessLoopElseViolation(node)) def _check_lambda_inside_loop(self, node: _AnyLoop) -> None: for subnode in node.body: if is_contained(subnode, (ast.Lambda,)): self.add_violation(LambdaInsideLoopViolation(node)) def _check_useless_continue(self, node: _AnyLoop) -> None: nodes_at_line: DefaultDict[int, List[ast.AST]] = defaultdict(list) for sub_node in ast.walk(node): lineno = getattr(sub_node, 'lineno', None) if lineno is not None: nodes_at_line[lineno].append(sub_node) last_line = nodes_at_line[sorted(nodes_at_line.keys())[-1]] if any(isinstance(last, ast.Continue) for last in last_line): self.add_violation(UselessContinueViolation(node)) @final @decorators.alias('visit_any_for', ( 'visit_For', 'visit_AsyncFor', )) class WrongLoopDefinitionVisitor(base.BaseNodeVisitor): """Responsible for ``for`` loops and comprehensions definitions.""" _forbidden_for_iters: ClassVar[AnyNodes] = ( ast.List, ast.ListComp, ast.Dict, ast.DictComp, ast.Set, ast.SetComp, ast.GeneratorExp, ast.Num, ast.NameConstant, ) def visit_any_for(self, node: AnyFor) -> None: """ Ensures that ``for`` loop definitions are correct. Raises: LoopVariableDefinitionViolation WrongLoopIterTypeViolation ImplicitSumViolation """ self._check_variable_definitions(node.target) self._check_explicit_iter_type(node) self._check_implicit_sum(node) self.generic_visit(node) def visit_comprehension(self, node: ast.comprehension) -> None: """ Ensures that comprehension definitions are correct. Raises: LoopVariableDefinitionViolation """ self._check_variable_definitions(node.target) self._check_explicit_iter_type(node) self.generic_visit(node) def _check_variable_definitions(self, node: ast.AST) -> None: if not is_valid_block_variable_definition(node): self.add_violation(LoopVariableDefinitionViolation(node)) def _check_explicit_iter_type( self, node: Union[AnyFor, ast.comprehension], ) -> None: node_iter = unwrap_unary_node(node.iter) is_wrong = isinstance(node_iter, self._forbidden_for_iters) is_empty = isinstance(node_iter, ast.Tuple) and not node_iter.elts if is_wrong or is_empty: self.add_violation(WrongLoopIterTypeViolation(node_iter)) def _check_implicit_sum(self, node: AnyFor) -> None: is_implicit_sum = ( len(node.body) == 1 and isinstance(node.body[0], ast.AugAssign) and isinstance(node.body[0].op, ast.Add) and isinstance(node.body[0].target, ast.Name) ) if is_implicit_sum: self.add_violation(ImplicitSumViolation(node)) PK!5lj0wemake_python_styleguide/visitors/ast/modules.py# -*- coding: utf-8 -*- import ast from typing import ClassVar from typing_extensions import final from wemake_python_styleguide import constants from wemake_python_styleguide.logic.filenames import get_stem from wemake_python_styleguide.logic.naming.constants import is_constant from wemake_python_styleguide.logic.nodes import get_context from wemake_python_styleguide.logic.strings import is_doc_string from wemake_python_styleguide.types import AnyAssign, AnyNodes from wemake_python_styleguide.violations.best_practices import ( BadMagicModuleFunctionViolation, EmptyModuleViolation, InitModuleHasLogicViolation, MutableModuleConstantViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias @final class EmptyModuleContentsVisitor(BaseNodeVisitor): """Restricts to have empty modules.""" def visit_Module(self, node: ast.Module) -> None: """ Checks that module has something other than module definition. We have completely different rules for ``__init__.py`` and regular files. Since, we believe that ``__init__.py`` must be empty. But, other files must have contents. Raises: EmptyModuleViolation InitModuleHasLogicViolation """ self._check_init_contents(node) self._check_module_contents(node) self.generic_visit(node) def _is_init(self) -> bool: return get_stem(self.filename) == constants.INIT def _check_module_contents(self, node: ast.Module) -> None: if self._is_init(): return if not node.body: self.add_violation(EmptyModuleViolation()) def _check_init_contents(self, node: ast.Module) -> None: if not self._is_init() or not node.body: return if not self.options.i_control_code: return if len(node.body) > 1: self.add_violation(InitModuleHasLogicViolation()) return if not is_doc_string(node.body[0]): self.add_violation(InitModuleHasLogicViolation()) @final class MagicModuleFunctionsVisitor(BaseNodeVisitor): """Restricts to use magic module functions.""" def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Checks that module hasn't magic module functions. All this functions are defined in ``MAGIC_MODULE_NAMES_BLACKLIST`` Raises: BadMagicModuleFunctionViolation """ self._check_magic_module_functions(node) self.generic_visit(node) def _check_magic_module_functions(self, node: ast.FunctionDef) -> None: if node.name in constants.MAGIC_MODULE_NAMES_BLACKLIST: self.add_violation( BadMagicModuleFunctionViolation(node, text=node.name), ) @final @alias('visit_any_assign', ( 'visit_Assign', 'visit_AnnAssign', )) class ModuleConstantsVisitor(BaseNodeVisitor): """Finds incorrect module constants.""" _mutable_nodes: ClassVar[AnyNodes] = ( ast.Dict, ast.List, ast.Set, ast.DictComp, ast.ListComp, ast.SetComp, ) def visit_any_assign(self, node: AnyAssign) -> None: """ Checks that module's cannot have incorrect constants. Raises: MutableModuleConstantViolation """ self._check_mutable_constant(node) self.generic_visit(node) def _check_mutable_constant(self, node: AnyAssign) -> None: if not isinstance(get_context(node), ast.Module): return if isinstance(node, ast.AnnAssign): targets = [node.target] else: targets = node.targets for target in targets: if not isinstance(target, ast.Name) or not is_constant(target.id): continue if isinstance(node.value, self._mutable_nodes): self.add_violation(MutableModuleConstantViolation(target)) PK!4׌k33/wemake_python_styleguide/visitors/ast/naming.py# -*- coding: utf-8 -*- import ast import itertools from collections import Counter from typing import Callable, Iterable, List, Optional, Tuple, Union, cast from typing_extensions import final from wemake_python_styleguide.compat.aliases import AssignNodes from wemake_python_styleguide.compat.functions import get_assign_targets from wemake_python_styleguide.constants import ( MODULE_METADATA_VARIABLES_BLACKLIST, SPECIAL_ARGUMENT_NAMES_WHITELIST, VARIABLE_NAMES_BLACKLIST, ) from wemake_python_styleguide.logic import functions, nodes from wemake_python_styleguide.logic.naming import ( access, builtins, logical, name_nodes, ) from wemake_python_styleguide.types import ( AnyAssign, AnyFunctionDef, AnyFunctionDefAndLambda, AnyImport, ConfigurationOptions, ) from wemake_python_styleguide.violations import base, naming from wemake_python_styleguide.violations.best_practices import ( ReassigningVariableToItselfViolation, WrongModuleMetadataViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias VariableDef = Union[ast.Name, ast.Attribute, ast.ExceptHandler] AssignTargets = List[ast.expr] AssignTargetsNameList = List[Union[str, Tuple[str]]] @final class _NameValidator(object): """Utility class to separate logic from the naming visitor.""" def __init__( self, error_callback: Callable[[base.BaseViolation], None], options: ConfigurationOptions, ) -> None: """Creates new instance of a name validator.""" self._error_callback = error_callback self._options = options def check_name( self, node: ast.AST, name: str, *, is_first_argument: bool = False, ) -> None: if logical.is_wrong_name(name, VARIABLE_NAMES_BLACKLIST): self._error_callback( naming.WrongVariableNameViolation(node, text=name), ) if not is_first_argument: if logical.is_wrong_name(name, SPECIAL_ARGUMENT_NAMES_WHITELIST): self._error_callback( naming.ReservedArgumentNameViolation(node, text=name), ) if logical.does_contain_unicode(name): self._error_callback(naming.UnicodeNameViolation(node, text=name)) self._ensure_length(node, name) self._ensure_underscores(node, name) def check_function_signature(self, node: AnyFunctionDefAndLambda) -> None: arguments = functions.get_all_arguments(node) is_lambda = isinstance(node, ast.Lambda) for arg in arguments: should_check_argument = functions.is_first_argument( node, arg.arg, ) and not is_lambda self.check_name( arg, arg.arg, is_first_argument=should_check_argument, ) def check_attribute_name(self, node: ast.ClassDef) -> None: top_level_assigns = [ sub_node for sub_node in node.body if isinstance(sub_node, AssignNodes) ] for assignment in top_level_assigns: for target in get_assign_targets(assignment): if not isinstance(target, ast.Name): continue if not target.id or not logical.is_upper_case_name(target.id): continue self._error_callback( naming.UpperCaseAttributeViolation(target, text=target.id), ) def _ensure_underscores(self, node: ast.AST, name: str): if access.is_private(name): self._error_callback( naming.PrivateNameViolation(node, text=name), ) if logical.does_contain_underscored_number(name): self._error_callback( naming.UnderscoredNumberNameViolation(node, text=name), ) if logical.does_contain_consecutive_underscores(name): self._error_callback( naming.ConsecutiveUnderscoresInNameViolation( node, text=name, ), ) if builtins.is_wrong_alias(name): self._error_callback( naming.TrailingUnderscoreViolation(node, text=name), ) if access.is_unused(name) and len(name) > 1: self._error_callback( naming.WrongUnusedVariableNameViolation(node, text=name), ) def _ensure_length(self, node: ast.AST, name: str) -> None: min_length = self._options.min_name_length if logical.is_too_short_name(name, min_length=min_length): self._error_callback(naming.TooShortNameViolation(node, text=name)) max_length = self._options.max_name_length if logical.is_too_long_name(name, max_length=max_length): self._error_callback(naming.TooLongNameViolation(node, text=name)) @final @alias('visit_any_import', ( 'visit_ImportFrom', 'visit_Import', )) @alias('visit_any_function', ( 'visit_FunctionDef', 'visit_AsyncFunctionDef', )) @alias('visit_variable', ( 'visit_Name', 'visit_Attribute', 'visit_ExceptHandler', )) class WrongNameVisitor(BaseNodeVisitor): """Performs checks based on variable names.""" def __init__(self, *args, **kwargs) -> None: """Initializes new naming validator for this visitor.""" super().__init__(*args, **kwargs) self._validator = _NameValidator(self.add_violation, self.options) def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Used to find upper attribute declarations. Raises: UpperCaseAttributeViolation UnicodeNameViolation TrailingUnderscoreViolation """ self._validator.check_attribute_name(node) self._validator.check_name(node, node.name) self.generic_visit(node) def visit_any_function(self, node: AnyFunctionDef) -> None: """ Used to find wrong function and method parameters. Raises: WrongVariableNameViolation TooShortNameViolation PrivateNameViolation TooLongNameViolation UnicodeNameViolation TrailingUnderscoreViolation """ self._validator.check_name(node, node.name) self._validator.check_function_signature(node) self.generic_visit(node) def visit_Lambda(self, node: ast.Lambda) -> None: """ Used to find wrong parameters. Raises: WrongVariableNameViolation TooShortNameViolation PrivateNameViolation TooLongNameViolation TrailingUnderscoreViolation """ self._validator.check_function_signature(node) self.generic_visit(node) def visit_any_import(self, node: AnyImport) -> None: """ Used to check wrong import alias names. Raises: WrongVariableNameViolation TooShortNameViolation PrivateNameViolation TooLongNameViolation TrailingUnderscoreViolation """ for alias_node in node.names: if alias_node.asname: self._validator.check_name(node, alias_node.asname) self.generic_visit(node) def visit_variable(self, node: VariableDef) -> None: """ Used to check wrong names of assigned. Raises: WrongVariableNameViolation TooShortNameViolation PrivateNameViolation TooLongNameViolation UnicodeNameViolation TrailingUnderscoreViolation """ variable_name = name_nodes.get_assigned_name(node) if variable_name is not None: self._validator.check_name(node, variable_name) self.generic_visit(node) @final @alias('visit_any_assign', ( 'visit_Assign', 'visit_AnnAssign', )) class WrongModuleMetadataVisitor(BaseNodeVisitor): """Finds wrong metadata information of a module.""" def visit_any_assign(self, node: AnyAssign) -> None: """ Used to find the bad metadata variable names. Raises: WrongModuleMetadataViolation """ self._check_metadata(node) self.generic_visit(node) def _check_metadata(self, node: AnyAssign) -> None: if not isinstance(nodes.get_parent(node), ast.Module): return targets = get_assign_targets(node) for target_node in targets: if not isinstance(target_node, ast.Name): continue if target_node.id in MODULE_METADATA_VARIABLES_BLACKLIST: self.add_violation( WrongModuleMetadataViolation(node, text=target_node.id), ) @final @alias('visit_any_assign', ( 'visit_Assign', 'visit_AnnAssign', )) class WrongVariableAssignmentVisitor(BaseNodeVisitor): """Finds wrong variables assignments.""" def visit_any_assign(self, node: AnyAssign) -> None: """ Used to check assignment variable to itself. Raises: ReassigningVariableToItselfViolation """ names = list(name_nodes.flat_variable_names([node])) self._check_reassignment(node, names) self._check_unique_assignment(node, names) self.generic_visit(node) def _check_reassignment( self, node: AnyAssign, names: List[str], ) -> None: if not node.value: return var_values = name_nodes.get_variables_from_node(node.value) for var_name, var_value in itertools.zip_longest(names, var_values): if var_name == var_value: self.add_violation( ReassigningVariableToItselfViolation(node, text=var_name), ) def _check_unique_assignment( self, node: AnyAssign, names: List[str], ) -> None: for used_name, count in Counter(names).items(): if count > 1: self.add_violation( ReassigningVariableToItselfViolation(node, text=used_name), ) @final @alias('visit_any_assign', ( 'visit_Assign', 'visit_AnnAssign', )) class WrongVariableUsageVisitor(BaseNodeVisitor): """Checks how variables are used.""" def visit_Name(self, node: ast.Name) -> None: """ Checks that we cannot use ``_`` anywhere. Raises: UnusedVariableIsUsedViolation """ self._check_variable_used( node, node.id, is_created=isinstance(node.ctx, ast.Store), ) self.generic_visit(node) def visit_any_assign(self, node: AnyAssign) -> None: """ Checks that we cannot assign explicit unused variables. We do not check assignes inside modules and classes, since there ``_`` prefixed variable means that it is protected, not unused. Raises: UnusedVariableIsDefinedViolation """ is_inside_class_or_module = isinstance( nodes.get_context(node), (ast.ClassDef, ast.Module), ) self._check_assign_unused( node, name_nodes.flat_variable_names([node]), is_local=not is_inside_class_or_module, ) self.generic_visit(node) def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None: """ Checks that we cannot create explicit unused exceptions. Raises: UnusedVariableIsDefinedViolation """ if node.name: self._check_assign_unused(node, [node.name]) self.generic_visit(node) def visit_withitem(self, node: ast.withitem) -> None: """ Checks that we cannot create explicit unused context variables. Raises: UnusedVariableIsDefinedViolation """ if node.optional_vars: self._check_assign_unused( cast(ast.AST, nodes.get_parent(node)), name_nodes.get_variables_from_node(node.optional_vars), ) self.generic_visit(node) def _check_variable_used( self, node: ast.AST, assigned_name: Optional[str], *, is_created: bool, ) -> None: if not assigned_name or not access.is_unused(assigned_name): return if not is_created: self.add_violation( naming.UnusedVariableIsUsedViolation(node, text=assigned_name), ) def _check_assign_unused( self, node: ast.AST, all_names: Iterable[str], *, is_local: bool = True, ) -> None: all_names = list(all_names) # we are using it twice all_unused = all( is_local if access.is_protected(vn) else access.is_unused(vn) for vn in all_names ) if all_names and all_unused: self.add_violation( naming.UnusedVariableIsDefinedViolation( node, text=', '.join(all_names), ), ) PK!NEE2wemake_python_styleguide/visitors/ast/operators.py# -*- coding: utf-8 -*- import ast from typing import ClassVar, Mapping, Optional, Tuple, Type from typing_extensions import final from wemake_python_styleguide.logic import walk from wemake_python_styleguide.logic.operators import ( count_unary_operator, unwrap_unary_node, ) from wemake_python_styleguide.types import AnyNodes from wemake_python_styleguide.violations import consistency from wemake_python_styleguide.violations.best_practices import ( ListMultiplyViolation, ) from wemake_python_styleguide.visitors import base _MeaninglessOperators = Mapping[complex, Tuple[Type[ast.operator], ...]] _OperatorLimits = Mapping[Type[ast.unaryop], int] @final class UselessOperatorsVisitor(base.BaseNodeVisitor): """Checks operators used in the code.""" _limits: ClassVar[_OperatorLimits] = { ast.UAdd: 0, ast.Invert: 1, ast.Not: 1, ast.USub: 1, } _meaningless_operations: ClassVar[_MeaninglessOperators] = { # ast.Div is not in the list, # since we have a special violation for it. 0: (ast.Mult, ast.Add, ast.Sub, ast.Pow, ast.Mod), # `1` and `-1` are different, `-1` is allowed. 1: (ast.Div, ast.Mult, ast.Pow, ast.Mod), } #: Used to ignore some special cases like `1 / x`: _left_special_cases: ClassVar[_MeaninglessOperators] = { 1: (ast.Div,), } def visit_Num(self, node: ast.Num) -> None: """ Checks numbers unnecessary operators inside the code. Raises: UselessOperatorsViolation """ self._check_operator_count(node) self.generic_visit(node) def visit_BinOp(self, node: ast.BinOp) -> None: """ Visits binary operators. Raises: ZeroDivisionViolation """ self._check_zero_division(node.op, node.right) self._check_useless_math_operator(node.op, node.left, node.right) self.generic_visit(node) def visit_AugAssign(self, node: ast.AugAssign) -> None: """ Visits augmented assigns. Raises: ZeroDivisionViolation """ self._check_zero_division(node.op, node.value) self._check_useless_math_operator(node.op, node.value) self.generic_visit(node) def _check_operator_count(self, node: ast.Num) -> None: for node_type, limit in self._limits.items(): if count_unary_operator(node, node_type) > limit: self.add_violation( consistency.UselessOperatorsViolation( node, text=str(node.n), ), ) def _check_zero_division(self, op: ast.operator, number: ast.AST) -> None: number = unwrap_unary_node(number) is_zero_division = ( isinstance(op, ast.Div) and isinstance(number, ast.Num) and number.n == 0 ) if is_zero_division: self.add_violation(consistency.ZeroDivisionViolation(number)) def _check_useless_math_operator( self, op: ast.operator, left: Optional[ast.AST], right: Optional[ast.AST] = None, ) -> None: if isinstance(left, ast.Num) and left.n in self._left_special_cases: if right and isinstance(op, self._left_special_cases[left.n]): left = None non_negative_numbers = self._get_non_negative_nodes(left, right) for number in non_negative_numbers: forbidden = self._meaningless_operations.get(number.n, None) if forbidden and isinstance(op, forbidden): self.add_violation( consistency.MeaninglessNumberOperationViolation(number), ) def _get_non_negative_nodes( self, left: Optional[ast.AST], right: Optional[ast.AST] = None, ): non_negative_numbers = [] for node in filter(None, (left, right)): real_node = unwrap_unary_node(node) if not isinstance(real_node, ast.Num): continue if real_node.n not in self._meaningless_operations: continue if real_node.n == 1 and walk.is_contained(node, ast.USub): continue non_negative_numbers.append(real_node) return non_negative_numbers @final class WrongMathOperatorVisitor(base.BaseNodeVisitor): """Checks that there are not wrong math operations.""" _string_nodes: ClassVar[AnyNodes] = ( ast.Str, ast.Bytes, ast.JoinedStr, ) _list_nodes: ClassVar[AnyNodes] = ( ast.List, ast.ListComp, ) def visit_BinOp(self, node: ast.BinOp) -> None: """ Visits binary operations. Raises: DoubleMinusOpeationViolation """ self._check_negation(node.op, node.right) self._check_list_multiply(node) self._check_string_concat(node.left, node.op, node.right) self.generic_visit(node) def visit_AugAssign(self, node: ast.AugAssign) -> None: """ Visits augmented assignes. Raises: DoubleMinusOpeationViolation """ self._check_negation(node.op, node.value) self._check_string_concat(node.value, node.op) self.generic_visit(node) def _check_negation(self, op: ast.operator, right: ast.AST) -> None: is_double_minus = ( isinstance(op, (ast.Add, ast.Sub)) and isinstance(right, ast.UnaryOp) and isinstance(right.op, ast.USub) ) if is_double_minus: self.add_violation( consistency.OperationSignNegationViolation(right), ) def _check_list_multiply(self, node: ast.BinOp) -> None: is_list_multiply = ( isinstance(node.op, ast.Mult) and isinstance(node.left, self._list_nodes) ) if is_list_multiply: self.add_violation(ListMultiplyViolation(node.left)) def _check_string_concat( self, left: ast.AST, op: ast.operator, right: Optional[ast.AST] = None, ) -> None: if not isinstance(op, ast.Add): return left_line = getattr(left, 'lineno', 0) if left_line != getattr(right, 'lineno', left_line): # By default we treat nodes that do not have lineno # as nodes on the same line. return for node in (left, right): if isinstance(node, self._string_nodes): self.add_violation( consistency.ExplicitStringConcatViolation(node), ) return PK!W&&3wemake_python_styleguide/visitors/ast/statements.py# -*- coding: utf-8 -*- import ast from typing import ClassVar, Mapping, Optional, Sequence, Union from typing_extensions import final from wemake_python_styleguide.compat.aliases import ForNodes, FunctionNodes from wemake_python_styleguide.logic.collections import normalize_dict_elements from wemake_python_styleguide.logic.functions import get_all_arguments from wemake_python_styleguide.logic.nodes import get_parent from wemake_python_styleguide.logic.strings import is_doc_string from wemake_python_styleguide.types import ( AnyFor, AnyFunctionDef, AnyNodes, AnyWith, ) from wemake_python_styleguide.violations.best_practices import ( StatementHasNoEffectViolation, UnreachableCodeViolation, ) from wemake_python_styleguide.violations.consistency import ( ParametersIndentationViolation, UselessNodeViolation, ) from wemake_python_styleguide.violations.refactoring import ( PointlessStarredViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor from wemake_python_styleguide.visitors.decorators import alias StatementWithBody = Union[ ast.If, AnyFor, ast.While, AnyWith, ast.Try, ast.ExceptHandler, AnyFunctionDef, ast.ClassDef, ast.Module, ] AnyCollection = Union[ ast.List, ast.Set, ast.Dict, ast.Tuple, ] @final @alias('visit_statement_with_body', ( 'visit_If', 'visit_For', 'visit_AsyncFor', 'visit_While', 'visit_With', 'visit_AsyncWith', 'visit_Try', 'visit_ExceptHandler', 'visit_FunctionDef', 'visit_AsyncFunctionDef', 'visit_ClassDef', 'visit_Module', )) class StatementsWithBodiesVisitor(BaseNodeVisitor): """ Responsible for restricting incorrect patterns and members inside bodies. This visitor checks all statements that have multiline bodies. """ _closing_nodes: ClassVar[AnyNodes] = ( ast.Raise, ast.Return, ast.Break, ast.Continue, ) _have_doc_strings: ClassVar[AnyNodes] = ( *FunctionNodes, ast.ClassDef, ast.Module, ) _nodes_with_orelse = ( ast.If, *ForNodes, ast.While, ast.Try, ) _have_effect: ClassVar[AnyNodes] = ( ast.Return, ast.YieldFrom, ast.Yield, ast.Raise, ast.Break, ast.Continue, ast.Call, ast.Await, ast.Nonlocal, ast.Global, ast.Delete, ast.Pass, ast.Assert, ) # Useless nodes: _generally_useless_body: ClassVar[AnyNodes] = ( ast.Break, ast.Continue, ast.Pass, ast.Ellipsis, ) _loop_useless_body: ClassVar[AnyNodes] = ( ast.Return, ast.Raise, ) _useless_combination: ClassVar[Mapping[str, AnyNodes]] = { 'For': _generally_useless_body + _loop_useless_body, 'AsyncFor': _generally_useless_body + _loop_useless_body, 'While': _generally_useless_body + _loop_useless_body, 'Try': _generally_useless_body + (ast.Raise,), 'With': _generally_useless_body, 'AsyncWith': _generally_useless_body, } def visit_statement_with_body(self, node: StatementWithBody) -> None: """ Visits statement's body internals. Raises: UnreachableCodeViolation, UselessNodeViolation """ self._check_internals(node.body) if isinstance(node, self._nodes_with_orelse): self._check_internals(node.orelse) if isinstance(node, ast.Try): self._check_internals(node.finalbody) self._check_useless_node(node, node.body) self.generic_visit(node) def _check_useless_node( self, node: StatementWithBody, body: Sequence[ast.stmt], ) -> None: if len(body) != 1: return forbiden = self._useless_combination.get( node.__class__.__qualname__, None, ) if not forbiden or not isinstance(body[0], forbiden): return self.add_violation( UselessNodeViolation( node, text=node.__class__.__qualname__.lower(), ), ) def _check_expression( self, node: ast.Expr, is_first: bool = False, ) -> None: if isinstance(node.value, self._have_effect): return if is_first and is_doc_string(node): if isinstance(get_parent(node), self._have_doc_strings): return self.add_violation(StatementHasNoEffectViolation(node)) def _check_internals(self, body: Sequence[ast.stmt]) -> None: after_closing_node = False for index, statement in enumerate(body): if after_closing_node: self.add_violation(UnreachableCodeViolation(statement)) if isinstance(statement, self._closing_nodes): after_closing_node = True if isinstance(statement, ast.Expr): self._check_expression(statement, is_first=index == 0) @final @alias('visit_collection', ( 'visit_List', 'visit_Set', 'visit_Dict', 'visit_Tuple', )) @alias('visit_any_function', ( 'visit_FunctionDef', 'visit_AsyncFunctionDef', )) class WrongParametersIndentationVisitor(BaseNodeVisitor): """Ensures that all parameters indentation follow our rules.""" def visit_collection(self, node: AnyCollection) -> None: """Checks how collection items indentation.""" if isinstance(node, ast.Dict): elements = normalize_dict_elements(node) else: elements = node.elts self._check_indentation(node, elements, extra_lines=1) self.generic_visit(node) def visit_Call(self, node: ast.Call) -> None: """Checks call arguments indentation.""" all_args = [*node.args, *[kw.value for kw in node.keywords]] self._check_indentation(node, all_args) self.generic_visit(node) def visit_any_function(self, node: AnyFunctionDef) -> None: """Checks function parameters indentation.""" self._check_indentation(node, get_all_arguments(node)) self.generic_visit(node) def visit_ClassDef(self, node: ast.ClassDef) -> None: """Checks base classes indentation.""" all_args = [*node.bases, *[kw.value for kw in node.keywords]] self._check_indentation(node, all_args) self.generic_visit(node) def _check_first_element( self, node: ast.AST, statement: ast.AST, extra_lines: int, ) -> Optional[bool]: if statement.lineno == node.lineno and not extra_lines: return False return None def _check_rest_elements( self, node: ast.AST, statement: ast.AST, previous_line: int, multi_line_mode: Optional[bool], ) -> Optional[bool]: previous_has_break = previous_line != statement.lineno if not previous_has_break and multi_line_mode: self.add_violation(ParametersIndentationViolation(node)) return None elif previous_has_break and multi_line_mode is False: self.add_violation(ParametersIndentationViolation(node)) return None return previous_has_break def _check_indentation( self, node: ast.AST, elements: Sequence[ast.AST], extra_lines: int = 0, # we need it due to wrong lineno in collections ) -> None: multi_line_mode: Optional[bool] = None for index, statement in enumerate(elements): if index == 0: # We treat first element differently, # since it is impossible to say what kind of multi-line # parameters styles will be used at this moment. multi_line_mode = self._check_first_element( node, statement, extra_lines, ) else: multi_line_mode = self._check_rest_elements( node, statement, elements[index - 1].lineno, multi_line_mode, ) @final class PointlessStarredVisitor(BaseNodeVisitor): """Responsible for absence of useless starred expressions.""" _pointless_star_nodes: ClassVar[AnyNodes] = ( ast.Dict, ast.List, ast.Set, ast.Tuple, ) def visit_Call(self, node: ast.Call) -> None: """Checks useless call arguments.""" self._check_starred_args(node.args) self._check_double_starred_dict(node.keywords) self.generic_visit(node) def _check_starred_args( self, args: Sequence[ast.AST], ) -> None: for node in args: if isinstance(node, ast.Starred): if self._is_pointless_star(node.value): self.add_violation(PointlessStarredViolation(node)) def _check_double_starred_dict( self, keywords: Sequence[ast.keyword], ) -> None: for keyword in keywords: if keyword.arg is not None: continue complex_keys = self._has_non_string_keys(keyword) pointless_args = self._is_pointless_star(keyword.value) if not complex_keys and pointless_args: self.add_violation(PointlessStarredViolation(keyword.value)) def _is_pointless_star(self, node: ast.AST) -> bool: return isinstance(node, self._pointless_star_nodes) def _has_non_string_keys(self, node: ast.keyword) -> bool: if not isinstance(node.value, ast.Dict): return True for key_node in node.value.keys: if not isinstance(key_node, ast.Str): return True return False PK!!__)wemake_python_styleguide/visitors/base.py# -*- coding: utf-8 -*- """ Contains detailed technical documentation about how to write a :term:`visitor`. See also: Visitor is a well-known software engineering pattern: https://en.wikipedia.org/wiki/Visitor_pattern Each visitor might work with one or many :term:`violations `. Multiple visitors might one with the same violation. .. mermaid:: :caption: Visitor relation with violations. graph TD V1[Visitor 1] --> EA[Violation A] V1[Visitor 1] --> EB[Violation B] V2[Visitor 2] --> EA[Violation A] V2[Visitor 2] --> EC[Violation C] V3[Visitor 3] --> EZ[Violation WPS] .. _visitors: Visitors API ------------ .. currentmodule:: wemake_python_styleguide.visitors.base .. autoclasstree:: wemake_python_styleguide.visitors.base .. autosummary:: :nosignatures: BaseNodeVisitor BaseFilenameVisitor BaseTokenVisitor The decision relies on what parameters do you need for the task. It is highly unlikely that you will need two parameters at the same time. See :ref:`tutorial` for more information about choosing a correct base class. Conventions ~~~~~~~~~~~ Then you will have to write logic for your visitor. We follow these conventions: - Public visitor methods start with ``visit_``, than comes the name of a token or node to be visited - All other methods and attributes should be protected - We try to separate as much logic from ``visit_`` methods as possible, so they only route for callbacks that actually executes the checks - We place repeating logic into ``logic/`` package to be able to reuse it There are different example of visitors in this project already. Reference ~~~~~~~~~ """ import ast import tokenize from typing import List, Sequence, Type from typing_extensions import final from wemake_python_styleguide import constants from wemake_python_styleguide.logic.filenames import get_stem from wemake_python_styleguide.types import ConfigurationOptions from wemake_python_styleguide.violations.base import BaseViolation class BaseVisitor(object): """ Abstract base class for different types of visitors. Attributes: options: contains the options objects passed and parsed by ``flake8``. filename: filename passed by ``flake8``, each visitor has a file name. violations: list of :term:`violations ` for the specific visitor. """ def __init__( self, options: ConfigurationOptions, filename: str = constants.STDIN, ) -> None: """Creates base visitor instance.""" self.options = options self.filename = filename self.violations: List[BaseViolation] = [] @classmethod def from_checker( cls: Type['BaseVisitor'], checker, ) -> 'BaseVisitor': """ Constructs visitor instance from the checker. Each unique visitor class should know how to construct itself from the :term:`checker` instance. Generally speaking, each visitor class needs to eject required parameters from checker and then run its constructor with these parameters. """ return cls(options=checker.options, filename=checker.filename) @final def add_violation(self, violation: BaseViolation) -> None: """Adds violation to the visitor.""" self.violations.append(violation) def run(self) -> None: """ Abstract method to run a visitor. Each visitor should know what exactly it needs to do when it was told to ``run``. This method should be defined in all subclasses. """ raise NotImplementedError('Should be defined in a subclass') def _post_visit(self) -> None: """ Executed after all nodes have been visited. This method is useful for counting statistics, etc. By default does nothing. """ class BaseNodeVisitor(ast.NodeVisitor, BaseVisitor): """ Allows to store violations while traversing node tree. This class should be used as a base class for all ``ast`` based checkers. Method ``visit()`` is defined in ``NodeVisitor`` class. Attributes: tree: ``ast`` tree to be checked. """ def __init__( self, options: ConfigurationOptions, tree: ast.AST, **kwargs, ) -> None: """Creates new ``ast`` based instance.""" super().__init__(options, **kwargs) self.tree = tree @final @classmethod def from_checker( cls: Type['BaseNodeVisitor'], checker, ) -> 'BaseNodeVisitor': """Constructs visitor instance from the checker.""" return cls( options=checker.options, filename=checker.filename, tree=checker.tree, ) @final def run(self) -> None: """Recursively visits all ``ast`` nodes. Then executes post hook.""" self.visit(self.tree) self._post_visit() class BaseFilenameVisitor(BaseVisitor): """ Abstract base class that allows to visit and check module file names. Has ``visit_filename()`` method that should be defined in subclasses. Attributes: stem: the last part of the filename. Does not contain extension. """ stem: str def visit_filename(self) -> None: """ Abstract method to check module file names. This method should be overridden in a subclass. """ raise NotImplementedError('Should be defined in a subclass') @final def run(self) -> None: """ Checks module's filename. Skips modules that are checked as piped output. Since these modules are checked as a ``stdin`` input. And do not have names. """ if self.filename != constants.STDIN: self.stem = get_stem(self.filename) self.visit_filename() self._post_visit() class BaseTokenVisitor(BaseVisitor): """ Allows to check ``tokenize`` sequences. Attributes: file_tokens: ``tokenize.TokenInfo`` sequence to be checked. """ def __init__( self, options: ConfigurationOptions, file_tokens: Sequence[tokenize.TokenInfo], **kwargs, ) -> None: """Creates new ``tokenize`` based visitor instance.""" super().__init__(options, **kwargs) self.file_tokens = file_tokens @final @classmethod def from_checker( cls: Type['BaseTokenVisitor'], checker, ) -> 'BaseTokenVisitor': """Constructs ``tokenize`` based visitor instance from the checker.""" return cls( options=checker.options, filename=checker.filename, file_tokens=checker.file_tokens, ) def visit(self, token: tokenize.TokenInfo) -> None: """ Runs custom defined handlers in a visitor for each specific token type. Uses ``.exact_type`` property to fetch the token name. So, you have to be extra careful with tokens like ``->`` and other operators, since they might resolve in just ``OP`` name. Does nothing if handler for any token type is not defined. Inspired by ``NodeVisitor`` class. See also: https://docs.python.org/3/library/tokenize.html """ token_type = tokenize.tok_name[token.exact_type].lower() method = getattr(self, 'visit_{0}'.format(token_type), None) if method is not None: method(token) @final def run(self) -> None: """Visits all token types that have a handler method.""" for token in self.file_tokens: self.visit(token) self._post_visit() PK!xnn/wemake_python_styleguide/visitors/decorators.py# -*- coding: utf-8 -*- from typing import Callable, Tuple def alias( original: str, aliases: Tuple[str, ...], ) -> Callable[[type], type]: """ Decorator to alias handlers. Why do we need it? Because there are cases when we need to use the same method to handle different nodes types. We can just create aliases like ``visit_Import = visit_ImportFrom``, but it looks verbose and ugly. """ all_names = aliases + (original, ) if len(all_names) != len(set(all_names)): raise ValueError('Found duplicate aliases') def decorator(cls: type) -> type: original_handler = getattr(cls, original, None) if original_handler is None: raise AttributeError('Aliased attribute {0} does not exist'.format( original, )) for method_alias in aliases: if getattr(cls, method_alias, None): raise AttributeError( 'Alias {0} already exists'.format(method_alias), ) setattr(cls, method_alias, original_handler) return cls return decorator PK!uh7wemake_python_styleguide/visitors/filenames/__init__.py# -*- coding: utf-8 -*- PK!0- 5wemake_python_styleguide/visitors/filenames/module.py# -*- coding: utf-8 -*- from typing_extensions import final from wemake_python_styleguide import constants from wemake_python_styleguide.logic.naming import access, logical from wemake_python_styleguide.violations.naming import ( ConsecutiveUnderscoresInNameViolation, PrivateNameViolation, TooLongNameViolation, TooShortNameViolation, UnderscoredNumberNameViolation, UnicodeNameViolation, WrongModuleMagicNameViolation, WrongModuleNamePatternViolation, WrongModuleNameViolation, ) from wemake_python_styleguide.visitors.base import BaseFilenameVisitor @final class WrongModuleNameVisitor(BaseFilenameVisitor): """Checks that modules have correct names.""" def visit_filename(self) -> None: """ Checks a single module's filename. Raises: TooShortModuleNameViolation WrongModuleMagicNameViolation WrongModuleNameViolation WrongModuleNamePatternViolation WrongModuleNameUnderscoresViolation UnderscoredNumberNameViolation TooLongNameViolation """ self._check_module_name() self._check_module_name_length() self._check_module_name_pattern() def _check_module_name(self) -> None: if logical.is_wrong_name(self.stem, constants.MODULE_NAMES_BLACKLIST): self.add_violation(WrongModuleNameViolation()) if access.is_magic(self.stem): if self.stem not in constants.MAGIC_MODULE_NAMES_WHITELIST: self.add_violation(WrongModuleMagicNameViolation()) if access.is_private(self.stem): self.add_violation(PrivateNameViolation(text=self.stem)) if logical.does_contain_unicode(self.stem): self.add_violation(UnicodeNameViolation(text=self.stem)) def _check_module_name_length(self) -> None: min_length = self.options.min_name_length if logical.is_too_short_name(self.stem, min_length=min_length): self.add_violation(TooShortNameViolation(text=self.stem)) elif not constants.MODULE_NAME_PATTERN.match(self.stem): self.add_violation(WrongModuleNamePatternViolation()) max_length = self.options.max_name_length if logical.is_too_long_name(self.stem, max_length=max_length): self.add_violation(TooLongNameViolation(text=self.stem)) def _check_module_name_pattern(self) -> None: if logical.does_contain_consecutive_underscores(self.stem): self.add_violation( ConsecutiveUnderscoresInNameViolation(text=self.stem), ) if logical.does_contain_underscored_number(self.stem): self.add_violation(UnderscoredNumberNameViolation(text=self.stem)) PK!uh6wemake_python_styleguide/visitors/tokenize/__init__.py# -*- coding: utf-8 -*- PK!Mmm6wemake_python_styleguide/visitors/tokenize/comments.py# -*- coding: utf-8 -*- r""" Disallows to use incorrect magic comments. That's how a basic ``comment`` type token looks like: .. code:: python TokenInfo( type=57 (COMMENT), string='# noqa: WPS100', start=(1, 4), end=(1, 16), line="u'' # noqa: WPS100\n", ) All comments have the same type. """ import re import tokenize from typing import ClassVar, FrozenSet from typing.re import Pattern from typing_extensions import final from wemake_python_styleguide.constants import ( MAX_NO_COVER_COMMENTS, MAX_NOQA_COMMENTS, ) from wemake_python_styleguide.logic.tokens import get_comment_text from wemake_python_styleguide.violations.best_practices import ( OveruseOfNoCoverCommentViolation, OveruseOfNoqaCommentViolation, WrongDocCommentViolation, WrongMagicCommentViolation, ) from wemake_python_styleguide.violations.consistency import ( EmptyLineAfterCodingViolation, ) from wemake_python_styleguide.visitors.base import BaseTokenVisitor @final class WrongCommentVisitor(BaseTokenVisitor): """Checks comment tokens.""" _no_cover: ClassVar[Pattern] = re.compile(r'^pragma:\s+no\s+cover') _noqa_check: ClassVar[Pattern] = re.compile(r'^noqa:?($|[A-Z\d\,\s]+)') _type_check: ClassVar[Pattern] = re.compile( r'^type:\s?([\w\d\[\]\'\"\.]+)$', ) def __init__(self, *args, **kwargs) -> None: """Initializes a counter.""" super().__init__(*args, **kwargs) self._noqa_count = 0 self._no_cover_count = 0 def visit_comment(self, token: tokenize.TokenInfo) -> None: """ Performs comment checks. Raises: OveruseOfNoqaCommentViolation WrongDocCommentViolation WrongMagicCommentViolation """ self._check_noqa(token) self._check_typed_ast(token) self._check_empty_doc_comment(token) self._check_cover_comments(token) def _check_noqa(self, token: tokenize.TokenInfo) -> None: comment_text = get_comment_text(token) match = self._noqa_check.match(comment_text) if not match: return self._noqa_count += 1 excludes = match.groups()[0].strip() if not excludes: # We cannot pass the actual line here, # since it will be ignored due to `# noqa` comment: self.add_violation(WrongMagicCommentViolation(text=comment_text)) def _check_typed_ast(self, token: tokenize.TokenInfo) -> None: comment_text = get_comment_text(token) match = self._type_check.match(comment_text) if not match: return declared_type = match.groups()[0].strip() if declared_type != 'ignore': self.add_violation( WrongMagicCommentViolation(token, text=comment_text), ) def _check_empty_doc_comment(self, token: tokenize.TokenInfo) -> None: if get_comment_text(token) == ':': self.add_violation(WrongDocCommentViolation(token)) def _check_cover_comments(self, token: tokenize.TokenInfo) -> None: comment_text = get_comment_text(token) match = self._no_cover.match(comment_text) if not match: return self._no_cover_count += 1 def _post_visit(self) -> None: if self._noqa_count > MAX_NOQA_COMMENTS: self.add_violation( OveruseOfNoqaCommentViolation(text=str(self._noqa_count)), ) if self._no_cover_count > MAX_NO_COVER_COMMENTS: self.add_violation( OveruseOfNoCoverCommentViolation( text=str(self._no_cover_count), ), ) @final class FileMagicCommentsVisitor(BaseTokenVisitor): """Checks comments for the whole file.""" _allowed_newlines: ClassVar[FrozenSet[int]] = frozenset(( tokenize.NL, tokenize.NEWLINE, tokenize.ENDMARKER, )) def visit_comment(self, token: tokenize.TokenInfo) -> None: """ Checks special comments that are magic per each file. Raises: EmptyLineAfterCoddingViolation """ self._check_empty_line_after_codding(token) def _offset_for_comment_line(self, token: tokenize.TokenInfo) -> int: if token.exact_type == tokenize.COMMENT: return 2 return 0 def _check_empty_line_after_codding( self, token: tokenize.TokenInfo, ) -> None: """ Checks that we have a blank line after the magic comments. PEP-263 says: a magic comment must be placed into the source files either as first or second line in the file See also: https://www.python.org/dev/peps/pep-0263/ """ if token.start == (1, 0): tokens = iter(self.file_tokens[self.file_tokens.index(token):]) available_offset = 2 # comment + newline while True: next_token = next(tokens) if not available_offset: available_offset = self._offset_for_comment_line( next_token, ) if available_offset > 0: available_offset -= 1 continue if next_token.exact_type not in self._allowed_newlines: self.add_violation(EmptyLineAfterCodingViolation(token)) break PK!<8wemake_python_styleguide/visitors/tokenize/conditions.py# -*- coding: utf-8 -*- import tokenize from typing import ClassVar, FrozenSet from typing_extensions import final from wemake_python_styleguide.violations.refactoring import ( ImplicitElifViolation, ) from wemake_python_styleguide.visitors.base import BaseTokenVisitor @final class IfElseVisitor(BaseTokenVisitor): """ Checks if tokens tokens. We use ``tokenize`` instead of ``ast`` because .. code:: python if some: ... else: if other: ... has the same ``ast`` representation as: .. code:: python if some: ... elif other: ... That's why we have to use ``tokenize`` to find the raw tokens inside the text. """ _allowed_token_types: ClassVar[FrozenSet[int]] = frozenset(( tokenize.NEWLINE, tokenize.NL, tokenize.COLON, tokenize.INDENT, )) def visit_name(self, token: tokenize.TokenInfo) -> None: """ Checks that ``if`` nodes are defined correctly. Raises: ImplicitElifViolation """ self._check_implicit_elif(token) def _check_implicit_elif(self, token: tokenize.TokenInfo) -> None: if token.string != 'else': return index = self.file_tokens.index(token) # There's a bug in coverage, I am not sure how to make it work. for next_token in self.file_tokens[index + 1:]: # pragma: no cover if next_token.exact_type in self._allowed_token_types: continue elif next_token.string == 'if': self.add_violation(ImplicitElifViolation(next_token)) return PK!A6wemake_python_styleguide/visitors/tokenize/keywords.py# -*- coding: utf-8 -*- import keyword import tokenize from typing_extensions import final from wemake_python_styleguide.violations.consistency import ( MissingSpaceBetweenKeywordAndParenViolation, ) from wemake_python_styleguide.visitors.base import BaseTokenVisitor @final class WrongKeywordTokenVisitor(BaseTokenVisitor): """Visits keywords and finds violations related to their usage.""" def visit_name(self, token: tokenize.TokenInfo) -> None: """ Check keywords related rules. Raises: MissingSpaceBetweenKeywordAndParenViolation """ if keyword.iskeyword(token.string): self._check_space_before_open_paren(token) def _check_space_before_open_paren(self, token: tokenize.TokenInfo) -> None: if token.line[token.end[1]:].startswith('('): self.add_violation( MissingSpaceBetweenKeywordAndParenViolation(token), ) PK!yTC("("8wemake_python_styleguide/visitors/tokenize/primitives.py# -*- coding: utf-8 -*- import re import tokenize from typing import ClassVar, FrozenSet, Optional from typing.re import Pattern from flake8_quotes.docstring_detection import get_docstring_tokens from typing_extensions import final from wemake_python_styleguide.logic.tokens import ( has_triple_string_quotes, split_prefixes, ) from wemake_python_styleguide.violations.best_practices import ( WrongUnicodeEscapeViolation, ) from wemake_python_styleguide.violations.consistency import ( BadComplexNumberSuffixViolation, BadNumberSuffixViolation, ImplicitRawStringViolation, ImplicitStringConcatenationViolation, NumberWithMeaninglessZeroViolation, PartialFloatViolation, PositiveExponentViolation, UnderscoredNumberViolation, UnicodeStringViolation, UppercaseStringModifierViolation, WrongHexNumberCaseViolation, WrongMultilineStringViolation, ) from wemake_python_styleguide.visitors.base import BaseTokenVisitor def _replace_braces(string: str) -> str: if string.startswith('"'): return string.lstrip('"').rstrip('"') return string.lstrip("'").rstrip("'") @final class WrongNumberTokenVisitor(BaseTokenVisitor): """Visits number tokens to find incorrect usages.""" _bad_number_suffixes: ClassVar[Pattern] = re.compile( r'^[0-9\.]+[BOXE]', ) _leading_zero_pattern: ClassVar[Pattern] = re.compile( r'^[0-9\.]+([box]|e\+?\-?)0.+', re.IGNORECASE | re.ASCII, ) _leading_zero_float_pattern: ClassVar[Pattern] = re.compile( r'^[0-9]*\.[0-9]+0+$', ) _positive_exponent_pattens: ClassVar[Pattern] = re.compile( r'^[0-9\.]+e\+', re.IGNORECASE | re.ASCII, ) _bad_hex_numbers: ClassVar[FrozenSet[str]] = frozenset(( 'a', 'b', 'c', 'd', 'e', 'f', )) _bad_complex_suffix: ClassVar[str] = 'J' def visit_number(self, token: tokenize.TokenInfo) -> None: """ Checks number declarations. Raises: UnderscoredNumberViolation PartialFloatViolation BadNumberSuffixViolation NumberWithMeaninglessZeroViolation PositiveExponentViolation Regressions: https://github.com/wemake-services/wemake-python-styleguide/issues/557 """ self._check_complex_suffix(token) self._check_underscored_number(token) self._check_partial_float(token) self._check_bad_number_suffixes(token) def _check_complex_suffix(self, token: tokenize.TokenInfo) -> None: if self._bad_complex_suffix in token.string: self.add_violation( BadComplexNumberSuffixViolation( token, text=self._bad_complex_suffix, ), ) def _check_underscored_number(self, token: tokenize.TokenInfo) -> None: if '_' in token.string: self.add_violation( UnderscoredNumberViolation(token, text=token.string), ) def _check_partial_float(self, token: tokenize.TokenInfo) -> None: if token.string.startswith('.') or token.string.endswith('.'): self.add_violation(PartialFloatViolation(token, text=token.string)) def _check_bad_number_suffixes(self, token: tokenize.TokenInfo) -> None: if self._bad_number_suffixes.match(token.string): self.add_violation( BadNumberSuffixViolation(token, text=token.string), ) float_zeros = self._leading_zero_float_pattern.match(token.string) other_zeros = self._leading_zero_pattern.match(token.string) if float_zeros or other_zeros: self.add_violation( NumberWithMeaninglessZeroViolation(token, text=token.string), ) if self._positive_exponent_pattens.match(token.string): self.add_violation( PositiveExponentViolation(token, text=token.string), ) if token.string.startswith('0x') or token.string.startswith('0X'): has_wrong_hex_numbers = any( char in self._bad_hex_numbers for char in token.string ) if has_wrong_hex_numbers: self.add_violation( WrongHexNumberCaseViolation(token, text=token.string), ) @final class WrongStringTokenVisitor(BaseTokenVisitor): """Checks incorrect string tokens usages.""" _bad_string_modifiers: ClassVar[FrozenSet[str]] = frozenset(( 'R', 'F', 'B', 'U', )) _unicode_escapes: ClassVar[FrozenSet[str]] = frozenset(( 'u', 'U', 'N', )) _implicit_raw_strigns: ClassVar[Pattern] = re.compile(r'\\{2}.+') def __init__(self, *args, **kwargs) -> None: """Initializes new visitor and saves all docstrings.""" super().__init__(*args, **kwargs) self._docstrings = get_docstring_tokens(self.file_tokens) def visit_string(self, token: tokenize.TokenInfo) -> None: """ Finds incorrect string usages. ``u`` can only be the only prefix. You can not combine it with ``r``, ``b``, or ``f``. Since it will raise a ``SyntaxError`` while parsing. Raises: UnicodeStringViolation WrongMultilineStringViolation ImplicitRawStringViolation WrongUnicodeEscapeViolation """ self._check_correct_multiline(token) self._check_string_modifiers(token) self._check_implicit_raw_string(token) self._check_wrong_unicode_escape(token) def _check_correct_multiline(self, token: tokenize.TokenInfo) -> None: _, string_def = split_prefixes(token) if has_triple_string_quotes(string_def): if '\n' not in string_def and token not in self._docstrings: self.add_violation(WrongMultilineStringViolation(token)) def _check_string_modifiers(self, token: tokenize.TokenInfo) -> None: modifiers, _ = split_prefixes(token) if 'u' in modifiers.lower(): self.add_violation( UnicodeStringViolation(token, text=token.string), ) for mod in modifiers: if mod in self._bad_string_modifiers: self.add_violation( UppercaseStringModifierViolation(token, text=mod), ) def _check_implicit_raw_string(self, token: tokenize.TokenInfo) -> None: modifiers, string_def = split_prefixes(token) if 'r' in modifiers.lower(): return if self._implicit_raw_strigns.search(_replace_braces(string_def)): self.add_violation( ImplicitRawStringViolation(token, text=token.string), ) def _check_wrong_unicode_escape(self, token: tokenize.TokenInfo) -> None: # See: http://docs.python.org/reference/lexical_analysis.html modifiers, string_body = split_prefixes(token) index = 0 while True: index = string_body.find('\\', index) if index == -1: break next_char = string_body[index + 1] if 'b' in modifiers.lower() and next_char in self._unicode_escapes: self.add_violation( WrongUnicodeEscapeViolation(token, text=token.string), ) # Whether it was a valid escape or not, backslash followed by # another character can always be consumed whole: the second # character can never be the start of a new backslash escape. index += 2 @final class WrongStringConcatenationVisitor(BaseTokenVisitor): """Checks incorrect string concatenation.""" _ignored_tokens: ClassVar[FrozenSet[int]] = frozenset(( tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, )) def __init__(self, *args, **kwargs) -> None: """Adds extra ``_previous_token`` property.""" super().__init__(*args, **kwargs) self._previous_token: Optional[tokenize.TokenInfo] = None def visit(self, token: tokenize.TokenInfo) -> None: """ Ensures that all string are concatenated as we allow. Raises: ImplicitStringConcatenationViolation """ self._check_concatenation(token) def _check_concatenation(self, token: tokenize.TokenInfo) -> None: if token.exact_type in self._ignored_tokens: return if token.exact_type == tokenize.STRING: if self._previous_token: self.add_violation(ImplicitStringConcatenationViolation(token)) self._previous_token = token else: self._previous_token = None PK!Rq8wemake_python_styleguide/visitors/tokenize/statements.py# -*- coding: utf-8 -*- import tokenize import types from collections import defaultdict from typing import ( ClassVar, DefaultDict, Dict, FrozenSet, List, Mapping, Sequence, Tuple, ) from typing_extensions import final from wemake_python_styleguide.logic.tokens import only_contains from wemake_python_styleguide.violations.consistency import ( ExtraIndentationViolation, WrongBracketPositionViolation, ) from wemake_python_styleguide.visitors.base import BaseTokenVisitor TokenLines = DefaultDict[int, List[tokenize.TokenInfo]] MATCHING: Mapping[int, int] = types.MappingProxyType({ tokenize.LBRACE: tokenize.RBRACE, tokenize.LSQB: tokenize.RSQB, tokenize.LPAR: tokenize.RPAR, }) ALLOWED_EMPTY_LINE_TOKENS: FrozenSet[int] = frozenset(( tokenize.NL, tokenize.NEWLINE, *MATCHING.values(), )) def _get_reverse_bracket(bracket: tokenize.TokenInfo) -> int: index = list(MATCHING.values()).index(bracket.exact_type) return list(MATCHING.keys())[index] @final class ExtraIndentationVisitor(BaseTokenVisitor): """ Is used to find extra indentation in nodes. Algorithm: 1. goes through all nodes in a module 2. remembers minimal indentation for each line 3. compares each two closest lines: indentation should not be >4 """ _ignored_tokens: ClassVar[Tuple[int, ...]] = ( tokenize.NEWLINE, ) _ignored_previous_token: ClassVar[Tuple[int, ...]] = ( tokenize.NL, ) def __init__(self, *args, **kwargs) -> None: """Creates empty counter.""" super().__init__(*args, **kwargs) self._offsets: Dict[int, tokenize.TokenInfo] = {} def visit(self, token: tokenize.TokenInfo) -> None: """ Goes through all tokens to find wrong indentation. Raises: ExtraIndentationViolation """ self._check_extra_indentation(token) def _check_extra_indentation(self, token: tokenize.TokenInfo) -> None: lineno, _offset = token.start if lineno not in self._offsets: self._offsets[lineno] = token def _get_token_offset(self, token: tokenize.TokenInfo) -> int: if token.exact_type == tokenize.INDENT: return token.end[1] return token.start[1] def _check_individual_line( self, lines: Sequence[int], line: int, index: int, ) -> None: current_token = self._offsets[line] if current_token.exact_type in self._ignored_tokens: return previous_token = self._offsets[lines[index - 1]] if previous_token.exact_type in self._ignored_previous_token: return offset = self._get_token_offset(current_token) previous_offset = self._get_token_offset(previous_token) if offset > previous_offset + 4: self.add_violation(ExtraIndentationViolation(current_token)) def _post_visit(self) -> None: lines = sorted(self._offsets.keys()) for index, line in enumerate(lines): if index == 0 or line != lines[index - 1] + 1: continue self._check_individual_line(lines, line, index) @final class BracketLocationVisitor(BaseTokenVisitor): """ Finds closing brackets location. We check that brackets can be on the same line or brackets can be the only tokens on the line. We track all kind of brackets: round, square, and curly. """ def __init__(self, *args, **kwargs) -> None: """Creates line tracking for tokens.""" super().__init__(*args, **kwargs) self._lines: TokenLines = defaultdict(list) def visit(self, token: tokenize.TokenInfo) -> None: """ Goes trough all tokens to separate them by line numbers. Raises: WrongBracketPositionViolation """ self._lines[token.start[0]].append(token) def _annotate_brackets( self, tokens: List[tokenize.TokenInfo], ) -> Dict[int, int]: """Annotates each opening bracket with the nested level index.""" brackets = {bracket: 0 for bracket in MATCHING} for token in tokens: if token.exact_type in MATCHING.keys(): brackets[token.exact_type] += 1 if token.exact_type in MATCHING.values(): reverse_bracket = _get_reverse_bracket(token) if brackets[reverse_bracket] > 0: brackets[reverse_bracket] -= 1 return brackets def _check_closing( self, token: tokenize.TokenInfo, index: int, tokens: List[tokenize.TokenInfo], ) -> None: tokens_before = tokens[:index] annotated = self._annotate_brackets(tokens_before) if annotated[_get_reverse_bracket(token)] == 0: if not only_contains(tokens_before, ALLOWED_EMPTY_LINE_TOKENS): self.add_violation(WrongBracketPositionViolation(token)) def _check_individual_line(self, tokens: List[tokenize.TokenInfo]) -> None: for index, token in enumerate(tokens): if token.exact_type in MATCHING.values(): self._check_closing(token, index, tokens) def _post_visit(self) -> None: for _, tokens in self._lines.items(): self._check_individual_line(tokens) PK!H ^:wemake_python_styleguide-0.12.4.dist-info/entry_points.txtNINK(I+ϋ -O TdT椦f%g&gY9Ch.hEE%\ H/M,)VsqPK!f001wemake_python_styleguide-0.12.4.dist-info/LICENSEMIT License Copyright (c) 2018 wemake.services Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HڽTU/wemake_python_styleguide-0.12.4.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H "2wemake_python_styleguide-0.12.4.dist-info/METADATAYr)z3I<^X=,hؙQM )t-%O'#!q(˲2,[}Ǿ>Y `'-gǪR1(oWJ F6QEp$>R S}dhkʳa=L1# REfٱ0`Ym4S[@j9Pl̀ȶ堒l;B,E,E +YQ`KM.kVm8_>:r$,a#cEߟXD7SoJp,)tī־>|'O8FXuk1eLWq uz/cMc aϼ67Viz>] Zmz:ٜ}v e .4HTCb=\%pm6[ZFʨ<[OUOdUDW\"i $_yUo]gtar hVQ6қ]6ڈLZB(fI)KO c!zM;#58Qay;UK{?0U oR&^#bV+^MC6X(R&󡵱3&_YA2Y)Q2>f_ C>|P(xai䞛 7] ,CK^\3\DnfΩ(/)TQ@VOF9*54 X 7~8))OBӕ"M(UDWՙD fHy[뀹T,5e`qu;ϒŵ67MnhXe'QM|O[ 6gfsۧ'ton5{;o6o:ś_:э{v P? _ҽx̓Ơ~L^>>98fsxc~[)zo;;7[vޙíǿfqsv1-{$K[`FKd"ϑC5:,Ri Qi:׉z14IW ct".;1@P־lS\oExDcdN"[#qr#SD Raa[Q3`7+{Y:4$6_q6Ҋ,K>3|!E.j5ދٻMa<z$[_@/ ƞAftpɣY'bZ YP(H >< P0j hk1i,t8;u+͜EVK9 sBVvfH 7)rMV|RQI1a{TWur4KhaDC/t-nPhA.sR/_USX& H 8ka<:Iu$9'<9 ݑIw} |)$e]ta#-[ėc^jiHӻS'Ot+| nFl[^f TމLZePm  8c?O)1_`tf/GEYʉn {ߍ'] <1C̑(3TNg[?M0qm?Yu1 a3GtR5'yn@E^hr kɱ/f[\ǔ3j|BD z;4)gIgw1 y[$O!(2R4<iIF!b%,/ddSdW(8Zr kIך d:K\+|*XBGa'iaT U=d["<.LZ̥`%dH##YW{3!&9 !m VǴtU|F˒ V:09?j) XȂ3k Η&X ij)W+_Ѫ0PIk >6F L([3Nj";H߹Z&f:K2~, I$⥃QQ ݽ.-ziK" $J;ʅPZ2[J;q#QHߩU7K]aF~HVI%19K?rrKD:Ϸ^--99u9Rb˸T|0!E>Vٝ#(%V:Qr!̔[M:%&ڱkeR0Hk 94*Fۅo(0&j|b ]Dq? v]Jp4FJ1#Fޙ::Y mD[%3Nd!*53E, X|ͽRnC iwR8ijJ<iu2]zCp{/T۸OrE6{Q'  ؿ;:H ڇ%Z!a񲌄!TTf@u苳?APp J^D5mUb8i_ *. n5\ހ)vi47caܖ n`(()kZȑvs"dxȋʤ4dTvEp] pU[x}v=f列rvXEŪe2}=z^]OJkCWDcnX1tcNzlNs"m8BQtyO,'7O-$kF(ۓ59Uuj#n=m/ 9ڦ`wǛM L+5 !/I_xA/qAuϏR];&`mfرc |P׀*ϣ_lU#t+5,8{g4Kmw {` 8`x k hVY}vPǓ_˯p`A(*|[$#~Fn@ev3o[֩ʭ2i5T@_14JxCηhhz39ڔf@Q^ 8Z\D|Rvu*zEZcw. .5ݖ~V`:4,r>rhk-6sz%LDZUTѹRA$R;_)_`E:IࡓfAM51K1H/e?9^Dc8ٜi[i<[6LH1YEl)ί_'ҼO偿5dS ڀ/ɞO7"TB6M(t ~>N.K5?g5z̜k;}k͹nr#n?p:quG& 5ʴӬ 9EsonaF~]^b0\sH<3btWSS!4Z3BFO(#ubP9BkèFe"}U;қͣ)ǫRZwd`b NB|̈f(c@l/`Z+Q z3"#v҄\ZBea #M0n:kK}r9!j/ji<"I^ Lݍ ޓ^ՂgRFn-n"iS]K0SGvpBzw4_'ccbaE?(d:[49xQ(+[?]ޣ>5N7#4~^i>Q}Od=~_-Br2 {yuCCUCV f"Ъjjۣ'~p` (O5vWn`$wFI EG7P&ITB2?&pc}9%x.`xGF W"V_yկǨ[CaXZug] sF]TeN\SK`3ϟlmp6Il4D9H;:ʖQV+S/*##Z\:O)M.s ̼}T>l邃>E_ܧڶz;з_H;ӆ9:S49r~oxiPev}T,Y!@h#ˉ v/>d)Ĺ0 1=l]=y]T{l֡e_"9M"0E`DUՏ3V BmV C9I5F'тW<"mS9JDz`6[תEN?:0n@k]] [4,yA@Oh{1>>nm~]:Q(EN>UPX&RDŽ}K>nMw!+W;Ali:|bOk4,i$ՊGmIa3Cΰr\rdY, S~Y~`B2鍰cw; K̂"z8YL';QOS7%:P+ r"6WC!e,MWޢC[hZ'#ԙgU"δ[e5wʢ^Z_bZ֞"0ẻ(5^l׶6ާBDJ_0Ջu\IZ 1|EKYj,r%s!l{#:ZV Bݛn`!$̃+`- KPk]vZPc?0r[`XEp$b̷#* \sŜ On5*gFevm<5$cQ jymxW7 ֻ3WT,~cgH2Pu_cEdI߉&í3,f4'8sk=D-6u5) V\~'W~kN;)5)AfS=4`㓿H;f_Q[{~dXwG*Ǯ:9|b(^}B I :IX$xb[ZU'ag1Xo#J|MO\\';u͆W _GZݦT Ę ()$^}4I-)bVc zKi c0@taa-ȞC6håz%T ]e\{%[W 0le<箿Cl>=B'^&BJ1HE?uk;{ړ(tAŲS=S{Pug#O~.ЇM4jSV۴Y ;癞dʀO#Euf?]8TH̵_}p7Hn$neQ+?N>wemake_python_styleguide-0.12.4.dist-info/RECORDPKjj&`O