1# -*- coding: utf-8 -*-
2"""Checker of PEP-8 Naming Conventions."""
3import sys
4from collections import deque
5from fnmatch import fnmatch
6from functools import partial
7from itertools import chain
8
9from flake8 import style_guide
10from flake8_polyfill import options
11
12try:
13    import ast
14    from ast import iter_child_nodes
15except ImportError:
16    from flake8.util import ast, iter_child_nodes
17
18__version__ = '0.12.1'
19
20PYTHON_VERSION = sys.version_info[:3]
21PY2 = PYTHON_VERSION[0] == 2
22
23CLASS_METHODS = frozenset((
24    '__new__',
25    '__init_subclass__',
26    '__class_getitem__',
27))
28METACLASS_BASES = frozenset(('type', 'ABCMeta'))
29
30# Node types which may contain class methods
31METHOD_CONTAINER_NODES = {ast.If, ast.While, ast.For, ast.With}
32FUNC_NODES = (ast.FunctionDef,)
33
34if PY2:
35    METHOD_CONTAINER_NODES |= {ast.TryExcept, ast.TryFinally}
36else:
37    METHOD_CONTAINER_NODES |= {ast.Try}
38
39if PYTHON_VERSION > (3, 5):
40    FUNC_NODES += (ast.AsyncFunctionDef,)
41    METHOD_CONTAINER_NODES |= {ast.AsyncWith, ast.AsyncFor}
42
43if PY2:
44    def _unpack_args(args):
45        ret = []
46        for arg in args:
47            if isinstance(arg, ast.Tuple):
48                ret.extend(_unpack_args(arg.elts))
49            else:
50                ret.append((arg, arg.id))
51        return ret
52
53    def get_arg_name_tuples(node):
54        return _unpack_args(node.args.args)
55elif PYTHON_VERSION < (3, 8):
56    def get_arg_name_tuples(node):
57        groups = (node.args.args, node.args.kwonlyargs)
58        return [(arg, arg.arg) for args in groups for arg in args]
59else:
60    def get_arg_name_tuples(node):
61        groups = (node.args.posonlyargs, node.args.args, node.args.kwonlyargs)
62        return [(arg, arg.arg) for args in groups for arg in args]
63
64
65class _ASTCheckMeta(type):
66    def __init__(cls, class_name, bases, namespace):
67        cls.codes = tuple(code for code in namespace if code.startswith('N'))
68        try:
69            cls.all.append(cls())
70        except AttributeError:
71            cls.all = []
72
73
74def _err(self, node, code, **kwargs):
75    lineno, col_offset = node.lineno, node.col_offset
76    if isinstance(node, ast.ClassDef):
77        if PYTHON_VERSION < (3, 8):
78            lineno += len(node.decorator_list)
79        col_offset += 6
80    elif isinstance(node, FUNC_NODES):
81        if PYTHON_VERSION < (3, 8):
82            lineno += len(node.decorator_list)
83        col_offset += 4
84    code_str = getattr(self, code)
85    if kwargs:
86        code_str = code_str.format(**kwargs)
87    return lineno, col_offset + 1, '%s %s' % (code, code_str), self
88
89
90def _ignored(name, ignore):
91    return any(fnmatch(name, i) for i in ignore)
92
93
94BaseASTCheck = _ASTCheckMeta('BaseASTCheck', (object,),
95                             {'__doc__': "Base for AST Checks.", 'err': _err})
96
97
98class _FunctionType(object):
99    CLASSMETHOD = 'classmethod'
100    STATICMETHOD = 'staticmethod'
101    FUNCTION = 'function'
102    METHOD = 'method'
103
104
105_default_ignore_names = [
106        'setUp',
107        'tearDown',
108        'setUpClass',
109        'tearDownClass',
110        'asyncSetUp',
111        'asyncTearDown',
112        'setUpTestData',
113        'failureException',
114        'longMessage',
115        'maxDiff']
116_default_classmethod_decorators = ['classmethod']
117_default_staticmethod_decorators = ['staticmethod']
118
119
120def _build_decorator_to_type(classmethod_decorators, staticmethod_decorators):
121    decorator_to_type = {}
122    for decorator in classmethod_decorators:
123        decorator_to_type[decorator] = _FunctionType.CLASSMETHOD
124    for decorator in staticmethod_decorators:
125        decorator_to_type[decorator] = _FunctionType.STATICMETHOD
126    return decorator_to_type
127
128
129class NamingChecker(object):
130    """Checker of PEP-8 Naming Conventions."""
131    name = 'naming'
132    version = __version__
133    visitors = BaseASTCheck.all
134    decorator_to_type = _build_decorator_to_type(
135        _default_classmethod_decorators, _default_staticmethod_decorators)
136    ignore_names = frozenset(_default_ignore_names)
137
138    def __init__(self, tree, filename):
139        self.parents = deque()
140        self._node = tree
141
142    @classmethod
143    def add_options(cls, parser):
144        options.register(parser, '--ignore-names',
145                         default=_default_ignore_names,
146                         action='store',
147                         type='string',
148                         parse_from_config=True,
149                         comma_separated_list=True,
150                         help='List of names or glob patterns the pep8-naming '
151                              'plugin should ignore. (Defaults to %default)')
152
153        options.register(parser, '--classmethod-decorators',
154                         default=_default_classmethod_decorators,
155                         action='store',
156                         type='string',
157                         parse_from_config=True,
158                         comma_separated_list=True,
159                         help='List of method decorators pep8-naming plugin '
160                              'should consider classmethods (Defaults to '
161                              '%default)')
162
163        options.register(parser, '--staticmethod-decorators',
164                         default=_default_staticmethod_decorators,
165                         action='store',
166                         type='string',
167                         parse_from_config=True,
168                         comma_separated_list=True,
169                         help='List of method decorators pep8-naming plugin '
170                              'should consider staticmethods (Defaults to '
171                              '%default)')
172        parser.extend_default_ignore(['N818'])
173
174    @classmethod
175    def parse_options(cls, options):
176        cls.ignore_names = frozenset(options.ignore_names)
177        cls.decorator_to_type = _build_decorator_to_type(
178            options.classmethod_decorators,
179            options.staticmethod_decorators)
180
181        # Build a list of node visitors based the error codes that have been
182        # selected in the style guide. Only the checks that have been selected
183        # will be evaluated as a performance optimization.
184        engine = style_guide.DecisionEngine(options)
185        cls.visitors = frozenset(
186            visitor for visitor in BaseASTCheck.all for code in visitor.codes
187            if engine.decision_for(code) is style_guide.Decision.Selected
188        )
189
190    def run(self):
191        return self.visit_tree(self._node) if self._node else ()
192
193    def visit_tree(self, node):
194        for error in self.visit_node(node):
195            yield error
196        self.parents.append(node)
197        for child in iter_child_nodes(node):
198            for error in self.visit_tree(child):
199                yield error
200        self.parents.pop()
201
202    def visit_node(self, node):
203        if isinstance(node, ast.ClassDef):
204            self.tag_class_functions(node)
205        elif isinstance(node, FUNC_NODES):
206            self.find_global_defs(node)
207
208        method = 'visit_' + node.__class__.__name__.lower()
209        parents = self.parents
210        ignore_names = self.ignore_names
211        for visitor in self.visitors:
212            visitor_method = getattr(visitor, method, None)
213            if visitor_method is None:
214                continue
215            for error in visitor_method(node, parents, ignore_names):
216                yield error
217
218    def tag_class_functions(self, cls_node):
219        """Tag functions if they are methods, classmethods, staticmethods"""
220        # tries to find all 'old style decorators' like
221        # m = staticmethod(m)
222        late_decoration = {}
223        for node in iter_child_nodes(cls_node):
224            if not (isinstance(node, ast.Assign) and
225                    isinstance(node.value, ast.Call) and
226                    isinstance(node.value.func, ast.Name)):
227                continue
228            func_name = node.value.func.id
229            if func_name not in self.decorator_to_type:
230                continue
231            meth = (len(node.value.args) == 1 and node.value.args[0])
232            if isinstance(meth, ast.Name):
233                late_decoration[meth.id] = self.decorator_to_type[func_name]
234
235        # If this class inherits from a known metaclass base class, it is
236        # itself a metaclass, and we'll consider all of its methods to be
237        # classmethods.
238        bases = chain(
239            (b.id for b in cls_node.bases if isinstance(b, ast.Name)),
240            (b.attr for b in cls_node.bases if isinstance(b, ast.Attribute)),
241        )
242        ismetaclass = any(name for name in bases if name in METACLASS_BASES)
243
244        self.set_function_nodes_types(
245            iter_child_nodes(cls_node), ismetaclass, late_decoration)
246
247    def set_function_nodes_types(self, nodes, ismetaclass, late_decoration):
248        # iterate over all functions and tag them
249        for node in nodes:
250            if type(node) in METHOD_CONTAINER_NODES:
251                self.set_function_nodes_types(
252                    iter_child_nodes(node), ismetaclass, late_decoration)
253            if not isinstance(node, FUNC_NODES):
254                continue
255            node.function_type = _FunctionType.METHOD
256            if node.name in CLASS_METHODS or ismetaclass:
257                node.function_type = _FunctionType.CLASSMETHOD
258            if node.name in late_decoration:
259                node.function_type = late_decoration[node.name]
260            elif node.decorator_list:
261                for d in node.decorator_list:
262                    name = self.find_decorator_name(d)
263                    if name in self.decorator_to_type:
264                        node.function_type = self.decorator_to_type[name]
265                        break
266
267    @classmethod
268    def find_decorator_name(cls, d):
269        if isinstance(d, ast.Name):
270            return d.id
271        elif isinstance(d, ast.Attribute):
272            return d.attr
273        elif isinstance(d, ast.Call):
274            return cls.find_decorator_name(d.func)
275
276    @staticmethod
277    def find_global_defs(func_def_node):
278        global_names = set()
279        nodes_to_check = deque(iter_child_nodes(func_def_node))
280        while nodes_to_check:
281            node = nodes_to_check.pop()
282            if isinstance(node, ast.Global):
283                global_names.update(node.names)
284
285            if not isinstance(node, (ast.ClassDef,) + FUNC_NODES):
286                nodes_to_check.extend(iter_child_nodes(node))
287        func_def_node.global_names = global_names
288
289
290class ClassNameCheck(BaseASTCheck):
291    """
292    Almost without exception, class names use the CapWords convention.
293
294    Classes for internal use have a leading underscore in addition.
295    """
296    N801 = "class name '{name}' should use CapWords convention"
297    N818 = "exception name '{name}' should be named with an Error suffix"
298
299    @classmethod
300    def get_classdef(cls, name, parents):
301        for parent in parents:
302            for node in parent.body:
303                if isinstance(node, ast.ClassDef) and node.name == name:
304                    return node
305
306    @classmethod
307    def superclass_names(cls, name, parents, _names=None):
308        names = _names or set()
309        classdef = cls.get_classdef(name, parents)
310        if not classdef:
311            return names
312        for base in classdef.bases:
313            if isinstance(base, ast.Name) and base.id not in names:
314                names.add(base.id)
315                names.update(cls.superclass_names(base.id, parents, names))
316        return names
317
318    def visit_classdef(self, node, parents, ignore=None):
319        name = node.name
320        if _ignored(name, ignore):
321            return
322        name = name.strip('_')
323        if not name[:1].isupper() or '_' in name:
324            yield self.err(node, 'N801', name=name)
325        superclasses = self.superclass_names(name, parents)
326        if "Exception" in superclasses and not name.endswith("Error"):
327            yield self.err(node, 'N818', name=name)
328
329
330class FunctionNameCheck(BaseASTCheck):
331    """
332    Function names should be lowercase, with words separated by underscores
333    as necessary to improve readability.
334
335    Functions *not* being methods '__' in front and back are not allowed.
336
337    mixedCase is allowed only in contexts where that's already the
338    prevailing style (e.g. threading.py), to retain backwards compatibility.
339    """
340    N802 = "function name '{name}' should be lowercase"
341    N807 = "function name '{name}' should not start and end with '__'"
342
343    def visit_functiondef(self, node, parents, ignore=None):
344        function_type = getattr(node, 'function_type', _FunctionType.FUNCTION)
345        name = node.name
346        if _ignored(name, ignore):
347            return
348        if name in ('__dir__', '__getattr__'):
349            return
350        if name.lower() != name:
351            yield self.err(node, 'N802', name=name)
352        if (function_type == _FunctionType.FUNCTION
353                and name[:2] == '__' and name[-2:] == '__'):
354            yield self.err(node, 'N807', name=name)
355
356    visit_asyncfunctiondef = visit_functiondef
357
358
359class FunctionArgNamesCheck(BaseASTCheck):
360    """
361    The argument names of a function should be lowercase, with words separated
362    by underscores.
363
364    A classmethod should have 'cls' as first argument.
365    A method should have 'self' as first argument.
366    """
367    N803 = "argument name '{name}' should be lowercase"
368    N804 = "first argument of a classmethod should be named 'cls'"
369    N805 = "first argument of a method should be named 'self'"
370
371    def visit_functiondef(self, node, parents, ignore=None):
372
373        def arg_name(arg):
374            try:
375                return arg, arg.arg
376            except AttributeError:  # PY2
377                return node, arg
378
379        for arg, name in arg_name(node.args.vararg), arg_name(node.args.kwarg):
380            if name is None or _ignored(name, ignore):
381                continue
382            if name.lower() != name:
383                yield self.err(arg, 'N803', name=name)
384                return
385
386        arg_name_tuples = get_arg_name_tuples(node)
387        if not arg_name_tuples:
388            return
389        arg0, name0 = arg_name_tuples[0]
390        function_type = getattr(node, 'function_type', _FunctionType.FUNCTION)
391
392        if function_type == _FunctionType.METHOD:
393            if name0 != 'self' and not _ignored(name0, ignore):
394                yield self.err(arg0, 'N805')
395        elif function_type == _FunctionType.CLASSMETHOD:
396            if name0 != 'cls' and not _ignored(name0, ignore):
397                yield self.err(arg0, 'N804')
398        for arg, name in arg_name_tuples:
399            if name.lower() != name and not _ignored(name, ignore):
400                yield self.err(arg, 'N803', name=name)
401                return
402
403    visit_asyncfunctiondef = visit_functiondef
404
405
406class ImportAsCheck(BaseASTCheck):
407    """
408    Don't change the naming convention via an import
409    """
410    N811 = "constant '{name}' imported as non constant '{asname}'"
411    N812 = "lowercase '{name}' imported as non lowercase '{asname}'"
412    N813 = "camelcase '{name}' imported as lowercase '{asname}'"
413    N814 = "camelcase '{name}' imported as constant '{asname}'"
414    N817 = "camelcase '{name}' imported as acronym '{asname}'"
415
416    def visit_importfrom(self, node, parents, ignore=None):
417        for name in node.names:
418            asname = name.asname
419            if not asname:
420                continue
421            original_name = name.name
422            err_kwargs = {'name': original_name, 'asname': asname}
423            if original_name.isupper():
424                if not asname.isupper():
425                    yield self.err(node, 'N811', **err_kwargs)
426            elif original_name.islower():
427                if asname.lower() != asname:
428                    yield self.err(node, 'N812', **err_kwargs)
429            elif asname.islower():
430                yield self.err(node, 'N813', **err_kwargs)
431            elif asname.isupper():
432                if ''.join(filter(str.isupper, original_name)) == asname:
433                    yield self.err(node, 'N817', **err_kwargs)
434                else:
435                    yield self.err(node, 'N814', **err_kwargs)
436
437    visit_import = visit_importfrom
438
439
440class VariablesCheck(BaseASTCheck):
441    """
442    Class attributes and local variables in functions should be lowercase
443    """
444    N806 = "variable '{name}' in function should be lowercase"
445    N815 = "variable '{name}' in class scope should not be mixedCase"
446    N816 = "variable '{name}' in global scope should not be mixedCase"
447
448    def _find_errors(self, assignment_target, parents, ignore):
449        for parent_func in reversed(parents):
450            if isinstance(parent_func, ast.ClassDef):
451                checker = self.class_variable_check
452                break
453            if isinstance(parent_func, FUNC_NODES):
454                checker = partial(self.function_variable_check, parent_func)
455                break
456        else:
457            checker = self.global_variable_check
458        for name in _extract_names(assignment_target):
459            if _ignored(name, ignore):
460                continue
461            error_code = checker(name)
462            if error_code:
463                yield self.err(assignment_target, error_code, name=name)
464
465    @staticmethod
466    def is_namedtupe(node_value):
467        if isinstance(node_value, ast.Call):
468            if isinstance(node_value.func, ast.Attribute):
469                if node_value.func.attr == 'namedtuple':
470                    return True
471            elif isinstance(node_value.func, ast.Name):
472                if node_value.func.id == 'namedtuple':
473                    return True
474        return False
475
476    def visit_assign(self, node, parents, ignore=None):
477        if self.is_namedtupe(node.value):
478            return
479        for target in node.targets:
480            for error in self._find_errors(target, parents, ignore):
481                yield error
482
483    def visit_namedexpr(self, node, parents, ignore):
484        if self.is_namedtupe(node.value):
485            return
486        for error in self._find_errors(node.target, parents, ignore):
487            yield error
488
489    visit_annassign = visit_namedexpr
490
491    def visit_with(self, node, parents, ignore):
492        if PY2:
493            for error in self._find_errors(
494                    node.optional_vars, parents, ignore):
495                yield error
496            return
497        for item in node.items:
498            for error in self._find_errors(
499                    item.optional_vars, parents, ignore):
500                yield error
501
502    visit_asyncwith = visit_with
503
504    def visit_for(self, node, parents, ignore):
505        for error in self._find_errors(node.target, parents, ignore):
506            yield error
507
508    visit_asyncfor = visit_for
509
510    def visit_excepthandler(self, node, parents, ignore):
511        if node.name:
512            for error in self._find_errors(node, parents, ignore):
513                yield error
514
515    def visit_generatorexp(self, node, parents, ignore):
516        for gen in node.generators:
517            for error in self._find_errors(gen.target, parents, ignore):
518                yield error
519
520    visit_listcomp = visit_dictcomp = visit_setcomp = visit_generatorexp
521
522    @staticmethod
523    def global_variable_check(name):
524        if is_mixed_case(name):
525            return 'N816'
526
527    @staticmethod
528    def class_variable_check(name):
529        if is_mixed_case(name):
530            return 'N815'
531
532    @staticmethod
533    def function_variable_check(func, var_name):
534        if var_name in func.global_names:
535            return None
536        if var_name.lower() == var_name:
537            return None
538        return 'N806'
539
540
541def _extract_names(assignment_target):
542    """Yield assignment_target ids."""
543    target_type = type(assignment_target)
544    if target_type is ast.Name:
545        yield assignment_target.id
546    elif target_type in (ast.Tuple, ast.List):
547        for element in assignment_target.elts:
548            element_type = type(element)
549            if element_type is ast.Name:
550                yield element.id
551            elif element_type in (ast.Tuple, ast.List):
552                for n in _extract_names(element):
553                    yield n
554            elif not PY2 and element_type is ast.Starred:  # PEP 3132
555                for n in _extract_names(element.value):
556                    yield n
557    elif target_type is ast.ExceptHandler:
558        if PY2:
559            # Python 2 supports unpacking tuple exception values.
560            if isinstance(assignment_target.name, ast.Tuple):
561                for name in assignment_target.name.elts:
562                    yield name.id
563            elif isinstance(assignment_target.name, ast.Attribute):
564                # Python 2 also supports assigning an exception to an attribute
565                # eg. except Exception as obj.attr
566                yield assignment_target.name.attr
567            else:
568                yield assignment_target.name.id
569        else:
570            yield assignment_target.name
571
572
573def is_mixed_case(name):
574    return name.lower() != name and name.lstrip('_')[:1].islower()
575