Пример #1
0
 def _match(self, context, candidate):
     parent = context.parsed_file.nav.get_parent(candidate)
     if parent is None:
         return None
     m = self._submatcher.match(context, parent)
     if m is None:
         return None
     return matcher.MatchInfo(
         matcher.create_match(context.parsed_file, candidate), m.bindings)
Пример #2
0
 def _match(self, context, candidate):
     for child in _ast_children(candidate):
         m = self._submatcher.match(context, child)
         if m is None:
             continue
         return matcher.MatchInfo(
             matcher.create_match(context.parsed_file, candidate),
             m.bindings)
     return None
Пример #3
0
    def _match(self, context, candidate):

        # Not all ast-nodes have the lineno attr, only expressions and statements
        # (so modules and some other weird ones don't).
        if getattr(candidate, 'lineno', None) in self.lines:
            return matcher.MatchInfo(
                matcher.create_match(context.parsed_file, candidate))
        else:
            return None
Пример #4
0
 def _match(self, context, candidate):
     rewritten = self.rewrite(context, candidate)
     if rewritten is None:
         return None
     else:
         return matcher.MatchInfo.from_diff(self._metavariable_prefix,
                                            context.parsed_file.text,
                                            rewritten,
                                            match=matcher.create_match(
                                                context.parsed_file,
                                                candidate))
Пример #5
0
 def _match(self, context, candidate):
     try:
         items = iter(candidate)
     except TypeError:
         return None
     for can in items:
         m = self._submatcher.match(context, can)
         if m is not None:
             return matcher.MatchInfo(
                 matcher.create_match(context.parsed_file, candidate),
                 m.bindings)
     return None
Пример #6
0
 def _match(self, context, candidate):
     try:
         sub_candidate = candidate[self._index]
     except (LookupError, TypeError):
         return None
     else:
         m = self._submatcher.match(context, sub_candidate)
         if m is None:
             return None
         return matcher.MatchInfo(
             matcher.create_match(context.parsed_file, candidate),
             m.bindings)
Пример #7
0
 def _constant_match(context, candidate, value_matcher, value_types):
     if type(candidate) != ast.Constant:  # pylint: disable=unidiomatic-typecheck
         return None
     # note: not isinstance. The only concrete subclass that can occur in a
     # Constant AST is bool (which subclasses int). And in that case, we actually
     # don't want to include it -- Num() should not match `True`!.
     # Instead, all types must be listed out explicitly.
     if type(candidate.value) not in value_types:
         return None
     result = value_matcher.match(context, candidate.value)
     if result is None:
         return None
     return matcher.MatchInfo(
         matcher.create_match(context.parsed_file, candidate),
         result.bindings)
Пример #8
0
    def _match(self, context, candidate):
        parent = candidate
        while True:
            parent = context.parsed_file.nav.get_parent(parent)
            if parent is None:
                return None

            m = self._first_ancestor.match(context, parent)
            if m is not None:
                break

        ancestor = m.match.matched
        m2 = self._also_matches.match(context, ancestor)
        if m2 is None:
            return None
        return matcher.MatchInfo(
            matcher.create_match(context.parsed_file, candidate),
            matcher.merge_bindings(m.bindings, m2.bindings))
Пример #9
0
    def test_inclusivity(self):
        parsed = matcher.parse_ast('[hello, world]')
        node = parsed.tree.body[0]
        m = matcher.create_match(parsed, node)

        # Putting this all in one test case also verifies that attr.evolve()
        # works as expected (e.g. doesn't interact poorly with cached_property)

        with self.subTest(inclusive='both'):
            self.assertEqual(m.string, '[hello, world]')

        with self.subTest(inclusive='first'):
            m = attr.evolve(m, include_first=True, include_last=False)
            self.assertEqual(m.string, '[hello, world')

        with self.subTest(inclusive='last'):
            m = attr.evolve(m, include_first=False, include_last=True)
            self.assertEqual(m.string, 'hello, world]')

        with self.subTest(inclusive='neither'):
            m = attr.evolve(m, include_first=False, include_last=False)
            self.assertEqual(m.string, 'hello, world')
Пример #10
0
 def testObjectMatch(self):
     obj = object(),
     self.assertEqual(match.ObjectMatch(obj),
                      matcher.create_match(_FAKE_CONTEXT.parsed_file, obj))
Пример #11
0
 def testStringMatch(self):
     self.assertEqual(
         match.StringMatch('hello world'),
         matcher.create_match(_FAKE_CONTEXT.parsed_file, 'hello world'))
Пример #12
0
 def _match(self, context, candidate):
     del candidate  # unused
     return matcher.MatchInfo(
         matcher.create_match(context.parsed_file, 'hello world'),
         {})
Пример #13
0
 def _match(self, context, candidate):
     if type(candidate) == self._type:
         return matcher.MatchInfo(
             matcher.create_match(context.parsed_file, candidate))
     else:
         return None
Пример #14
0
 def _match(self, context, candidate):
     if self._submatcher.match(context, candidate) is None:
         return matcher.MatchInfo(
             matcher.create_match(context.parsed_file, candidate))
     else:
         return None
Пример #15
0
 def _match(self, context, candidate):
     return matcher.MatchInfo(
         matcher.create_match(context.parsed_file, candidate))
Пример #16
0
    def substitute_match(self, parsed, match, matches):
        """Syntax-aware substitution, parsing the template using _pattern_factory.

    Args:
      parsed: The ParsedFile being substituted into.
      match: The match being replaced.
      matches: The matches used for formatting.

    Returns:
      Substituted Python code.
    """
        replacement, _ = self._parenthesized(
            _matchers_for_matches(matches),
            formatting.stringify_matches(matches),
        )
        if not isinstance(parsed, matcher.PythonParsedFile):
            # This file is not (known to be) a Python file, so we can't (efficiently)
            # check the replacement into the parent AST node.
            # TODO: It would help if we could automatically obtain a
            # PythonParsedFile for any given ParsedFile. This also lets us compose
            # arbitrary searchers together, some of which take a PythonParsedFile, and
            # others which don't.
            return replacement

        # replacement is now a safely-interpolated string: all of the substitutions
        # are parenthesized where needed. The only thing left is to ensure that when
        # replacement itself is substituted in, it itself is parenthesized
        # correctly.
        #
        # To do this, we essentially repeat the same safe substitution algorithm,
        # except using the context of the replacement as a fake pattern.
        #
        # Note that this whole process is only valid if we are matching an actual
        # AST node, and an expr node at that.
        # For example, it is _not_ valid to replace b'foo' with b('foo'), or 'foo'
        # with 'f(o)o'.

        if not isinstance(match, matcher.LexicalASTMatch):
            # The match wasn't even for an AST node. We'll assume they know what
            # they're doing and pass thru verbatim. Non-AST matches can't be
            # automatically parenthesized -- for example, b'foo' vs b('foo')
            return replacement

        if not isinstance(match.matched, ast.expr):
            # Only expressions need to be parenthesized, so this is fine as-is.
            return replacement

        parent = parsed.nav.get_parent(match.matched)

        if isinstance(parent, ast.Expr):
            # the matched object is already a top-level expression, and will never
            # need extra parentheses.
            # We are assuming here that the template does not contain comments,
            # which is not enforced. We also assume that it doesn't contain raw
            # newlines, but this is enforced by the template and substitution both
            # needing to parse on their own.
            return replacement

        if isinstance(parent, ast.stmt) and hasattr(parent, 'body'):
            # TODO(b/139758169): re-enable reparsing of statements in templating.
            # Multi-line statements can't be reparsed due to issues with
            # indentation. We can usually safely assume that it doesn't need parens,
            # although exceptions exist. (Including, in an ironic twist, "except".)
            return replacement

        # keep navigating up until we reach a lexical AST node.
        # e.g. skip over lists.
        while True:
            parent_span = matcher.create_match(parsed, parent).span
            if parent_span is not None and isinstance(parent,
                                                      (ast.expr, ast.stmt)):
                # We need a parent node which has a known source span,
                # and which is by itself parseable (i.e. is an expr or stmt).
                break
            next_parent = parsed.nav.get_parent(parent)
            if isinstance(next_parent, ast.stmt) and hasattr(
                    next_parent, 'body'):
                # TODO(b/139758169): re-enable reparsing of statements in templating.
                # We encountered no reparseable parents between here and a
                # non-reparseable statement, so, as before, we must fall back to
                # returning the replacement verbatim and hoping it isn't broken.
                # (For example, replacing T with T1, T2 in "class A(T)" is incorrect.)
                return replacement
            parent = next_parent
        else:
            raise formatting.RewriteError(
                "Bug in Python formatter? Couldn't find parent of %r" % match)

        # Re-apply the safe substitution, but on the parent.
        context_start, context_end = parent_span
        match_start, match_end = match.span
        prefix = parsed.text[context_start:match_start]
        suffix = parsed.text[match_end:context_end]
        if isinstance(parent, ast.expr):
            # expressions can occur in a multiline context, but now that we've
            # removed it from its context for reparsing, we're in a single-line
            # unparenthesized context. We need to add parens to make sure this
            # parses correctly.
            prefix = '(' + prefix
            suffix += ')'
        parent_pseudotemplate = PythonTemplate(prefix + u'$current_expr' +
                                               suffix)
        parsed_replacement = ast.parse(replacement)
        if len(parsed_replacement.body) != 1 or not isinstance(
                parsed_replacement.body[0], ast.Expr):
            raise formatting.RewriteError(
                "Non-expression template can't be used in expression context: %s"
                % self.template)

        current_matcher = syntax_matchers.ast_matchers_matcher(
            parsed_replacement.body[0].value)
        _, safe_mapping = parent_pseudotemplate._parenthesized(  # pylint: disable=protected-access
            {u'current_expr': current_matcher}, {u'current_expr': replacement})
        return safe_mapping[u'current_expr']