Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report unused assignments in doctest #53

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should wait until #343

return
for name, binding in super(FunctionScope, self).unusedAssignments():
if name not in self.globals:
yield name, binding


Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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():
Expand Down
24 changes: 23 additions & 1 deletion pyflakes/test/test_doctests.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def test_scope_class(self):
def doctest_stuff():
'''
>>> d = doctest_stuff()
>>> d
None
'''
f = m
return f
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
11 changes: 6 additions & 5 deletions pyflakes/test/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions pyflakes/test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -60,6 +61,7 @@ def test_redefinedInGenerator(self):
self.flakes('''
a = 1
(1 for a, b in [(1, 2)])
a
''')
self.flakes('''
class A:
Expand Down Expand Up @@ -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():
Expand All @@ -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:
Expand Down Expand Up @@ -220,6 +225,7 @@ def test_redefinedIfElseInListComp(self):
a = 1
else:
[a for a in '12']
a
''')

@skipIf(version_info >= (3,),
Expand Down Expand Up @@ -1020,6 +1026,7 @@ def test_doubleAssignment(self):
self.flakes('''
x = 10
x = 20
x
''', m.RedefinedWhileUnused)

def test_doubleAssignmentConditionally(self):
Expand All @@ -1031,6 +1038,7 @@ def test_doubleAssignmentConditionally(self):
x = 10
if True:
x = 20
x
''')

def test_doubleAssignmentWithUse(self):
Expand All @@ -1042,6 +1050,7 @@ def test_doubleAssignmentWithUse(self):
x = 10
y = x * 2
x = 20
y
''')

def test_comparison(self):
Expand Down
6 changes: 5 additions & 1 deletion pyflakes/test/test_undefined_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ def test_delConditional(self):
Ignores conditional bindings deletion.
"""
self.flakes('''
context = None
test = True
if False:
del(test)
Expand Down Expand Up @@ -256,6 +255,7 @@ def fun():
a
a = 2
return a
a
''', m.UndefinedLocal)

def test_laterRedefinedGlobalFromNestedScope2(self):
Expand All @@ -272,6 +272,7 @@ def fun2():
a
a = 2
return a
a
''', m.UndefinedLocal)

def test_intermediateClassScopeIgnored(self):
Expand Down Expand Up @@ -507,6 +508,7 @@ def test_undefinedWithErrorHandler(self):
socket_map
except NameError:
socket_map = {}
socket_map['foo'] = 1
''')
self.flakes('''
try:
Expand All @@ -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):
Expand Down