def test_on_merge_differs(self): with self.assertRaises(matcher.MatchError): matcher.merge_bindings( { 'a': matcher.BoundValue(0, on_merge=matcher.BindMerge.KEEP_FIRST) }, { 'a': matcher.BoundValue(0, on_merge=matcher.BindMerge.KEEP_LAST) }, )
def test_on_conflict_differs(self): with self.assertRaises(matcher.MatchError): matcher.merge_bindings( { 'a': matcher.BoundValue(0, on_conflict=matcher.BindConflict.SKIP) }, { 'a': matcher.BoundValue(0, on_conflict=matcher.BindConflict.ERROR) }, )
def _match(self, context, candidate): matchinfo = self._subpattern.match(context, candidate) if matchinfo is None: return None span = matchinfo.match.span if span is None: return None # can't search within this AST node. try: m = self._wrapped_regex.match(context.parsed_file.text, *span) except TypeError: return None if m is None: return None # TODO(b/118507248): Allow choosing a different binding type. bindings = matcher.merge_bindings( _re_match_to_bindings(self._wrapped_regex, context.parsed_file.text, m), matchinfo.bindings) if bindings is None: return None return attr.evolve(matchinfo, bindings=bindings)
def _match(self, context, candidate): """Returns the submatcher's match, with a binding introduced by this Bind. Args: context: The match context. candidate: The candidate node to be matched. Returns: An extended :class:`~refex.python.matcher.MatchInfo` with the new binding specified in the constructor. Conflicts are merged according to ``on_conflict``. If there was no match, or on_conflict result in a skip, then this returns ``None``. See matcher.merge_bindings for more details. """ result = self._submatcher.match(context, candidate) if result is None: return None bindings = matcher.merge_bindings( result.bindings, { self.name: matcher.BoundValue(result.match, on_conflict=self._on_conflict, on_merge=self._on_merge) }) if bindings is None: return None return attr.evolve(result, bindings=bindings)
def test_error_after_skip(self): """ERROR should still raise even if it is processed after a skip.""" # This is a gnarly thing to test, because dicts aren't ordered. # Even if we used OrderedDict, ordered dict key views don't (and can't) # preserve order when they're intersected and so on. @attr.s() class OrderedKeyView(object): """Ordered fake key view for deterministic key iteration order.""" keys = attr.ib() def __sub__(self, other_ordered_key_view): if self.keys != other_ordered_key_view.keys: raise NotImplementedError( "Test ordered key view doesn't support this.") return frozenset() def __and__(self, other_ordered_key_view): # can't preserve order otherwise... if self.keys != other_ordered_key_view.keys: raise NotImplementedError("Can't preserve order.") return list(self.keys) @attr.s() class OrderedBindings(object): """OrderedDict wrapper that returns OrderedKeyView.""" _dict = attr.ib(converter=collections.OrderedDict) def __getitem__(self, v): return self._dict[v] def keys(self): return OrderedKeyView(self._dict) viewkeys = keys bad_bindings = [ ('a', matcher.BoundValue(0, matcher.BindConflict.SKIP)), ('b', matcher.BoundValue(0, matcher.BindConflict.ERROR)), ] for bindings in [bad_bindings, bad_bindings[::-1]]: with self.subTest(bindings=bindings): bindings = OrderedBindings(bindings) with self.assertRaises(matcher.MatchError): matcher.merge_bindings(bindings, 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_empty(self): self.assertEqual(matcher.merge_bindings({}, {}), {})
def test_noconflict(self): bindings = {'a': matcher.BoundValue(0)} self.assertEqual(matcher.merge_bindings({}, bindings), bindings)
def test_skip(self): skip_bindings = {'a': matcher.BoundValue(0, matcher.BindConflict.SKIP)} self.assertIsNone(matcher.merge_bindings(skip_bindings, skip_bindings))
def test_error(self): error_bindings = { 'a': matcher.BoundValue(0, matcher.BindConflict.ERROR) } with self.assertRaises(matcher.MatchError): matcher.merge_bindings(error_bindings, error_bindings)