diff --git a/pyflakes/checker.py b/pyflakes/checker.py index fe201462..bea2f56d 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -229,6 +229,14 @@ def __repr__(self): scope_cls = self.__class__.__name__ return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self)) + def unusedAssignments(self): + """ + Return a generator for the assignments which have not been used. + """ + for name, binding in self.items(): + if not binding.used and isinstance(binding, Assignment): + yield name, binding + class ClassScope(Scope): pass @@ -254,11 +262,15 @@ def __init__(self): def unusedAssignments(self): """ Return a generator for the assignments which have not been used. + + All assignments are considered used when a scope uses locals(). + + Unused names in alwaysUsed are skipped. """ - for name, binding in self.items(): - if (not binding.used and name not in self.globals - and not self.usesLocals - and isinstance(binding, Assignment)): + if self.usesLocals: + return + for name, binding in super(FunctionScope, self).unusedAssignments(): + if name not in self.globals: yield name, binding @@ -274,6 +286,16 @@ class ModuleScope(Scope): class DoctestScope(ModuleScope): """Scope for a doctest.""" + def unusedAssignments(self): + """ + Return a generator for the assignments which have not been used. + + '_' is never unused. + """ + for name, binding in super(DoctestScope, self).unusedAssignments(): + if name != '_': + yield name, binding + # Globally defined names which are not attributes of the builtins module, or # are only present on some platforms. @@ -390,6 +412,13 @@ def scope(self): def popScope(self): self.deadScopes.append(self.scopeStack.pop()) + def _check_unused_assignments(self): + """ + Check to see if any assignments have not been used. + """ + for name, binding in self.scope.unusedAssignments(): + self.report(messages.UnusedVariable, binding.source, name) + def checkDeadScopes(self): """ Look at scopes which have been fully examined and report names in them @@ -738,6 +767,7 @@ def handleDoctests(self, node): self.offset = node_offset if not underscore_in_builtins: self.builtIns.remove('_') + self.deferAssignment(self._check_unused_assignments) self.popScope() self.scopeStack = saved_stack @@ -959,13 +989,7 @@ def runFunction(): # case for Lambdas self.handleNode(node.body, node) - def checkUnusedAssignments(): - """ - Check to see if any assignments have not been used. - """ - for name, binding in self.scope.unusedAssignments(): - self.report(messages.UnusedVariable, binding.source, name) - self.deferAssignment(checkUnusedAssignments) + self.deferAssignment(self._check_unused_assignments) if PY32: def checkReturnWithArgumentInsideGenerator(): diff --git a/pyflakes/test/test_doctests.py b/pyflakes/test/test_doctests.py index aed7957b..fcc53474 100644 --- a/pyflakes/test/test_doctests.py +++ b/pyflakes/test/test_doctests.py @@ -62,6 +62,8 @@ def test_scope_class(self): def doctest_stuff(): ''' >>> d = doctest_stuff() + >>> d + None ''' f = m return f @@ -238,6 +240,24 @@ def doctest_stuff(self): m = 1 """, m.UndefinedName) + def test_assignment_unused(self): + """Report unused assignments.""" + self.flakes(""" + def doctest_stuff(): + ''' + >>> foo = 1 + ''' + """, m.UnusedVariable) + + def test_assignment_underscore(self): + """Assign to underscore (_) to avoid a doctest output.""" + self.flakes(""" + def doctest_stuff(): + ''' + >>> _ = 1 + ''' + """) + def test_importBeforeDoctest(self): self.flakes(""" import foo @@ -304,12 +324,14 @@ def test_offsetAfterDoctests(self): def doctest_stuff(): """ >>> x = 5 + >>> x + 5 """ x ''', m.UndefinedName).messages[0] - self.assertEqual(exc.lineno, 8) + self.assertEqual(exc.lineno, 10) self.assertEqual(exc.col, 0) def test_syntaxErrorInDoctest(self): diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py index 21015795..fc341b9e 100644 --- a/pyflakes/test/test_imports.py +++ b/pyflakes/test/test_imports.py @@ -23,7 +23,7 @@ def test_usedImport(self): self.flakes('import fu; del fu') def test_redefinedWhileUnused(self): - self.flakes('import fu; fu = 3', m.RedefinedWhileUnused) + self.flakes('import fu; fu = 3; fu', m.RedefinedWhileUnused) self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused) self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused) @@ -274,7 +274,7 @@ def fun(fu): ''') def test_newAssignment(self): - self.flakes('fu = None') + self.flakes('fu = None; fu') def test_usedInGetattr(self): self.flakes('import fu; fu.bar.baz') @@ -448,7 +448,7 @@ def test_redefinedByExcept(self): self.flakes(''' import fu try: pass - except Exception%sfu: pass + except Exception%sfu: fu ''' % as_exc, m.RedefinedWhileUnused) def test_usedInRaise(self): @@ -482,7 +482,7 @@ def test_usedInKeywordArg(self): self.flakes('import fu; fu.bar(stuff=fu)') def test_usedInAssignment(self): - self.flakes('import fu; bar=fu') + self.flakes('import fu; bar=fu; bar') self.flakes('import fu; n=0; n+=fu') def test_usedInListComp(self): @@ -725,7 +725,7 @@ def fu(): ''', m.RedefinedWhileUnused) def test_ignoreNonImportRedefinitions(self): - self.flakes('a = 1; a = 2') + self.flakes('a = 1; a = 2; a') @skip("todo") def test_importingForImportError(self): @@ -772,6 +772,7 @@ def test_futureImportFirst(self): self.flakes(''' x = 5 from __future__ import division + x ''', m.LateFutureImport) self.flakes(''' from foo import bar diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py index 0ac96a32..9f935eba 100644 --- a/pyflakes/test/test_other.py +++ b/pyflakes/test/test_other.py @@ -31,6 +31,7 @@ def test_redefinedInListComp(self): self.flakes(''' a = 1 [1 for a, b in [(1, 2)]] + a ''', m.RedefinedInListComp) self.flakes(''' class A: @@ -60,6 +61,7 @@ def test_redefinedInGenerator(self): self.flakes(''' a = 1 (1 for a, b in [(1, 2)]) + a ''') self.flakes(''' class A: @@ -90,11 +92,13 @@ def test_redefinedInSetComprehension(self): self.flakes(''' a = 1 {1 for a, b in [(1, 2)]} + a ''') self.flakes(''' class A: a = 1 {1 for a, b in [(1, 2)]} + a ''') self.flakes(''' def f(): @@ -120,6 +124,7 @@ def test_redefinedInDictComprehension(self): self.flakes(''' a = 1 {1: 42 for a, b in [(1, 2)]} + a ''') self.flakes(''' class A: @@ -220,6 +225,7 @@ def test_redefinedIfElseInListComp(self): a = 1 else: [a for a in '12'] + a ''') @skipIf(version_info >= (3,), @@ -1020,6 +1026,7 @@ def test_doubleAssignment(self): self.flakes(''' x = 10 x = 20 + x ''', m.RedefinedWhileUnused) def test_doubleAssignmentConditionally(self): @@ -1031,6 +1038,7 @@ def test_doubleAssignmentConditionally(self): x = 10 if True: x = 20 + x ''') def test_doubleAssignmentWithUse(self): @@ -1042,6 +1050,7 @@ def test_doubleAssignmentWithUse(self): x = 10 y = x * 2 x = 20 + y ''') def test_comparison(self): diff --git a/pyflakes/test/test_undefined_names.py b/pyflakes/test/test_undefined_names.py index 5653b915..a28228b3 100644 --- a/pyflakes/test/test_undefined_names.py +++ b/pyflakes/test/test_undefined_names.py @@ -173,7 +173,6 @@ def test_delConditional(self): Ignores conditional bindings deletion. """ self.flakes(''' - context = None test = True if False: del(test) @@ -256,6 +255,7 @@ def fun(): a a = 2 return a + a ''', m.UndefinedLocal) def test_laterRedefinedGlobalFromNestedScope2(self): @@ -272,6 +272,7 @@ def fun2(): a a = 2 return a + a ''', m.UndefinedLocal) def test_intermediateClassScopeIgnored(self): @@ -507,6 +508,7 @@ def test_undefinedWithErrorHandler(self): socket_map except NameError: socket_map = {} + socket_map['foo'] = 1 ''') self.flakes(''' try: @@ -520,12 +522,14 @@ def test_undefinedWithErrorHandler(self): socket_map except: socket_map = {} + socket_map['foo'] = 1 ''', m.UndefinedName) self.flakes(''' try: socket_map except Exception: socket_map = {} + socket_map['foo'] = 1 ''', m.UndefinedName) def test_definedInClass(self):