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)
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
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
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))
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
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)
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)
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))
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')
def testObjectMatch(self): obj = object(), self.assertEqual(match.ObjectMatch(obj), matcher.create_match(_FAKE_CONTEXT.parsed_file, obj))
def testStringMatch(self): self.assertEqual( match.StringMatch('hello world'), matcher.create_match(_FAKE_CONTEXT.parsed_file, 'hello world'))
def _match(self, context, candidate): del candidate # unused return matcher.MatchInfo( matcher.create_match(context.parsed_file, 'hello world'), {})
def _match(self, context, candidate): if type(candidate) == self._type: return matcher.MatchInfo( matcher.create_match(context.parsed_file, candidate)) else: return None
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
def _match(self, context, candidate): return matcher.MatchInfo( matcher.create_match(context.parsed_file, candidate))
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']