diff options
author | cpopa <devnull@localhost> | 2014-07-22 13:29:02 +0200 |
---|---|---|
committer | cpopa <devnull@localhost> | 2014-07-22 13:29:02 +0200 |
commit | 5359ed4f09b8667907280b49c84831a1c1dfe8bf (patch) | |
tree | a5f44cd333582ed59302f881bde5ddb5cbbdd9fb /checkers/variables.py | |
parent | 7f5869347f70a4af11587a8975e262e9c813386b (diff) | |
parent | d2dac2c5d2ba2f8a06fe43d815fd3ff63a089000 (diff) | |
download | pylint-5359ed4f09b8667907280b49c84831a1c1dfe8bf.tar.gz |
Merge with default.
Diffstat (limited to 'checkers/variables.py')
-rw-r--r-- | checkers/variables.py | 125 |
1 files changed, 120 insertions, 5 deletions
diff --git a/checkers/variables.py b/checkers/variables.py index ebe520a..6fba97d 100644 --- a/checkers/variables.py +++ b/checkers/variables.py @@ -25,6 +25,7 @@ from astroid import are_exclusive, builtin_lookup, AstroidBuildingException from logilab.common.modutils import file_from_modpath from pylint.interfaces import IAstroidChecker +from pylint.utils import get_global_option from pylint.checkers import BaseChecker from pylint.checkers.utils import (PYMETHODS, is_ancestor_name, is_builtin, is_defined_before, is_error, is_func_default, is_func_decorator, @@ -68,6 +69,71 @@ def _get_unpacking_extra_info(node, infered): more = ' defined at line %s of %s' % (infered.lineno, infered_module) return more +def _detect_global_scope(node, frame, defframe): + """ Detect that the given frames shares a global + scope. + + Two frames shares a global scope when neither + of them are hidden under a function scope, as well + as any of parent scope of them, until the root scope. + In this case, depending from something defined later on + will not work, because it is still undefined. + + Example: + class A: + # B has the same global scope as `C`, leading to a NameError. + class B(C): ... + class C: ... + + """ + def_scope = scope = None + if frame and frame.parent: + scope = frame.parent.scope() + if defframe and defframe.parent: + def_scope = defframe.parent.scope() + if isinstance(frame, astroid.Function): + # If the parent of the current node is a + # function, then it can be under its scope + # (defined in, which doesn't concern us) or + # the `->` part of annotations. The same goes + # for annotations of function arguments, they'll have + # their parent the Arguments node. + if not isinstance(node.parent, + (astroid.Function, astroid.Arguments)): + return False + elif any(not isinstance(f, (astroid.Class, astroid.Module)) + for f in (frame, defframe)): + # Not interested in other frames, since they are already + # not in a global scope. + return False + + break_scopes = [] + for s in (scope, def_scope): + # Look for parent scopes. If there is anything different + # than a module or a class scope, then they frames don't + # share a global scope. + parent_scope = s + while parent_scope: + if not isinstance(parent_scope, (astroid.Class, astroid.Module)): + break_scopes.append(parent_scope) + break + if parent_scope.parent: + parent_scope = parent_scope.parent.scope() + else: + break + if break_scopes and len(set(break_scopes)) != 1: + # Store different scopes than expected. + # If the stored scopes are, in fact, the very same, then it means + # that the two frames (frame and defframe) shares the same scope, + # and we could apply our lineno analysis over them. + # For instance, this works when they are inside a function, the node + # that uses a definition and the definition itself. + return False + # At this point, we are certain that frame and defframe shares a scope + # and the definition of the first depends on the second. + return frame.lineno < defframe.lineno + + MSGS = { 'E0601': ('Using variable %r before assignment', 'used-before-assignment', @@ -147,7 +213,7 @@ MSGS = { 'a sequence is used in an unpack assignment'), 'W0640': ('Cell variable %s defined in loop', - 'cell-var-from-loop', + 'cell-var-from-loop', 'A variable used in a closure is defined in a loop. ' 'This will result in all closures using the same value for ' 'the closed-over variable.'), @@ -355,6 +421,10 @@ builtins. Remember that you should avoid to define new builtins when possible.' authorized_rgx = self.config.dummy_variables_rgx called_overridden = False argnames = node.argnames() + global_names = set() + for global_stmt in node.nodes_of_class(astroid.Global): + global_names.update(set(global_stmt.names)) + for name, stmts in not_consumed.iteritems(): # ignore some special names specified by user configuration if authorized_rgx.match(name): @@ -364,6 +434,23 @@ builtins. Remember that you should avoid to define new builtins when possible.' stmt = stmts[0] if isinstance(stmt, astroid.Global): continue + if isinstance(stmt, (astroid.Import, astroid.From)): + # Detect imports, assigned to global statements. + if global_names: + skip = False + for import_name, import_alias in stmt.names: + # If the import uses an alias, check only that. + # Otherwise, check only the import name. + if import_alias: + if import_alias in global_names: + skip = True + break + elif import_name in global_names: + skip = True + break + if skip: + continue + # care about functions with unknown argument (builtins) if name in argnames: if is_method: @@ -411,7 +498,26 @@ builtins. Remember that you should avoid to define new builtins when possible.' break else: # global but no assignment - self.add_message('global-variable-not-assigned', args=name, node=node) + # Detect imports in the current frame, with the required + # name. Such imports can be considered assignments. + imports = frame.nodes_of_class((astroid.Import, astroid.From)) + for import_node in imports: + found = False + for import_name, import_alias in import_node.names: + # If the import uses an alias, check only that. + # Otherwise, check only the import name. + if import_alias: + if import_alias == name: + found = True + break + elif import_name and import_name == name: + found = True + break + if found: + break + else: + self.add_message('global-variable-not-assigned', + args=name, node=node) default_message = False if not assign_nodes: continue @@ -447,7 +553,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' else: if maybe_for.parent_of(node_scope) and not isinstance(node_scope.statement(), astroid.Return): self.add_message('cell-var-from-loop', node=node, args=node.name) - + def _loopvar_name(self, node, name): # filter variables according to node's scope # XXX used to filter parents but don't remember why, and removing this @@ -552,7 +658,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' defframe = defstmt.frame() maybee0601 = True if not frame is defframe: - maybee0601 = False + maybee0601 = _detect_global_scope(node, frame, defframe) elif defframe.parent is None: # we are at the module level, check the name is not # defined in builtins @@ -649,6 +755,10 @@ builtins. Remember that you should avoid to define new builtins when possible.' # attempt to check unpacking is properly balanced values = infered.itered() if len(targets) != len(values): + # Check if we have starred nodes. + if any(isinstance(target, astroid.Starred) + for target in targets): + return self.add_message('unbalanced-tuple-unpacking', node=node, args=(_get_unpacking_extra_info(node, infered), len(targets), @@ -675,6 +785,8 @@ builtins. Remember that you should avoid to define new builtins when possible.' if the latest access name corresponds to a module, return it """ assert isinstance(module, astroid.Module), module + ignored_modules = get_global_option(self, 'ignored-modules', + default=[]) while module_names: name = module_names.pop(0) if name == '__dict__': @@ -685,7 +797,10 @@ builtins. Remember that you should avoid to define new builtins when possible.' if module is astroid.YES: return None except astroid.NotFoundError: - self.add_message('no-name-in-module', args=(name, module.name), node=node) + if module.name in ignored_modules: + return None + self.add_message('no-name-in-module', + args=(name, module.name), node=node) return None except astroid.InferenceError: return None |