From b5c362fa51e928505ec2bf7aeb0b7bcc3715cdc6 Mon Sep 17 00:00:00 2001 From: Caleb Donovick Date: Thu, 24 Sep 2020 14:38:56 -0700 Subject: [PATCH 1/6] Add failing test cases --- .../tests/test_expression_context_provider.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/libcst/metadata/tests/test_expression_context_provider.py b/libcst/metadata/tests/test_expression_context_provider.py index 439e6e3ca..be282f2f1 100644 --- a/libcst/metadata/tests/test_expression_context_provider.py +++ b/libcst/metadata/tests/test_expression_context_provider.py @@ -376,3 +376,26 @@ def test_for(self) -> None: }, ) ) + + def test_class(self) -> None: + wrapper = MetadataWrapper(parse_module("class Foo: pass")) + wrapper.visit( + DependentVisitor( + test=self, + name_to_context={ + "Foo": ExpressionContext.STORE, + }, + ) + ) + + + def test_function(self) -> None: + wrapper = MetadataWrapper(parse_module("def foo(): pass")) + wrapper.visit( + DependentVisitor( + test=self, + name_to_context={ + "foo": ExpressionContext.STORE, + }, + ) + ) From c3faed25961e0a7e4c272f0038b619704131bf35 Mon Sep 17 00:00:00 2001 From: Caleb Donovick Date: Thu, 24 Sep 2020 14:56:20 -0700 Subject: [PATCH 2/6] mark *Def names as STORE --- .../metadata/expression_context_provider.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/libcst/metadata/expression_context_provider.py b/libcst/metadata/expression_context_provider.py index b6ee1a35b..72f1cebd6 100644 --- a/libcst/metadata/expression_context_provider.py +++ b/libcst/metadata/expression_context_provider.py @@ -155,6 +155,29 @@ def visit_List(self, node: cst.List) -> Optional[bool]: def visit_StarredElement(self, node: cst.StarredElement) -> Optional[bool]: self.provider.set_metadata(node, self.context) + def visit_ClassDef(self, node: cst.ClassDef) -> Optional[bool]: + node.name.visit( + ExpressionContextVisitor(self.provider, ExpressionContext.STORE) + ) + node.body.visit(self) + for base in node.bases: + base.visit(self) + for keyword in node.keywords: + keyword.visit(self) + for decorator in node.decorators: + decorator.visit(self) + return False + + def visit_FunctionDef(self, node: cst.FunctionDef) -> Optional[bool]: + node.name.visit( + ExpressionContextVisitor(self.provider, ExpressionContext.STORE) + ) + node.params.visit(self) + node.body.visit(self) + for decorator in node.decorators: + decorator.visiet(self) + return False + class ExpressionContextProvider(BatchableMetadataProvider[Optional[ExpressionContext]]): """ From 3872f4b1e0031093b2fb3f61a1e6b16d78bfe0cf Mon Sep 17 00:00:00 2001 From: Caleb Donovick Date: Mon, 28 Sep 2020 11:36:05 -0700 Subject: [PATCH 3/6] Update libcst/metadata/expression_context_provider.py Co-authored-by: Jimmy Lai --- libcst/metadata/expression_context_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcst/metadata/expression_context_provider.py b/libcst/metadata/expression_context_provider.py index 72f1cebd6..1e4879fc5 100644 --- a/libcst/metadata/expression_context_provider.py +++ b/libcst/metadata/expression_context_provider.py @@ -175,7 +175,7 @@ def visit_FunctionDef(self, node: cst.FunctionDef) -> Optional[bool]: node.params.visit(self) node.body.visit(self) for decorator in node.decorators: - decorator.visiet(self) + decorator.visit(self) return False From 092c532fb1bc1edd7d6d33000d7e14c9ac5720ae Mon Sep 17 00:00:00 2001 From: Caleb Donovick Date: Mon, 28 Sep 2020 11:40:21 -0700 Subject: [PATCH 4/6] Fix lint --- libcst/metadata/tests/test_expression_context_provider.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libcst/metadata/tests/test_expression_context_provider.py b/libcst/metadata/tests/test_expression_context_provider.py index be282f2f1..4d4488bee 100644 --- a/libcst/metadata/tests/test_expression_context_provider.py +++ b/libcst/metadata/tests/test_expression_context_provider.py @@ -388,7 +388,6 @@ def test_class(self) -> None: ) ) - def test_function(self) -> None: wrapper = MetadataWrapper(parse_module("def foo(): pass")) wrapper.visit( From 970456dee6ca65dd9cf0c2e4435fe78866caa047 Mon Sep 17 00:00:00 2001 From: Caleb Donovick Date: Mon, 28 Sep 2020 12:19:05 -0700 Subject: [PATCH 5/6] Visit annotations and params --- libcst/metadata/expression_context_provider.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libcst/metadata/expression_context_provider.py b/libcst/metadata/expression_context_provider.py index 1e4879fc5..b06ba1132 100644 --- a/libcst/metadata/expression_context_provider.py +++ b/libcst/metadata/expression_context_provider.py @@ -176,6 +176,21 @@ def visit_FunctionDef(self, node: cst.FunctionDef) -> Optional[bool]: node.body.visit(self) for decorator in node.decorators: decorator.visit(self) + returns = node.returns + if returns: + returns.visit(self) + return False + + def visit_Param(self, node: cst.Param) -> Optional[bool]: + node.name.visit( + ExpressionContextVisitor(self.provider, ExpressionContext.STORE) + ) + annotation = node.annotation + if annotation: + annotation.visit(self) + default = node.default + if default: + default.visit(self) return False From 8881a22936d347fa3e7af8e5732adee54dadb548 Mon Sep 17 00:00:00 2001 From: Caleb Donovick Date: Mon, 28 Sep 2020 12:20:55 -0700 Subject: [PATCH 6/6] Fix and extend tests --- libcst/matchers/tests/test_findall.py | 40 +++++++++++++++++-- .../tests/test_matchers_with_metadata.py | 8 ++-- .../tests/test_expression_context_provider.py | 17 +++++++- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/libcst/matchers/tests/test_findall.py b/libcst/matchers/tests/test_findall.py index ade99743c..95233f536 100644 --- a/libcst/matchers/tests/test_findall.py +++ b/libcst/matchers/tests/test_findall.py @@ -75,7 +75,15 @@ def foo(bar: int) -> bool: meta.ExpressionContextProvider, meta.ExpressionContext.STORE ), ) - self.assertNodeSequenceEqual(booleans, [cst.Name("a"), cst.Name("b")]) + self.assertNodeSequenceEqual( + booleans, + [ + cst.Name("a"), + cst.Name("b"), + cst.Name("foo"), + cst.Name("bar"), + ], + ) # Test that we can provide an explicit resolver and tree booleans = findall( @@ -85,7 +93,15 @@ def foo(bar: int) -> bool: ), metadata_resolver=wrapper, ) - self.assertNodeSequenceEqual(booleans, [cst.Name("a"), cst.Name("b")]) + self.assertNodeSequenceEqual( + booleans, + [ + cst.Name("a"), + cst.Name("b"), + cst.Name("foo"), + cst.Name("bar"), + ], + ) # Test that failing to provide metadata leads to no match booleans = findall( @@ -127,7 +143,15 @@ def foo(bar: int) -> bool: wrapper = meta.MetadataWrapper(module) visitor = TestVisitor() wrapper.visit(visitor) - self.assertNodeSequenceEqual(visitor.results, [cst.Name("a"), cst.Name("b")]) + self.assertNodeSequenceEqual( + visitor.results, + [ + cst.Name("a"), + cst.Name("b"), + cst.Name("foo"), + cst.Name("bar"), + ], + ) def test_findall_with_transformers(self) -> None: # Find all assignments in a tree @@ -160,7 +184,15 @@ def foo(bar: int) -> bool: wrapper = meta.MetadataWrapper(module) visitor = TestTransformer() wrapper.visit(visitor) - self.assertNodeSequenceEqual(visitor.results, [cst.Name("a"), cst.Name("b")]) + self.assertNodeSequenceEqual( + visitor.results, + [ + cst.Name("a"), + cst.Name("b"), + cst.Name("foo"), + cst.Name("bar"), + ], + ) class MatchersExtractAllTest(UnitTest): diff --git a/libcst/matchers/tests/test_matchers_with_metadata.py b/libcst/matchers/tests/test_matchers_with_metadata.py index fcc86f66d..a41913d94 100644 --- a/libcst/matchers/tests/test_matchers_with_metadata.py +++ b/libcst/matchers/tests/test_matchers_with_metadata.py @@ -492,7 +492,7 @@ def bar() -> int: visitor = TestVisitor() module.visit(visitor) - self.assertEqual(visitor.match_names, {"a", "b", "c"}) + self.assertEqual(visitor.match_names, {"a", "b", "c", "foo", "bar"}) def test_matches_on_transformers(self) -> None: # Set up a simple visitor that has a metadata dependency, try to use it in matchers. @@ -533,7 +533,7 @@ def bar() -> int: visitor = TestTransformer() module.visit(visitor) - self.assertEqual(visitor.match_names, {"a", "b", "c"}) + self.assertEqual(visitor.match_names, {"a", "b", "c", "foo", "bar"}) def test_matches_decorator_on_visitors(self) -> None: # Set up a simple visitor that has a metadata dependency, try to use it in matchers. @@ -573,7 +573,7 @@ def bar() -> int: visitor = TestVisitor() module.visit(visitor) - self.assertEqual(visitor.match_names, {"a", "b", "c"}) + self.assertEqual(visitor.match_names, {"a", "b", "c", "foo", "bar"}) def test_matches_decorator_on_transformers(self) -> None: # Set up a simple visitor that has a metadata dependency, try to use it in matchers. @@ -613,4 +613,4 @@ def bar() -> int: visitor = TestTransformer() module.visit(visitor) - self.assertEqual(visitor.match_names, {"a", "b", "c"}) + self.assertEqual(visitor.match_names, {"a", "b", "c", "foo", "bar"}) diff --git a/libcst/metadata/tests/test_expression_context_provider.py b/libcst/metadata/tests/test_expression_context_provider.py index 4d4488bee..25cc1d0da 100644 --- a/libcst/metadata/tests/test_expression_context_provider.py +++ b/libcst/metadata/tests/test_expression_context_provider.py @@ -4,6 +4,7 @@ # LICENSE file in the root directory of this source tree. +from textwrap import dedent from typing import Dict, Optional, cast import libcst as cst @@ -378,23 +379,35 @@ def test_for(self) -> None: ) def test_class(self) -> None: - wrapper = MetadataWrapper(parse_module("class Foo: pass")) + code = """ + class Foo(Bar): + x = y + """ + wrapper = MetadataWrapper(parse_module(dedent(code))) wrapper.visit( DependentVisitor( test=self, name_to_context={ "Foo": ExpressionContext.STORE, + "Bar": ExpressionContext.LOAD, + "x": ExpressionContext.STORE, + "y": ExpressionContext.LOAD, }, ) ) def test_function(self) -> None: - wrapper = MetadataWrapper(parse_module("def foo(): pass")) + code = """def foo(x: int = y) -> None: pass""" + wrapper = MetadataWrapper(parse_module(code)) wrapper.visit( DependentVisitor( test=self, name_to_context={ "foo": ExpressionContext.STORE, + "x": ExpressionContext.STORE, + "int": ExpressionContext.LOAD, + "y": ExpressionContext.LOAD, + "None": ExpressionContext.LOAD, }, ) )