summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pyflakes/checker.py37
-rw-r--r--pyflakes/test/test_doctests.py48
2 files changed, 71 insertions, 14 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index e693f20..f8c8df8 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -254,11 +254,11 @@ class GeneratorScope(Scope):
class ModuleScope(Scope):
- pass
+ """Scope for a module."""
class DoctestScope(ModuleScope):
- pass
+ """Scope for a doctest."""
# Globally defined names which are not attributes of the builtins module, or
@@ -352,6 +352,10 @@ class Checker(object):
self.offset = offset
handler()
+ def _in_doctest(self):
+ return (len(self.scopeStack) >= 2 and
+ isinstance(self.scopeStack[1], DoctestScope))
+
@property
def scope(self):
return self.scopeStack[-1]
@@ -681,6 +685,10 @@ class Checker(object):
return
if not examples:
return
+
+ # Place doctest in module scope
+ saved_stack = self.scopeStack
+ self.scopeStack = [self.scopeStack[0]]
node_offset = self.offset or (0, 0)
self.pushScope(DoctestScope)
underscore_in_builtins = '_' in self.builtIns
@@ -704,6 +712,7 @@ class Checker(object):
if not underscore_in_builtins:
self.builtIns.remove('_')
self.popScope()
+ self.scopeStack = saved_stack
def ignore(self, node):
pass
@@ -745,14 +754,8 @@ class Checker(object):
"""
Keep track of globals declarations.
"""
- for i, scope in enumerate(self.scopeStack):
- if isinstance(scope, DoctestScope):
- global_scope_index = i
- global_scope = scope
- break
- else:
- global_scope_index = 0
- global_scope = self.scopeStack[0]
+ global_scope_index = 1 if self._in_doctest() else 0
+ global_scope = self.scopeStack[global_scope_index]
# Ignore 'global' statement in global scope.
if self.scope is not global_scope:
@@ -861,9 +864,11 @@ class Checker(object):
self.handleNode(deco, node)
self.LAMBDA(node)
self.addBinding(node, FunctionDefinition(node.name, node))
- # doctest does not process doctest within a doctest
- if self.withDoctest and not any(
- isinstance(scope, DoctestScope) for scope in self.scopeStack):
+ # doctest does not process doctest within a doctest,
+ # or in nested functions.
+ if (self.withDoctest and
+ not self._in_doctest() and
+ not isinstance(self.scope, FunctionScope)):
self.deferFunction(lambda: self.handleDoctests(node))
ASYNCFUNCTIONDEF = FUNCTIONDEF
@@ -963,7 +968,11 @@ class Checker(object):
for keywordNode in node.keywords:
self.handleNode(keywordNode, node)
self.pushScope(ClassScope)
- if self.withDoctest:
+ # doctest does not process doctest within a doctest
+ # classes within classes are processed.
+ if (self.withDoctest and
+ not self._in_doctest() and
+ not isinstance(self.scope, FunctionScope)):
self.deferFunction(lambda: self.handleDoctests(node))
for stmt in node.body:
self.handleNode(stmt, node)
diff --git a/pyflakes/test/test_doctests.py b/pyflakes/test/test_doctests.py
index 6793da9..9dd4278 100644
--- a/pyflakes/test/test_doctests.py
+++ b/pyflakes/test/test_doctests.py
@@ -190,6 +190,54 @@ class Test(TestCase):
'''
""", m.UndefinedName)
+ def test_nested_class(self):
+ """Doctest within nested class are processed."""
+ self.flakes("""
+ class C:
+ class D:
+ '''
+ >>> m
+ '''
+ def doctest_stuff(self):
+ '''
+ >>> m
+ '''
+ return 1
+ """, m.UndefinedName, m.UndefinedName)
+
+ def test_ignore_nested_function(self):
+ """Doctest module does not process doctest in nested functions."""
+ # 'syntax error' would cause a SyntaxError if the doctest was processed.
+ # However doctest does not find doctest in nested functions
+ # (https://bugs.python.org/issue1650090). If nested functions were
+ # processed, this use of m should cause UndefinedName, and the
+ # name inner_function should probably exist in the doctest scope.
+ self.flakes("""
+ def doctest_stuff():
+ def inner_function():
+ '''
+ >>> syntax error
+ >>> inner_function()
+ 1
+ >>> m
+ '''
+ return 1
+ m = inner_function()
+ return m
+ """)
+
+ def test_inaccessible_scope_class(self):
+ """Doctest may not access class scope."""
+ self.flakes("""
+ class C:
+ def doctest_stuff(self):
+ '''
+ >>> m
+ '''
+ return 1
+ m = 1
+ """, m.UndefinedName)
+
def test_importBeforeDoctest(self):
self.flakes("""
import foo