Skip to content

Commit

Permalink
fix: SaveMachedNode now matches with trailing empty wildcards (#356)
Browse files Browse the repository at this point in the history
* fix: SaveMachedNode now matches with trailing empty wildcards

Note that SaveMatchedNode was already matching leading empty wildcards, however it's value is incorect due to #337. This is why the test for the leading wildcards are failing, and will be so until #355 is merged.

This fixes #336.

* fix: _matches_zero_nodes type declaration
  • Loading branch information
sk- authored Aug 14, 2020
1 parent 3ada79e commit e43ef3e
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 7 deletions.
31 changes: 24 additions & 7 deletions libcst/matchers/_matcher_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,24 @@ def SaveMatchedNode(matcher: _OtherNodeT, name: str) -> _OtherNodeT:
return cast(_OtherNodeT, _ExtractMatchingNode(matcher, name))


def _matches_zero_nodes(
matcher: Union[
BaseMatcherNode,
_BaseWildcardNode,
MatchIfTrue[Callable[[object], bool]],
_BaseMetadataMatcher,
DoNotCareSentinel,
]
) -> bool:
if isinstance(matcher, AtLeastN) and matcher.n == 0:
return True
if isinstance(matcher, AtMostN):
return True
if isinstance(matcher, _ExtractMatchingNode):
return _matches_zero_nodes(matcher.matcher)
return False


@dataclass(frozen=True)
class _SequenceMatchesResult:
sequence_capture: Optional[
Expand Down Expand Up @@ -960,14 +978,13 @@ def _sequence_matches( # noqa: C901
return _SequenceMatchesResult({}, None)
if not nodes and matchers:
# Base case, we have one or more matcher that wasn't matched
return (
_SequenceMatchesResult({}, [])
if all(
(isinstance(m, AtLeastN) and m.n == 0) or isinstance(m, AtMostN)
for m in matchers
if all(_matches_zero_nodes(m) for m in matchers):
return _SequenceMatchesResult(
{m.name: () for m in matchers if isinstance(m, _ExtractMatchingNode)},
(),
)
else _SequenceMatchesResult(None, None)
)
else:
return _SequenceMatchesResult(None, None)
if nodes and not matchers:
# Base case, we have nodes left that don't match any matcher
return _SequenceMatchesResult(None, None)
Expand Down
28 changes: 28 additions & 0 deletions libcst/matchers/tests/test_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,34 @@ def test_extract_optional_wildcard(self) -> None:
)
self.assertEqual(nodes, {})

def test_extract_optional_wildcard_head(self) -> None:
expression = cst.parse_expression("[3]")
nodes = m.extract(
expression,
m.List(
elements=[
m.SaveMatchedNode(m.ZeroOrMore(), "head1"),
m.SaveMatchedNode(m.ZeroOrMore(), "head2"),
m.Element(value=m.Integer(value="3")),
]
),
)
self.assertEqual(nodes, {"head1": (), "head2": ()})

def test_extract_optional_wildcard_tail(self) -> None:
expression = cst.parse_expression("[3]")
nodes = m.extract(
expression,
m.List(
elements=[
m.Element(value=m.Integer(value="3")),
m.SaveMatchedNode(m.ZeroOrMore(), "tail1"),
m.SaveMatchedNode(m.ZeroOrMore(), "tail2"),
]
),
)
self.assertEqual(nodes, {"tail1": (), "tail2": ()})

def test_extract_optional_wildcard_present(self) -> None:
expression = cst.parse_expression("a + b[c], d(e, f * g, h.i.j)")
nodes = m.extract(
Expand Down

0 comments on commit e43ef3e

Please sign in to comment.