diff options
-rw-r--r-- | pyflakes/checker.py | 37 | ||||
-rw-r--r-- | pyflakes/test/test_doctests.py | 48 |
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 |