1# -*- coding: utf-8 -*-
2import ast
3import inspect
4import sys
5
6
7try:
8    from flake8.engine import pep8 as stdin_utils
9except ImportError:
10    from flake8 import utils as stdin_utils
11
12
13WHITE_LIST = {
14    '__name__',
15    '__doc__',
16    'credits',
17    '_',
18}
19
20
21if sys.version_info >= (3, 0):
22    import builtins
23    BUILTINS = [
24        a[0]
25        for a in inspect.getmembers(builtins)
26        if a[0] not in WHITE_LIST
27    ]
28    PY3 = True
29else:
30    import __builtin__
31    BUILTINS = [
32        a[0]
33        for a in inspect.getmembers(__builtin__)
34        if a[0] not in WHITE_LIST
35    ]
36    PY3 = False
37
38if sys.version_info >= (3, 6):
39    AnnAssign = ast.AnnAssign
40else:  # There was no `AnnAssign` before python3.6
41    AnnAssign = type('AnnAssign', (ast.AST, ), {})
42
43if sys.version_info >= (3, 8):
44    NamedExpr = ast.NamedExpr
45else:  # There was no walrus operator before python3.8
46    NamedExpr = type('NamedExpr', (ast.AST, ), {})
47
48
49class BuiltinsChecker(object):
50    name = 'flake8_builtins'
51    version = '1.5.2'
52    assign_msg = 'A001 variable "{0}" is shadowing a python builtin'
53    argument_msg = 'A002 argument "{0}" is shadowing a python builtin'
54    class_attribute_msg = 'A003 class attribute "{0}" is shadowing a python builtin'
55
56    def __init__(self, tree, filename):
57        self.tree = tree
58        self.filename = filename
59
60    def run(self):
61        tree = self.tree
62
63        if self.filename == 'stdin':
64            lines = stdin_utils.stdin_get_value()
65            tree = ast.parse(lines)
66
67        for statement in ast.walk(tree):
68            for child in ast.iter_child_nodes(statement):
69                child.__flake8_builtins_parent = statement
70
71        function_nodes = [ast.FunctionDef]
72        if getattr(ast, 'AsyncFunctionDef', None):
73            function_nodes.append(ast.AsyncFunctionDef)
74        function_nodes = tuple(function_nodes)
75
76        for_nodes = [ast.For]
77        if getattr(ast, 'AsyncFor', None):
78            for_nodes.append(ast.AsyncFor)
79        for_nodes = tuple(for_nodes)
80
81        with_nodes = [ast.With]
82        if getattr(ast, 'AsyncWith', None):
83            with_nodes.append(ast.AsyncWith)
84        with_nodes = tuple(with_nodes)
85
86        comprehension_nodes = (
87            ast.ListComp,
88            ast.SetComp,
89            ast.DictComp,
90            ast.GeneratorExp,
91        )
92
93        value = None
94        for statement in ast.walk(tree):
95            if isinstance(statement, (ast.Assign, AnnAssign, NamedExpr)):
96                value = self.check_assignment(statement)
97
98            elif isinstance(statement, function_nodes):
99                value = self.check_function_definition(statement)
100
101            elif isinstance(statement, for_nodes):
102                value = self.check_for_loop(statement)
103
104            elif isinstance(statement, with_nodes):
105                value = self.check_with(statement)
106
107            elif isinstance(statement, ast.excepthandler):
108                value = self.check_exception(statement)
109
110            elif isinstance(statement, comprehension_nodes):
111                value = self.check_comprehension(statement)
112
113            elif isinstance(statement, (ast.Import, ast.ImportFrom)):
114                value = self.check_import(statement)
115
116            elif isinstance(statement, ast.ClassDef):
117                value = self.check_class(statement)
118
119            if value:
120                for line, offset, msg, rtype in value:
121                    yield line, offset, msg, rtype
122
123    def check_assignment(self, statement):
124        msg = self.assign_msg
125        if type(statement.__flake8_builtins_parent) is ast.ClassDef:
126            msg = self.class_attribute_msg
127
128        if isinstance(statement, ast.Assign):
129            stack = list(statement.targets)
130        else:  # This is `ast.AnnAssign` or `ast.NamedExpr`:
131            stack = [statement.target]
132
133        while stack:
134            item = stack.pop()
135            if isinstance(item, (ast.Tuple, ast.List)):
136                stack.extend(list(item.elts))
137            elif isinstance(item, ast.Name) and \
138                    item.id in BUILTINS:
139                yield self.error(item, message=msg, variable=item.id)
140            elif PY3 and isinstance(item, ast.Starred):
141                if hasattr(item.value, 'id') and item.value.id in BUILTINS:
142                    yield self.error(
143                        statement,
144                        message=msg,
145                        variable=item.value.id,
146                    )
147                elif hasattr(item.value, 'elts'):
148                    stack.extend(list(item.value.elts))
149
150    def check_function_definition(self, statement):
151        if statement.name in BUILTINS:
152            msg = self.assign_msg
153            if type(statement.__flake8_builtins_parent) is ast.ClassDef:
154                msg = self.class_attribute_msg
155
156            yield self.error(statement, message=msg, variable=statement.name)
157
158        if PY3:
159            all_arguments = []
160            all_arguments.extend(statement.args.args)
161            all_arguments.extend(getattr(statement.args, 'kwonlyargs', []))
162            all_arguments.extend(getattr(statement.args, 'posonlyargs', []))
163
164            for arg in all_arguments:
165                if isinstance(arg, ast.arg) and \
166                        arg.arg in BUILTINS:
167                    yield self.error(
168                        arg,
169                        message=self.argument_msg,
170                        variable=arg.arg,
171                    )
172        else:
173            for arg in statement.args.args:
174                if isinstance(arg, ast.Name) and \
175                        arg.id in BUILTINS:
176                    yield self.error(arg, message=self.argument_msg)
177
178    def check_for_loop(self, statement):
179        stack = [statement.target]
180        while stack:
181            item = stack.pop()
182            if isinstance(item, (ast.Tuple, ast.List)):
183                stack.extend(list(item.elts))
184            elif isinstance(item, ast.Name) and \
185                    item.id in BUILTINS:
186                yield self.error(statement, variable=item.id)
187            elif PY3 and isinstance(item, ast.Starred):
188                if hasattr(item.value, 'id') and item.value.id in BUILTINS:
189                    yield self.error(
190                        statement,
191                        variable=item.value.id,
192                    )
193                elif hasattr(item.value, 'elts'):
194                    stack.extend(list(item.value.elts))
195
196    def check_with(self, statement):
197        if not PY3:
198            var = statement.optional_vars
199            if isinstance(var, (ast.Tuple, ast.List)):
200                for element in var.elts:
201                    if isinstance(element, ast.Name) and \
202                            element.id in BUILTINS:
203                        yield self.error(statement, variable=element.id)
204
205            elif isinstance(var, ast.Name) and var.id in BUILTINS:
206                yield self.error(statement, variable=var.id)
207        else:
208            for item in statement.items:
209                var = item.optional_vars
210                if isinstance(var, (ast.Tuple, ast.List)):
211                    for element in var.elts:
212                        if isinstance(element, ast.Name) and \
213                                element.id in BUILTINS:
214                            yield self.error(statement, variable=element.id)
215                        elif isinstance(element, ast.Starred) and \
216                                element.value.id in BUILTINS:
217                            yield self.error(
218                                element,
219                                variable=element.value.id,
220                            )
221
222                elif isinstance(var, ast.Name) and var.id in BUILTINS:
223                    yield self.error(statement, variable=var.id)
224
225    def check_exception(self, statement):
226        exception_name = statement.name
227        value = ''
228        if isinstance(exception_name, ast.Name):
229            value = exception_name.id
230        elif isinstance(exception_name, str):  # Python +3.x
231            value = exception_name
232
233        if value in BUILTINS:
234            yield self.error(statement, variable=value)
235
236    def check_comprehension(self, statement):
237        for generator in statement.generators:
238            if isinstance(generator.target, ast.Name) \
239                    and generator.target.id in BUILTINS:
240                yield self.error(statement, variable=generator.target.id)
241
242            elif isinstance(generator.target, (ast.Tuple, ast.List)):
243                for tuple_element in generator.target.elts:
244                    if isinstance(tuple_element, ast.Name) and \
245                            tuple_element.id in BUILTINS:
246                        yield self.error(statement, variable=tuple_element.id)
247
248    def check_import(self, statement):
249        for name in statement.names:
250            if name.asname in BUILTINS:
251                yield self.error(statement, variable=name.asname)
252
253    def check_class(self, statement):
254        if statement.name in BUILTINS:
255            yield self.error(statement, variable=statement.name)
256
257    def error(
258        self,
259        statement,
260        message=None,
261        variable=None,
262        line=None,
263        column=None,
264    ):
265        if not message:
266            message = self.assign_msg
267        if not variable:
268            variable = statement.id
269        if not line:
270            line = statement.lineno
271        if not column:
272            column = statement.col_offset
273
274        return (
275            line,
276            column,
277            message.format(variable),
278            type(self),
279        )
280