def __extract_variable_name_type(self, node: cst.AnnAssign): """ Extracts a variable's identifier name and its type annotation """ return match.extract( node, match.AnnAssign( # Annotated Assignment target=match.OneOf( match.Name( # Variable name of assignment (only one) value=match.SaveMatchedNode( # Save result match.MatchRegex( r'(.)+'), # Match any string literal "name")), # This extracts variables inside __init__ which typically starts with self (e.g. self.x:int=2) match.Attribute( value=match.Name(value=match.SaveMatchedNode( match.MatchRegex(r'(.)+'), "obj_name" # Object name )), attr=match.Name( match.SaveMatchedNode(match.MatchRegex(r'(.)+'), "name")), )), annotation=match.SaveMatchedNode( # Save result match.DoNotCare(), # Match any string literal "type")))
def test_and_operator_matcher_true(self) -> None: # Match on True identifier in roundabout way. self.assertTrue( matches(cst.Name("True"), m.Name() & m.Name(value=m.MatchRegex(r"True"))) ) # Match in a really roundabout way that verifies the __or__ behavior on # AllOf itself. self.assertTrue( matches( cst.Name("True"), m.Name() & m.Name(value=m.MatchRegex(r"True")) & m.Name("True"), ) ) # Verify that MatchIfTrue works with __and__ behavior properly. self.assertTrue( matches( cst.Name("True"), m.MatchIfTrue(lambda x: isinstance(x, cst.Name)) & m.Name(value=m.MatchRegex(r"True")), ) ) self.assertTrue( matches( cst.Name("True"), m.Name(value=m.MatchRegex(r"True")) & m.MatchIfTrue(lambda x: isinstance(x, cst.Name)), ) )
def test_and_matcher_true(self) -> None: # Match on True identifier in roundabout way. self.assertTrue( matches( cst.Name("True"), m.AllOf(m.Name(), m.Name(value=m.MatchRegex(r"True"))) ) ) self.assertTrue( matches( cst.Call( func=cst.Name("foo"), args=( cst.Arg(cst.Integer("1")), cst.Arg(cst.Integer("2")), cst.Arg(cst.Integer("3")), ), ), m.Call( func=m.Name("foo"), args=m.AllOf( (m.Arg(), m.Arg(), m.Arg()), ( m.Arg(m.Integer("1")), m.Arg(m.Integer("2")), m.Arg(m.Integer("3")), ), ), ), ) )
def test_does_not_match_true(self) -> None: # Match on any call that takes one argument that isn't the value None. self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Name("True")), )), m.Call(args=(m.Arg(value=m.DoesNotMatch(m.Name("None"))), )), )) self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Integer("1")), )), m.Call(args=(m.DoesNotMatch(m.Arg(m.Name("None"))), )), )) self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Integer("1")), )), m.Call(args=m.DoesNotMatch((m.Arg(m.Integer("2")), ))), )) # Match any call that takes an argument which isn't True or False. self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Integer("1")), )), m.Call(args=(m.Arg(value=m.DoesNotMatch( m.OneOf(m.Name("True"), m.Name("False")))), )), )) # Match any name node that doesn't match the regex for True self.assertTrue( matches( libcst.Name("False"), m.Name(value=m.DoesNotMatch(m.MatchRegex(r"True"))), ))
def __extract_variable_name(self, node: cst.AssignTarget): extracted_var_names = match.extract( node, match.AssignTarget( # Assignment operator target=match.OneOf( # Two cases exist match.Name( # Single target value=match.SaveMatchedNode( # Save result match.MatchRegex( r'(.)+'), # Match any string literal "name")), match.Tuple( # Multi-target elements=match.SaveMatchedNode( # Save result match.DoNotCare(), # Type of list "names")), # This extracts variables inside __init__ without type annotation (e.g. self.x=2) match.Attribute( value=match.Name(value=match.SaveMatchedNode( match.MatchRegex(r'(.)+'), "obj_name" # Object name )), attr=match.Name( match.SaveMatchedNode(match.MatchRegex(r'(.)+'), "name")), )))) if extracted_var_names is not None: if "name" in extracted_var_names: t = self.__get_type_from_metadata(node.target) extracted_var_names['type'] = (t, INF_TYPE_ANNOT if t else UNK_TYPE_ANNOT) return extracted_var_names elif "names" in extracted_var_names: return { 'names': self.__extract_names_multi_assign( list(extracted_var_names['names'])) } else: return extracted_var_names
def test_does_not_match_operator_true(self) -> None: # Match on any call that takes one argument that isn't the value None. self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Name("True")), )), m.Call(args=(m.Arg(value=~m.Name("None")), )), )) self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Integer("1")), )), m.Call(args=(~m.Arg(m.Name("None")), )), )) # Match any call that takes an argument which isn't True or False. self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Integer("1")), )), m.Call(args=(m.Arg( value=~(m.Name("True") | m.Name("False"))), )), )) self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Name("None")), )), m.Call(args=(m.Arg(value=(~m.Name("True")) & (~m.Name("False"))), )), )) # Roundabout way to verify that or operator works with inverted nodes. self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Name("False")), )), m.Call(args=(m.Arg(value=(~m.Name("True")) | (~m.Name("True"))), )), )) # Roundabout way to verify that inverse operator works properly on AllOf. self.assertTrue( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Integer("1")), )), m.Call(args=(m.Arg(value=~(m.Name() & m.Name("True"))), )), )) # Match any name node that doesn't match the regex for True self.assertTrue( matches(libcst.Name("False"), m.Name(value=~m.MatchRegex(r"True"))))
def visit_ImportAlias(self, node: cst.ImportAlias): """ Extracts imports. Even if an Import has no alias, the Import node will still have an ImportAlias with it's real name. """ # Extract information from the Import Alias node. # Both the (real) import name and the alias are extracted. # Result is returned as a dictionary with a name:node KV pair, # and an alias:name KV pair if an alias is present. import_info = match.extract( node, match.ImportAlias( asname=match.AsName( # Attempt to match alias name=match.Name( # Check for alias name value=match.SaveMatchedNode( # Save the alias name match.MatchRegex( r'(.)+'), # Match any string literal "alias"))) | ~match.AsName(), # If there is no AsName, we should still match. We negate AsName to and OR to form a tautology. name=match.SaveMatchedNode( # Match & save name of import match.DoNotCare(), "name"))) # Add import if a name could be extracted. if "name" in import_info: # Append import name to imports list. # We convert the ImportAlias node to the code representation for simplified conversion # of multi-level imports (e.g. import.x.y.z) # TODO: This might be un-needed after implementing import type extraction change. # TODO: So far, no differentiation between import and from imports. import_name = self.__convert_node_to_code(import_info["name"]) import_name = self.__clean_string_whitespace(import_name) self.imports.append(import_name) if "alias" in import_info: import_name = self.__clean_string_whitespace(import_info["alias"]) self.imports.append(import_name) # No need to traverse children, as we already performed the matching. return False
def test_does_not_match_operator_false(self) -> None: # Match on any call that takes one argument that isn't the value None. self.assertFalse( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Name("None")), )), m.Call(args=(m.Arg(value=~m.Name("None")), )), )) self.assertFalse( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Integer("1")), )), m.Call(args=((~m.Arg(m.Integer("1"))), )), )) # Match any call that takes an argument which isn't True or False. self.assertFalse( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Name("False")), )), m.Call(args=(m.Arg( value=~(m.Name("True") | m.Name("False"))), )), )) # Roundabout way of verifying ~(x&y) behavior. self.assertFalse( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Name("False")), )), m.Call(args=(m.Arg(value=~(m.Name() & m.Name("False"))), )), )) # Roundabout way of verifying (~x)|(~y) behavior self.assertFalse( matches( libcst.Call(libcst.Name("foo"), (libcst.Arg(libcst.Name("True")), )), m.Call(args=(m.Arg(value=(~m.Name("True")) | (~m.Name("True"))), )), )) # Match any name node that doesn't match the regex for True self.assertFalse( matches(libcst.Name("True"), m.Name(value=~m.MatchRegex(r"True"))))
def __extract_assign_newtype(self, node: cst.Assign): """ Attempts extracting a NewType declaration from the provided Assign node. If the Assign node corresponds to a NewType assignment, the NewType name is added to the class definitions of the Visitor. """ # Define matcher to extract NewType assignment matcher_newtype = match.Assign( targets=[ # Check the assign targets match.AssignTarget( # There should only be one target target=match.Name( # Check target name value=match.SaveMatchedNode( # Save target name match.MatchRegex( r'(.)+'), # Match any string literal "type"))) ], value=match.Call( # We are examining a function call func=match.Name( # Function must have a name value="NewType" # Name must be 'NewType' ), args=[ match.Arg( # Check first argument value=match.SimpleString( ) # First argument must be the name for the type ), match.ZeroOrMore( ) # We allow any number of arguments after by def. of NewType ])) extracted_type = match.extract(node, matcher_newtype) if extracted_type is not None: # Append the additional type to the list # TODO: Either rename class defs, or create new list for additional types self.class_defs.append(extracted_type["type"].strip("\'"))
def test_regex_matcher_false(self) -> None: # Fail to match due to incorrect value on Name. self.assertFalse(matches(cst.Name("foo"), m.Name(value=m.MatchRegex(r".*a.*"))))
def test_regex_matcher_true(self) -> None: # Match based on identical attributes. self.assertTrue(matches(cst.Name("foo"), m.Name(value=m.MatchRegex(r".*o.*"))))