diff --git a/docs/source/matchers.rst b/docs/source/matchers.rst index c962f17d..8867f475 100644 --- a/docs/source/matchers.rst +++ b/docs/source/matchers.rst @@ -156,6 +156,15 @@ but for those that do, sequence wildcard matchers offer a great degree of flexibility. Unlike all other matcher types, these allow you to match against more than one LibCST node, much like wildcards in regular expressions do. +LibCST does not implicitly match on partial sequences for you. So, when matching +against a sequence you will need to provide a complete pattern. This often means +using helpers such as :func:`~libcst.matchers.ZeroOrMore` as the first and last +element of your sequence. Think of it as the difference between Python's +`re.match `_ and +`re.fullmatch `_ functions. +LibCST matchers behave like the latter so that it is possible to specify sequences +which must start with, end with or be exactly equal to some pattern. + .. autoclass:: libcst.matchers.AtLeastN .. autofunction:: libcst.matchers.ZeroOrMore .. autoclass:: libcst.matchers.AtMostN diff --git a/libcst/matchers/_matcher_base.py b/libcst/matchers/_matcher_base.py index 1abfc9f5..4b646a9c 100644 --- a/libcst/matchers/_matcher_base.py +++ b/libcst/matchers/_matcher_base.py @@ -460,10 +460,12 @@ class _BaseWildcardNode: class AtLeastN(Generic[_MatcherT], _BaseWildcardNode): """ - Matcher that matches ``n`` or more of a particular matcher in a sequence. + Matcher that matches ``n`` or more LibCST nodes in a row in a sequence. :class:`AtLeastN` defaults to matching against the :func:`DoNotCare` matcher, - so if you do not specify a concrete matcher as a child, :class:`AtLeastN` - will match only by count. + so if you do not specify a matcher as a child, :class:`AtLeastN` + will match only by count. If you do specify a matcher as a child, + :class:`AtLeastN` will instead make sure that each LibCST node matches the + matcher supplied. For example, this will match all function calls with at least 3 arguments:: @@ -475,7 +477,7 @@ class AtLeastN(Generic[_MatcherT], _BaseWildcardNode): You can combine sequence matchers with concrete matchers and special matchers and it will behave as you expect. For example, this will match all function - calls that have 2 or more integer arguments, followed by any arbitrary + calls that have 2 or more integer arguments in a row, followed by any arbitrary argument:: m.Call(args=[m.AtLeastN(n=2, matcher=m.Arg(m.Integer())), m.DoNotCare()]) @@ -547,16 +549,24 @@ def ZeroOrMore( m.Call(args=[m.Arg(m.Integer()), m.ZeroOrMore()]) + You will often want to use :class:`ZeroOrMore` on both sides of a concrete + matcher in order to match against sequences that contain a particular node + in an arbitrary location. For example, the following will match any function + call that takes in at least one string argument anywhere:: + + m.Call(args=[m.ZeroOrMore(), m.Arg(m.SimpleString()), m.ZeroOrMore()]) """ return cast(AtLeastN[Union[_MatcherT, DoNotCareSentinel]], AtLeastN(matcher, n=0)) class AtMostN(Generic[_MatcherT], _BaseWildcardNode): """ - Matcher that matches ``n`` or less of a particular matcher in a sequence. + Matcher that matches ``n`` or fewer LibCST nodes in a row in a sequence. :class:`AtMostN` defaults to matching against the :func:`DoNotCare` matcher, - so if you do not specify a concrete matcher as a child, :class:`AtMostN` will - match only by count. + so if you do not specify a matcher as a child, :class:`AtMostN` will + match only by count. If you do specify a matcher as a child, + :class:`AtMostN` will instead make sure that each LibCST node matches the + matcher supplied. For example, this will match all function calls with 3 or fewer arguments:: @@ -568,7 +578,7 @@ class AtMostN(Generic[_MatcherT], _BaseWildcardNode): You can combine sequence matchers with concrete matchers and special matchers and it will behave as you expect. For example, this will match all function - calls that have 0, 1 or 2 string arguments, followed by an arbitrary + calls that have 0, 1 or 2 string arguments in a row, followed by an arbitrary argument:: m.Call(args=[m.AtMostN(n=2, matcher=m.Arg(m.SimpleString())), m.DoNotCare()])