def test_restrictions(self): parsed = matcher.parse_ast('a = 1\na = 2', '<string>') stmt_match, stmt_nomatch = parsed.tree.body m = syntax_matchers.StmtPattern( 'a = $name', {'name': syntax_matchers.ExprPattern('1')}) self.assertIsNotNone(m.match(matcher.MatchContext(parsed), stmt_match)) self.assertIsNone(m.match(matcher.MatchContext(parsed), stmt_nomatch))
def test_restrictions(self): parsed = matcher.parse_ast('1\n2', '<string>') expr_match = parsed.tree.body[0].value expr_nomatch = parsed.tree.body[1].value m = syntax_matchers.ExprPattern( '$name', {'name': syntax_matchers.ExprPattern('1')}) self.assertIsNotNone(m.match(matcher.MatchContext(parsed), expr_match)) self.assertIsNone(m.match(matcher.MatchContext(parsed), expr_nomatch))
def test_type_only(self): parsed, e = expression('~a') self.assertEqual( ast_matchers.UnaryOp().match(matcher.MatchContext(parsed), e), matcher.MatchInfo( matcher.LexicalASTMatch(e, parsed.text, e.first_token, e.last_token)))
def test_ancestor(self): """The matcher won't traverse into child nodes.""" parsed = matcher.parse_ast('~a', '<string>') self.assertIsNone( ast_matchers.UnaryOp( op=base_matchers.Unless(base_matchers.Anything())).match( matcher.MatchContext(parsed), parsed.tree.body[0]))
def test_num(self, num_matcher): for s in '0', '0.0', '0j': with self.subTest(s=s): parsed = matcher.parse_ast(s, '<string>') self.assertIsNotNone( num_matcher.match( matcher.MatchContext(parsed), parsed.tree.body[0].value))
def test_non_lexical_node(self): """The matcher doesn't return lexical data for non-lexical AST nodes.""" parsed, binop = expression('a + b') add = binop.op self.assertEqual( ast_matchers.Add().match(matcher.MatchContext(parsed), add), matcher.MatchInfo(match.ObjectMatch(add)))
def test_named_constant(self, constant): parsed = matcher.parse_ast(str(constant), '<string>') for m in ast_matchers.NameConstant(), ast_matchers.NameConstant( value=constant): with self.subTest(matcher=m): self.assertIsNotNone( m.match(matcher.MatchContext(parsed), parsed.tree.body[0].value))
def test_nonvariable_name_fails(self): """Names are only treated as variables, or anything weird, on request.""" parsed = matcher.parse_ast('3', '<string>') stmt = parsed.tree.body[0] self.assertIsNone( syntax_matchers.StmtPattern('name').match( matcher.MatchContext(parsed), stmt))
def test_renamed_import(self): any_m = base_matchers.Anything() m_success = syntax_matchers.WithTopLevelImport(any_m, 'os', 'renamed') m_fail = syntax_matchers.WithTopLevelImport(any_m, 'os') context = matcher.MatchContext( matcher.parse_ast('import os as renamed')) self.assertIsNotNone(m_success.match(context, 1)) self.assertIsNone(m_fail.match(context, 1))
def test_renamed_fromimport(self, import_stmt): any_m = base_matchers.Anything() m_success = syntax_matchers.WithTopLevelImport(any_m, 'os.path', 'renamed') m_fail = syntax_matchers.WithTopLevelImport(any_m, 'os.path') context = matcher.MatchContext(matcher.parse_ast(import_stmt)) self.assertIsNotNone(m_success.match(context, 1)) self.assertIsNone(m_fail.match(context, 1))
def test_variable_name(self): parsed = matcher.parse_ast('3', '<string>') expr = parsed.tree.body[0].value expr_match = matcher.LexicalASTMatch(expr, parsed.text, expr.first_token, expr.last_token) self.assertEqual( syntax_matchers.ExprPattern('$name').match( matcher.MatchContext(parsed), expr), matcher.MatchInfo(expr_match, {'name': matcher.BoundValue(expr_match)}))
def empty_context(): """Returns a new match context for some empty file. The return value is suitable for use with matchers, e.g.: >>> from refex.python.matchers import base_matchers >>> base_matchers.Anything().match(empty_context(), object()) MatchInfo(...) """ return matcher.MatchContext(matcher.parse_ast(''))
def test_explicit_anything(self): parsed, e = expression('~a') self.assertEqual( ast_matchers.UnaryOp( op=base_matchers.Anything(), operand=base_matchers.Anything()).match( matcher.MatchContext(parsed), e), matcher.MatchInfo( matcher.LexicalASTMatch(e, parsed.text, e.first_token, e.last_token)))
def test_complex_variable(self): parsed = matcher.parse_ast('foo + bar', '<string>') expr = parsed.tree.body[0].value self.assertEqual( syntax_matchers.ExprPattern('foo + $name').match( matcher.MatchContext(parsed), expr), matcher.MatchInfo(matcher.LexicalASTMatch(expr, parsed.text, expr.first_token, expr.last_token), bindings=mock.ANY))
def test_lvalue_variable(self): parsed = matcher.parse_ast('a = b', '<string>') stmt = parsed.tree.body[0] self.assertEqual( syntax_matchers.StmtPattern('$x = $y').match( matcher.MatchContext(parsed), stmt), matcher.MatchInfo(matcher.LexicalASTMatch(stmt, parsed.text, stmt.first_token, stmt.last_token), bindings=mock.ANY))
def test_fully_specified_matcher(self): parsed, e = expression('~a') self.assertEqual( ast_matchers.UnaryOp( op=ast_matchers.Invert(), operand=ast_matchers.Name(ctx=ast_matchers.Load())).match( matcher.MatchContext(parsed), e), matcher.MatchInfo( matcher.LexicalASTMatch(e, parsed.text, e.first_token, e.last_token)))
def test_has_nested_import(self, import_stmt): any_m = base_matchers.Anything() matchers = [ syntax_matchers.WithTopLevelImport(any_m, 'os.path'), syntax_matchers.WithTopLevelImport(any_m, 'os.path', 'path'), ] context = matcher.MatchContext(matcher.parse_ast(import_stmt)) for m in matchers: with self.subTest(m=m): self.assertIsNotNone(m.match(context, 1))
def test_identical_patterns(self): """Tests that patterns match themselves when not parameterized. Many cases (e.g. None) are interesting for 2/3 compatibility, because the AST changes in Python 3. syntax_matchers gives an easy way to get cross-version compatibility. """ for code in ['None', '{}', '[]', '{1:2, 3:4}', 'lambda a: a', '""']: parsed = matcher.parse_ast(code, '<string>') expr = parsed.tree.body[0].value for extra_comment in ['', "# comment doesn't matter"]: with self.subTest(code=code, extra_comment=extra_comment): self.assertEqual( syntax_matchers.ExprPattern( code + extra_comment).match( matcher.MatchContext(parsed), expr), matcher.MatchInfo( matcher.LexicalASTMatch(expr, parsed.text, expr.first_token, expr.last_token)))
def test_num_non_number(self, non_number): parsed = matcher.parse_ast(non_number, '<string>') self.assertIsNone(ast_matchers.Num().match( matcher.MatchContext(parsed), parsed.tree.body[0].value))
def test_type_mismatch(self): parsed, e = expression('a + b') self.assertIsNone(ast_matchers.UnaryOp().match( matcher.MatchContext(parsed), e))
def test_submatcher_fail(self): parsed, e = expression('~a') self.assertIsNone( ast_matchers.UnaryOp( op=base_matchers.Unless(base_matchers.Anything())).match( matcher.MatchContext(parsed), e))
def test_missing_import(self, import_stmt): any_m = base_matchers.Anything() m = syntax_matchers.WithTopLevelImport(any_m, 'os.path') context = matcher.MatchContext(matcher.parse_ast(import_stmt)) self.assertIsNone(m.match(context, 1))
def test_dict_wrong_order(self): parsed = matcher.parse_ast('{1:2, 3:4}', '<string>') stmt = parsed.tree.body[0] self.assertIsNone( syntax_matchers.StmtPattern('{3:4, 1:2}').match( matcher.MatchContext(parsed), stmt))
def test_bytes_non_bytes(self): parsed = matcher.parse_ast('"string"', '<string>') self.assertIsNone(ast_matchers.Bytes().match( matcher.MatchContext(parsed), parsed.tree.body[0].value))
def test_named_constant_non_named_constant(self): parsed = matcher.parse_ast('1', '<string>') self.assertIsNone(ast_matchers.NameConstant().match( matcher.MatchContext(parsed), parsed.tree.body[0].value))
def test_string(self, str_matcher): parsed = matcher.parse_ast('""', '<string>') self.assertIsNotNone( str_matcher.match( matcher.MatchContext(parsed), parsed.tree.body[0].value))
def test_ellipsis_non_ellipsis(self): parsed = matcher.parse_ast('1', '<string>') self.assertIsNone(ast_matchers.Ellipsis().match( matcher.MatchContext(parsed), parsed.tree.body[0].value))
def test_string_non_string(self): parsed = matcher.parse_ast('2', '<string>') self.assertIsNone(ast_matchers.Str().match( matcher.MatchContext(parsed), parsed.tree.body[0].value))
import ast import collections import textwrap from absl.testing import absltest from absl.testing import parameterized import attr from refex import formatting from refex import match from refex import parsed_file from refex.python import matcher from refex.python.matchers import ast_matchers from refex.python.matchers import base_matchers _FAKE_CONTEXT = matcher.MatchContext(matcher.parse_ast('', 'foo.py')) @attr.s(frozen=True) class _SubmatcherAttribsClass(object): submatcher = matcher.submatcher_attrib(default=base_matchers.Anything()) submatcher_list = matcher.submatcher_list_attrib( default=(base_matchers.Anything(), )) class SubmatcherAttribTest(parameterized.TestCase): @parameterized.parameters(42, matcher.ImplicitEquals(42)) def test_coerce(self, value): self.assertEqual(matcher.coerce(value), matcher.ImplicitEquals(42)) def test_coerce_nounwrap(self):
def test_bytes(self, kwargs): bytes_matcher = ast_matchers.Bytes(**kwargs) # hack for py2 parsed = matcher.parse_ast('b""', '<string>') self.assertIsNotNone( bytes_matcher.match( matcher.MatchContext(parsed), parsed.tree.body[0].value))