summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMacBox7 <ajankit2304@gmail.com>2018-06-27 19:43:10 +0530
committerMacBox7 <ajankit2304@gmail.com>2018-07-19 12:17:16 +0530
commit9dd73ec54411a563410872b47e76f0f89f34ecfe (patch)
treee7c1d3943f57160777f9a656b91952ea0df05f7a
parentd6f54c33f428d19d5bcd4c67e9ff89572a85c1c5 (diff)
downloadpyflakes-9dd73ec54411a563410872b47e76f0f89f34ecfe.tar.gz
Handle subtrees as input to checker
This patch essentially adds the the functionality for the Checker to handle subtrees. Along with this args have also been parsed through handlers. Closes https://github.com/PyCQA/pyflakes/issues/338
-rw-r--r--pyflakes/checker.py66
-rw-r--r--pyflakes/test/harness.py3
-rw-r--r--pyflakes/test/test_code_segment.py126
-rw-r--r--pyflakes/test/test_other.py31
4 files changed, 212 insertions, 14 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index a57ce50..1c30dda 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -95,9 +95,15 @@ def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()):
"""
Yield all direct child nodes of *node*, that is, all fields that
are nodes and all items of fields that are lists of nodes.
+
+ :param node: AST node to be iterated upon
+ :param omit: String or tuple of strings denoting the
+ attributes of the node to be omitted from
+ further parsing
+ :param _fields_order: Order of AST node fields
"""
for name in _fields_order[node.__class__]:
- if name == omit:
+ if omit and name in omit:
continue
field = getattr(node, name, None)
if isinstance(field, ast.AST):
@@ -472,6 +478,17 @@ class Checker(object):
callables which are deferred assignment checks.
"""
+ _ast_node_scope = {
+ ast.Module: ModuleScope,
+ ast.ClassDef: ClassScope,
+ ast.FunctionDef: FunctionScope,
+ ast.Lambda: FunctionScope,
+ ast.ListComp: GeneratorScope,
+ ast.SetComp: GeneratorScope,
+ ast.GeneratorExp: GeneratorScope,
+ ast.DictComp: GeneratorScope,
+ }
+
nodeDepth = 0
offset = None
traceTree = False
@@ -493,7 +510,10 @@ class Checker(object):
if builtins:
self.builtIns = self.builtIns.union(builtins)
self.withDoctest = withDoctest
- self.scopeStack = [ModuleScope()]
+ try:
+ self.scopeStack = [Checker._ast_node_scope[type(tree)]()]
+ except KeyError:
+ raise RuntimeError('No scope implemented for the node %r' % tree)
self.exceptHandlers = [()]
self.root = tree
self.handleChildren(tree)
@@ -643,6 +663,18 @@ class Checker(object):
return True
return False
+ def _getAncestor(self, node, ancestor_type):
+ parent = node
+ while True:
+ if parent is self.root:
+ return None
+ parent = self.getParent(parent)
+ if isinstance(parent, ancestor_type):
+ return parent
+
+ def getScopeNode(self, node):
+ return self._getAncestor(node, tuple(Checker._ast_node_scope.keys()))
+
def differentForks(self, lnode, rnode):
"""True, if lnode and rnode are located on different forks of IF/TRY"""
ancestor = self.getCommonAncestor(lnode, rnode, self.root)
@@ -790,6 +822,8 @@ class Checker(object):
binding = Binding(name, node)
elif name == '__all__' and isinstance(self.scope, ModuleScope):
binding = ExportBinding(name, node.parent, self.scope)
+ elif isinstance(getattr(node, 'ctx', None), ast.Param):
+ binding = Argument(name, self.getScopeNode(node))
else:
binding = Assignment(name, node)
self.addBinding(node, binding)
@@ -1103,13 +1137,12 @@ class Checker(object):
and isinstance(node.parent, ast.Call)):
# we are doing locals() call in current scope
self.scope.usesLocals = True
- elif isinstance(node.ctx, (ast.Store, ast.AugStore)):
+ elif isinstance(node.ctx, (ast.Store, ast.AugStore, ast.Param)):
self.handleNodeStore(node)
elif isinstance(node.ctx, ast.Del):
self.handleNodeDelete(node)
else:
- # must be a Param context -- this only happens for names in function
- # arguments, but these aren't dispatched through here
+ # Unknown context
raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
def CONTINUE(self, node):
@@ -1225,15 +1258,8 @@ class Checker(object):
def runFunction():
self.pushScope()
- for name in args:
- self.addBinding(node, Argument(name, node))
- if isinstance(node.body, list):
- # case for FunctionDefs
- for stmt in node.body:
- self.handleNode(stmt, node)
- else:
- # case for Lambdas
- self.handleNode(node.body, node)
+
+ self.handleChildren(node, omit='decorator_list')
def checkUnusedAssignments():
"""
@@ -1257,6 +1283,18 @@ class Checker(object):
self.deferFunction(runFunction)
+ def ARGUMENTS(self, node):
+ self.handleChildren(node, omit=('defaults', 'kw_defaults'))
+ if PY2:
+ scope_node = self.getScopeNode(node)
+ if node.vararg:
+ self.addBinding(node, Argument(node.vararg, scope_node))
+ if node.kwarg:
+ self.addBinding(node, Argument(node.kwarg, scope_node))
+
+ def ARG(self, node):
+ self.addBinding(node, Argument(node.arg, self.getScopeNode(node)))
+
def CLASSDEF(self, node):
"""
Check names used in a class definition, including its decorators, base
diff --git a/pyflakes/test/harness.py b/pyflakes/test/harness.py
index 3f592db..0a58bd5 100644
--- a/pyflakes/test/harness.py
+++ b/pyflakes/test/harness.py
@@ -17,6 +17,9 @@ class TestCase(unittest.TestCase):
def flakes(self, input, *expectedOutputs, **kw):
tree = compile(textwrap.dedent(input), "<test>", "exec", PyCF_ONLY_AST)
+ if kw.get('is_segment'):
+ tree = tree.body[0]
+ kw.pop('is_segment')
w = checker.Checker(tree, withDoctest=self.withDoctest, **kw)
outputs = [type(o) for o in w.messages]
expectedOutputs = list(expectedOutputs)
diff --git a/pyflakes/test/test_code_segment.py b/pyflakes/test/test_code_segment.py
new file mode 100644
index 0000000..5e53f8f
--- /dev/null
+++ b/pyflakes/test/test_code_segment.py
@@ -0,0 +1,126 @@
+from pyflakes import messages as m
+from pyflakes.checker import (FunctionScope, ClassScope, ModuleScope,
+ Argument, FunctionDefinition, Assignment)
+from pyflakes.test.harness import TestCase
+
+
+class TestCodeSegments(TestCase):
+ """
+ Tests for segments of a module
+ """
+
+ def test_function_segment(self):
+ self.flakes('''
+ def foo():
+ def bar():
+ pass
+ ''', is_segment=True)
+
+ self.flakes('''
+ def foo():
+ def bar():
+ x = 0
+ ''', m.UnusedVariable, is_segment=True)
+
+ def test_class_segment(self):
+ self.flakes('''
+ class Foo:
+ class Bar:
+ pass
+ ''', is_segment=True)
+
+ self.flakes('''
+ class Foo:
+ def bar():
+ x = 0
+ ''', m.UnusedVariable, is_segment=True)
+
+ def test_scope_class(self):
+ checker = self.flakes('''
+ class Foo:
+ x = 0
+ def bar(a, b=1, *d, **e):
+ pass
+ ''', is_segment=True)
+
+ scopes = checker.deadScopes
+ module_scopes = [
+ scope for scope in scopes if scope.__class__ is ModuleScope]
+ class_scopes = [
+ scope for scope in scopes if scope.__class__ is ClassScope]
+ function_scopes = [
+ scope for scope in scopes if scope.__class__ is FunctionScope]
+
+ # Ensure module scope is not present because we are analysing
+ # the inner contents of Foo
+ self.assertEqual(len(module_scopes), 0)
+ self.assertEqual(len(class_scopes), 1)
+ self.assertEqual(len(function_scopes), 1)
+
+ class_scope = class_scopes[0]
+ function_scope = function_scopes[0]
+
+ self.assertIsInstance(class_scope, ClassScope)
+ self.assertIsInstance(function_scope, FunctionScope)
+
+ self.assertIn('x', class_scope)
+ self.assertIn('bar', class_scope)
+
+ self.assertIn('a', function_scope)
+ self.assertIn('b', function_scope)
+ self.assertIn('d', function_scope)
+ self.assertIn('e', function_scope)
+
+ self.assertIsInstance(class_scope['bar'], FunctionDefinition)
+ self.assertIsInstance(class_scope['x'], Assignment)
+
+ self.assertIsInstance(function_scope['a'], Argument)
+ self.assertIsInstance(function_scope['b'], Argument)
+ self.assertIsInstance(function_scope['d'], Argument)
+ self.assertIsInstance(function_scope['e'], Argument)
+
+ def test_scope_function(self):
+ checker = self.flakes('''
+ def foo(a, b=1, *d, **e):
+ def bar(f, g=1, *h, **i):
+ pass
+ ''', is_segment=True)
+
+ scopes = checker.deadScopes
+ module_scopes = [
+ scope for scope in scopes if scope.__class__ is ModuleScope]
+ function_scopes = [
+ scope for scope in scopes if scope.__class__ is FunctionScope]
+
+ # Ensure module scope is not present because we are analysing
+ # the inner contents of foo
+ self.assertEqual(len(module_scopes), 0)
+ self.assertEqual(len(function_scopes), 2)
+
+ function_scope_foo = function_scopes[1]
+ function_scope_bar = function_scopes[0]
+
+ self.assertIsInstance(function_scope_foo, FunctionScope)
+ self.assertIsInstance(function_scope_bar, FunctionScope)
+
+ self.assertIn('a', function_scope_foo)
+ self.assertIn('b', function_scope_foo)
+ self.assertIn('d', function_scope_foo)
+ self.assertIn('e', function_scope_foo)
+ self.assertIn('bar', function_scope_foo)
+
+ self.assertIn('f', function_scope_bar)
+ self.assertIn('g', function_scope_bar)
+ self.assertIn('h', function_scope_bar)
+ self.assertIn('i', function_scope_bar)
+
+ self.assertIsInstance(function_scope_foo['bar'], FunctionDefinition)
+ self.assertIsInstance(function_scope_foo['a'], Argument)
+ self.assertIsInstance(function_scope_foo['b'], Argument)
+ self.assertIsInstance(function_scope_foo['d'], Argument)
+ self.assertIsInstance(function_scope_foo['e'], Argument)
+
+ self.assertIsInstance(function_scope_bar['f'], Argument)
+ self.assertIsInstance(function_scope_bar['g'], Argument)
+ self.assertIsInstance(function_scope_bar['h'], Argument)
+ self.assertIsInstance(function_scope_bar['i'], Argument)
diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py
index 4dbaf23..fa093ce 100644
--- a/pyflakes/test/test_other.py
+++ b/pyflakes/test/test_other.py
@@ -1159,6 +1159,37 @@ class Test(TestCase):
def g(): foo = 'anything'; foo.is_used()
''')
+ def test_function_arguments(self):
+ """
+ Test to traverse ARG and ARGUMENT handler
+ """
+ self.flakes('''
+ def foo(a, b):
+ pass
+ ''')
+
+ self.flakes('''
+ def foo(a, b, c=0):
+ pass
+ ''')
+
+ self.flakes('''
+ def foo(a, b, c=0, *args):
+ pass
+ ''')
+
+ self.flakes('''
+ def foo(a, b, c=0, *args, **kwargs):
+ pass
+ ''')
+
+ @skipIf(version_info < (3, 3), "Python >= 3.3 only")
+ def test_function_arguments_python3(self):
+ self.flakes('''
+ def foo(a, b, c=0, *args, d=0, **kwargs):
+ pass
+ ''')
+
class TestUnusedAssignment(TestCase):
"""