Example #1
0
def _ast_pattern(tree, variables):
    """Shared logic to compile an AST matcher recursively.

  Args:
    tree: the ast.expr/ast.stmt to match, or a list of ast nodes.
    variables: names to replace with submatchers instead of literally.

  Returns:
    A raw_aw matcher with any Name nodes from the variables map swapped out,
    as in ExprPattern.
  """
    # recursion isn't good because we can blow the stack for a ~1000-deep ++++foo,
    # but does that even happen IRL?
    # TODO: use a stack.
    if isinstance(tree, list):
        return base_matchers.ItemsAre(
            [_ast_pattern(e, variables) for e in tree])
    if not isinstance(tree, ast.AST):
        # e.g. the identifier for an ast.Name.
        return base_matchers.Equals(tree)
    if isinstance(tree, ast.Name):
        if tree.id in variables:
            return variables[tree.id]
    return getattr(ast_matchers,
                   type(tree).__name__)(
                       **{
                           field: _ast_pattern(getattr(tree, field), variables)
                           for field in type(tree)._fields
                           # Filter out variable ctx.
                           if field != 'ctx' or not isinstance(tree, ast.Name)
                       })
Example #2
0
 def test_match(self):
     container = [1]
     self.assertEqual(
         base_matchers.ItemsAre([base_matchers.Bind('a')
                                 ]).match(_FAKE_CONTEXT, container),
         matcher.MatchInfo(match.ObjectMatch(container),
                           {'a': matcher.BoundValue(match.ObjectMatch(1))}))
Example #3
0
    def test_key(self):
        shared = 'shared'
        once_any = base_matchers.Once(base_matchers.Anything(), key=shared)
        once_never = base_matchers.Once(base_matchers.Unless(
            base_matchers.Anything()),
                                        key=shared)

        # once_never reuses the cached result of once_any, because they share a key.
        m = base_matchers.ItemsAre([once_any, once_never])
        self.assertIsNotNone(m.match(_FAKE_CONTEXT.new(), [1, 2]))
Example #4
0
    def test_cached_same_match(self):
        """Once caches results even within the same match attempt.

    This isn't a very realistic test case, but just in case the matcher gets
    used in unexpectedly complex ways, it should be sound.
    """
        once_equals_one = base_matchers.Once(base_matchers.Equals(1))
        m = base_matchers.ItemsAre([once_equals_one] * 3)

        # Fails if it sees non-one entries:
        self.assertIsNone(m.match(_FAKE_CONTEXT.new(), [2, 2, 3]))
        # But if it sees a 1 first, then all subsequent calls will also match, even
        # if they are not one, because the result is cached.
        self.assertIsNotNone(m.match(_FAKE_CONTEXT.new(), [1, 2, 3]))
Example #5
0
             # matched function call. This isn't perfect, since it
             # will still match `foo(func_that_can_raise())` and it
             # won't match cases where a try block has more than one
             # statement but only one can obviously raise an error
             # (for instance, a function call followed by continue,
             # break, or return).
             body=base_matchers.AllOf(
                 base_matchers.ItemsAre([
                     base_matchers.AllOf(
                         base_matchers.AnyOf(
                             ast_matchers.Return(
                                 value=ast_matchers.Call(
                                     func=base_matchers.Bind('func'))),
                             ast_matchers.Expr(value=ast_matchers.Call(
                                 func=base_matchers.Bind('func'))),
                             ast_matchers.Assign(
                                 value=ast_matchers.Call(
                                     func=base_matchers.Bind('func'))),
                         ),
                         # TODO: Escape double quotes in the
                         # replacement
                         base_matchers.Unless(
                             base_matchers.MatchesRegex(r'.+".+'))),
                 ]), ), ),
     )),
 replacement=dict(
     # TODO: Wrapping $func in quotes prevents the linter from
     # complaining if the outer quotes don't match the rest of the file,
     # since different quote style is allowed everywhere if it avoids
     # escaping. Is there a better option to keep the linter happy?
     # Also, in the case that all the arguments to $func were variables,
Example #6
0
 def test_submatcher_wrong(self):
     self.assertIsNone(
         base_matchers.ItemsAre([
             base_matchers.Unless(base_matchers.Anything())
         ]).match(_FAKE_CONTEXT, [1]))
Example #7
0
 def test_too_long(self):
     self.assertIsNone(base_matchers.ItemsAre([]).match(_FAKE_CONTEXT, [1]))
Example #8
0
 def test_too_short(self):
     self.assertIsNone(
         base_matchers.ItemsAre([base_matchers.Anything()
                                 ]).match(_FAKE_CONTEXT, []))
Example #9
0
 def _pattern_factory(pattern):
     return ast_matchers.Module(body=base_matchers.ItemsAre(
         [syntax_matchers.StmtPattern(pattern)]))
Example #10
0
 def _pattern_factory(pattern):
     return ast_matchers.Module(body=base_matchers.ItemsAre(
         [ast_matchers.Expr(value=syntax_matchers.ExprPattern(pattern))]))