Example #1
0
 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))
Example #2
0
 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))
Example #3
0
 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))
Example #4
0
    def test_variable_conflict(self):
        """Variables use the default conflict resolution outside of the pattern.

    Inside of the pattern, they use MERGE_EQUIVALENT_AST, but this is opaque to
    callers.
    """
        # The AllOf shouldn't make a difference, because the $x variable is just
        # a regular Bind() variable outside of the pattern, and merges via KEEP_LAST
        # per normal.
        self.assertEqual(
            self.get_all_match_strings(
                base_matchers.AllOf(syntax_matchers.StmtPattern('$x'),
                                    base_matchers.Bind('x')), '1'), ['1'])
Example #5
0
    def test_discards_unparseable_stmt(self):
        """Searching discards unparseable substitutions for statements.

    (Note: this only happens during fixedpoint computation.
    """
        fx = fixer.CombiningPythonFixer([
            fixer.SimplePythonFixer(
                message='',
                matcher=syntax_matchers.StmtPattern('raise e'),
                replacement=formatting.ShTemplate('x x x'),
                url='')
        ])
        self.assertEqual(
            list(search.find_iter(fx, 'raise e', 'foo.py', max_iterations=10)),
            [])
Example #6
0
    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', '""', 'x=1'
        ]:
            parsed = matcher.parse_ast(code, '<string>')
            stmt = parsed.tree.body[0]
            for extra_comment in ['', "# comment doesn't matter"]:
                with self.subTest(code=code, extra_comment=extra_comment):
                    self.assertEqual(
                        syntax_matchers.StmtPattern(
                            code + extra_comment).match(
                                matcher.MatchContext(parsed), stmt),
                        matcher.MatchInfo(
                            matcher.LexicalASTMatch(stmt, parsed.text,
                                                    stmt.first_token,
                                                    stmt.last_token)))
Example #7
0
 def from_pattern(
     cls, pattern: str, templates: Optional[Dict[str, formatting.Template]]
 ) -> "PyStmtRewritingSearcher":
     """Creates a searcher from a ``--mode=py.stmt`` template."""
     return cls.from_matcher(syntax_matchers.StmtPattern(pattern),
                             templates=templates)
Example #8
0
class NoCommentsTest(matcher_test_util.MatcherTestCase):
    _comments_source = '(a # comment\n + b)'
    _nocomments_source = '(a + b)'
    _including_comments_matcher = syntax_matchers.StmtPattern('a + b')
    _requiring_comments_matcher = lexical_matchers.HasComments(
        _including_comments_matcher)
    _banning_comments_matcher = lexical_matchers.NoComments(
        _including_comments_matcher)

    def test_outside_comment_irrelevant(self):
        for prefix in ['', '# earlier comment\n']:
            for suffix in ['', '  # trailing comment']:
                source_code = prefix + self._nocomments_source + suffix
                for m in [
                        self._including_comments_matcher,
                        self._requiring_comments_matcher,
                        self._banning_comments_matcher
                ]:
                    with self.subTest(source_code=source_code, matcher=m):
                        self.assertEqual(
                            self.get_all_match_strings(m, source_code),
                            self.get_all_match_strings(
                                m, self._nocomments_source))

    def test_interior_comments(self):
        for m in [
                self._including_comments_matcher,
                self._requiring_comments_matcher
        ]:
            with self.subTest(matcher=m):
                self.assertEqual(
                    self.get_all_match_strings(m, self._comments_source),
                    [self._comments_source])
        for m in [self._banning_comments_matcher]:
            with self.subTest(matcher=m):
                self.assertEqual(
                    self.get_all_match_strings(m, self._comments_source), [])

    def test_no_interior_comments(self):
        for m in [self._requiring_comments_matcher]:
            with self.subTest(matcher=m):
                self.assertEqual(
                    self.get_all_match_strings(m, self._nocomments_source), [])
        for m in [
                self._including_comments_matcher,
                self._banning_comments_matcher
        ]:
            with self.subTest(matcher=m):
                self.assertEqual(
                    self.get_all_match_strings(m, self._nocomments_source),
                    [self._nocomments_source])

    def test_incorrect_match_type(self):
        nonlexical_matcher = ast_matchers.Add()
        for m in [
                lexical_matchers.NoComments(nonlexical_matcher),
                lexical_matchers.HasComments(nonlexical_matcher)
        ]:
            with self.subTest(matcher=m):
                with self.assertRaises(TypeError):
                    self.get_all_match_strings(m, 'a + b')
Example #9
0
_NON_NONE_RETURN = matcher_.DebugLabeledMatcher(
    'Non-none return',
    ast_matchers.Return(value=base_matchers.Unless(
        base_matchers.AnyOf(base_matchers.Equals(None),
                            syntax_matchers.ExprPattern('None')))))

_NONE_RETURNS_FIXERS = [
    fixer.SimplePythonFixer(
        message=
        'If a function ever returns a value, all the code paths should have a return statement with a return value.',
        url=
        'https://refex.readthedocs.io/en/latest/guide/fixers/return_none.html',
        significant=False,
        category=_NONE_RETURNS_CATEGORY,
        matcher=base_matchers.AllOf(
            syntax_matchers.StmtPattern('return'),
            syntax_matchers.InNamedFunction(
                _function_containing(_NON_NONE_RETURN)),
            # Nested functions are too weird to consider right now.
            # TODO: Add matchers to match only the first ancestor
            # function and a way to use IsOrHasDescendant that doesn't recurse
            # into nested functions.
            base_matchers.Unless(
                syntax_matchers.InNamedFunction(
                    _function_containing(
                        syntax_matchers.NamedFunctionDefinition())))),
        replacement=syntactic_template.PythonStmtTemplate('return None'),
        example_fragment=textwrap.dedent("""
            def f(x):
              if x:
                return
Example #10
0
 def test_repeated_variable(self):
     self.assertEqual(
         self.get_all_match_strings(syntax_matchers.StmtPattern('$x + $x'),
                                    '1 + 1\n1 + 2\na + a\na + b'),
         ['1 + 1', 'a + a'])
Example #11
0
 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))
Example #12
0
 def test_multiple_statements(self):
     with self.assertRaises(ValueError):
         syntax_matchers.StmtPattern('{}; {}')
Example #13
0
 def test_no_statement(self):
     with self.assertRaises(ValueError):
         syntax_matchers.StmtPattern('')
Example #14
0
 def test_syntaxerror(self):
     with self.assertRaises(ValueError):
         syntax_matchers.StmtPattern('{')
Example #15
0
 def test_no_such_variable(self):
     with self.assertRaises(KeyError):
         syntax_matchers.StmtPattern('a', {'x': base_matchers.Anything()})
Example #16
0
 def _pattern_factory(pattern):
     return ast_matchers.Module(body=base_matchers.ItemsAre(
         [syntax_matchers.StmtPattern(pattern)]))