Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/matchers/_visitors.py: 71%
239 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:43 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:43 +0000
1# Copyright (c) Meta Platforms, Inc. and affiliates.
2#
3# This source code is licensed under the MIT license found in the
4# LICENSE file in the root directory of this source tree.
6from inspect import ismethod, signature
7from typing import (
8 Any,
9 Callable,
10 cast,
11 Dict,
12 get_type_hints,
13 List,
14 Optional,
15 Sequence,
16 Set,
17 Tuple,
18 Type,
19 Union,
20)
22import libcst as cst
23from libcst import CSTTransformer, CSTVisitor
24from libcst._types import CSTNodeT
25from libcst.matchers._decorators import (
26 CONSTRUCTED_LEAVE_MATCHER_ATTR,
27 CONSTRUCTED_VISIT_MATCHER_ATTR,
28 VISIT_NEGATIVE_MATCHER_ATTR,
29 VISIT_POSITIVE_MATCHER_ATTR,
30)
31from libcst.matchers._matcher_base import (
32 AllOf,
33 AtLeastN,
34 AtMostN,
35 BaseMatcherNode,
36 extract,
37 extractall,
38 findall,
39 matches,
40 MatchIfTrue,
41 MatchMetadata,
42 MatchMetadataIfTrue,
43 OneOf,
44 replace,
45)
46from libcst.matchers._return_types import TYPED_FUNCTION_RETURN_MAPPING
48try:
49 # PEP 604 unions, in Python 3.10+
50 from types import UnionType
51except ImportError:
52 # We use this for isinstance; no annotation will be an instance of this
53 class UnionType:
54 pass
57CONCRETE_METHODS: Set[str] = {
58 *{f"visit_{cls.__name__}" for cls in TYPED_FUNCTION_RETURN_MAPPING},
59 *{f"leave_{cls.__name__}" for cls in TYPED_FUNCTION_RETURN_MAPPING},
60}
63# pyre-ignore We don't care about Any here, its not exposed.
64def _match_decorator_unpickler(kwargs: Any) -> "MatchDecoratorMismatch":
65 return MatchDecoratorMismatch(**kwargs)
68class MatchDecoratorMismatch(Exception):
69 def __init__(self, func: str, message: str) -> None:
70 super().__init__(f"Invalid function signature for {func}: {message}")
71 self.func = func
72 self.message = message
74 def __reduce__(
75 self,
76 ) -> Tuple[Callable[..., "MatchDecoratorMismatch"], Tuple[object, ...]]:
77 return (
78 _match_decorator_unpickler,
79 ({"func": self.func, "message": self.message},),
80 )
83def _get_possible_match_classes(matcher: BaseMatcherNode) -> List[Type[cst.CSTNode]]:
84 if isinstance(matcher, (OneOf, AllOf)):
85 return [getattr(cst, m.__class__.__name__) for m in matcher.options]
86 else:
87 return [getattr(cst, matcher.__class__.__name__)]
90def _annotation_is_union(annotation: object) -> bool:
91 return (
92 isinstance(annotation, UnionType)
93 or getattr(annotation, "__origin__", None) is Union
94 )
97def _get_possible_annotated_classes(annotation: object) -> List[Type[object]]:
98 if _annotation_is_union(annotation):
99 return getattr(annotation, "__args__", [])
100 else:
101 return [cast(Type[object], annotation)]
104def _get_valid_leave_annotations_for_classes(
105 classes: Sequence[Type[cst.CSTNode]],
106) -> Set[Type[object]]:
107 retval: Set[Type[object]] = set()
109 for cls in classes:
110 # Look up the leave annotation for each class, combine them so we get a list of
111 # all possible valid return annotations. Its not really possible for us (or
112 # pyre) to fully enforce return types given the presence of OneOf/AllOf matchers, so
113 # we do the best we can by taking a union of all valid return annotations.
114 retval.update(
115 _get_possible_annotated_classes(TYPED_FUNCTION_RETURN_MAPPING[cls])
116 )
118 return retval
121def _verify_return_annotation(
122 possible_match_classes: Sequence[Type[cst.CSTNode]],
123 # pyre-ignore We only care that meth is callable.
124 meth: Callable[..., Any],
125 decorator_name: str,
126 *,
127 expected_none: bool,
128) -> None:
129 type_hints = get_type_hints(meth)
130 if expected_none:
131 # Simply look for any annotation at all and if it exists, verify that
132 # it is "None".
133 if type_hints.get("return", type(None)) is not type(None): # noqa: E721
134 raise MatchDecoratorMismatch(
135 # pyre-fixme[16]: Anonymous callable has no attribute `__qualname__`.
136 meth.__qualname__,
137 f"@{decorator_name} should only decorate functions that do "
138 + "not return.",
139 )
140 else:
141 if "return" not in type_hints:
142 # Can't check this, type annotation not supplied.
143 return
145 possible_annotated_classes = _get_possible_annotated_classes(
146 type_hints["return"]
147 )
148 possible_returns = _get_valid_leave_annotations_for_classes(
149 possible_match_classes
150 )
152 # Look at the union of specified return annotation, make sure that
153 # they are all subclasses of the original leave_<Node> return
154 # annotations. This catches when somebody tries to return a new node
155 # that we know can't fit where the existing node was in the tree.
156 for ret in possible_annotated_classes:
157 for annotation in possible_returns:
158 if issubclass(ret, annotation):
159 # This annotation is a superclass of the possible match,
160 # so we know that the types are correct.
161 break
162 else:
163 # The current ret was not a subclass of any of the annotated
164 # return types.
165 raise MatchDecoratorMismatch(
166 meth.__qualname__,
167 f"@{decorator_name} decorated function cannot return "
168 + f"the type {ret.__name__}.",
169 )
172def _verify_parameter_annotations(
173 possible_match_classes: Sequence[Type[cst.CSTNode]],
174 # pyre-ignore We only care that meth is callable.
175 meth: Callable[..., Any],
176 decorator_name: str,
177 *,
178 expected_param_count: int,
179) -> None:
180 # First, verify that the number of parameters is sane.
181 meth_signature = signature(meth)
182 if len(meth_signature.parameters) != expected_param_count:
183 raise MatchDecoratorMismatch(
184 # pyre-fixme[16]: Anonymous callable has no attribute `__qualname__`.
185 meth.__qualname__,
186 f"@{decorator_name} should decorate functions which take "
187 + f"{expected_param_count} parameter"
188 + ("s" if expected_param_count > 1 else ""),
189 )
191 # Finally, for each parameter, make sure that the annotation includes
192 # each of the classes that might appear given the match string. This
193 # can be done in the simple case by just specifying the correct cst node
194 # type. For complex matches that use OneOf/AllOf, this could be a base class
195 # that encompases all possible matches, or a union.
196 params = [v for k, v in get_type_hints(meth).items() if k != "return"]
197 for param in params:
198 # Go through each possible matcher, and make sure that the annotation
199 # for types is a superclass of each matcher.
200 possible_annotated_classes = _get_possible_annotated_classes(param)
201 for match in possible_match_classes:
202 for annotation in possible_annotated_classes:
203 if issubclass(match, annotation):
204 # This annotation is a superclass of the possible match,
205 # so we know that the types are correct.
206 break
207 else:
208 # The current match was not a subclass of any of the annotated
209 # types.
210 raise MatchDecoratorMismatch(
211 meth.__qualname__,
212 f"@{decorator_name} can be called with {match.__name__} "
213 + "but the decorated function parameter annotations do "
214 + "not include this type.",
215 )
218def _check_types(
219 # pyre-ignore We don't care about the type of sequence, just that its callable.
220 decoratormap: Dict[BaseMatcherNode, Sequence[Callable[..., Any]]],
221 decorator_name: str,
222 *,
223 expected_param_count: int,
224 expected_none_return: bool,
225) -> None:
226 for matcher, methods in decoratormap.items():
227 # Given the matcher class we have, get the list of possible cst nodes that
228 # could be passed to the functionis we wrap.
229 possible_match_classes = _get_possible_match_classes(matcher)
230 has_invalid_top_level = any(
231 isinstance(m, (AtLeastN, AtMostN, MatchIfTrue))
232 for m in possible_match_classes
233 )
235 # Now, loop through each function we wrap and verify that the type signature
236 # is valid.
237 for meth in methods:
238 # First thing first, make sure this isn't wrapping an inner class.
239 if not ismethod(meth):
240 raise MatchDecoratorMismatch(
241 # pyre-fixme[16]: Anonymous callable has no attribute
242 # `__qualname__`.
243 meth.__qualname__,
244 "Matcher decorators should only be used on methods of "
245 + "MatcherDecoratableTransformer or "
246 + "MatcherDecoratableVisitor",
247 )
248 if has_invalid_top_level:
249 raise MatchDecoratorMismatch(
250 meth.__qualname__,
251 "The root matcher in a matcher decorator cannot be an "
252 + "AtLeastN, AtMostN or MatchIfTrue matcher",
253 )
255 # Now, check that the return annotation is valid.
256 _verify_return_annotation(
257 possible_match_classes,
258 meth,
259 decorator_name,
260 expected_none=expected_none_return,
261 )
263 # Finally, check that the parameter annotations are valid.
264 _verify_parameter_annotations(
265 possible_match_classes,
266 meth,
267 decorator_name,
268 expected_param_count=expected_param_count,
269 )
272def _gather_matchers(obj: object) -> Set[BaseMatcherNode]:
273 visit_matchers: Set[BaseMatcherNode] = set()
275 for func in dir(obj):
276 try:
277 for matcher in getattr(getattr(obj, func), VISIT_POSITIVE_MATCHER_ATTR, []):
278 visit_matchers.add(cast(BaseMatcherNode, matcher))
279 for matcher in getattr(getattr(obj, func), VISIT_NEGATIVE_MATCHER_ATTR, []):
280 visit_matchers.add(cast(BaseMatcherNode, matcher))
281 except Exception:
282 # This could be a caculated property, and calling getattr() evaluates it.
283 # We have no control over the implementation detail, so if it raises, we
284 # should not crash.
285 pass
287 return visit_matchers
290def _assert_not_concrete(
291 decorator_name: str, func: Callable[[cst.CSTNode], None]
292) -> None:
293 if func.__name__ in CONCRETE_METHODS:
294 raise MatchDecoratorMismatch(
295 # pyre-ignore This anonymous method has a qualname.
296 func.__qualname__,
297 f"@{decorator_name} should not decorate functions that are concrete "
298 + "visit or leave methods.",
299 )
302def _gather_constructed_visit_funcs(
303 obj: object,
304) -> Dict[BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]]:
305 constructed_visitors: Dict[
306 BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
307 ] = {}
309 for funcname in dir(obj):
310 try:
311 possible_func = getattr(obj, funcname)
312 if not ismethod(possible_func):
313 continue
314 func = cast(Callable[[cst.CSTNode], None], possible_func)
315 except Exception:
316 # This could be a caculated property, and calling getattr() evaluates it.
317 # We have no control over the implementation detail, so if it raises, we
318 # should not crash.
319 continue
320 matchers = getattr(func, CONSTRUCTED_VISIT_MATCHER_ATTR, [])
321 if matchers:
322 # Make sure that we aren't accidentally putting a @visit on a visit_Node.
323 _assert_not_concrete("visit", func)
324 for matcher in matchers:
325 casted_matcher = cast(BaseMatcherNode, matcher)
326 constructed_visitors[casted_matcher] = (
327 *constructed_visitors.get(casted_matcher, ()),
328 func,
329 )
331 return constructed_visitors
334# pyre-ignore: There is no reasonable way to type this, so ignore the Any type. This
335# is because the leave_* methods have a different signature depending on whether they
336# are in a MatcherDecoratableTransformer or a MatcherDecoratableVisitor.
337def _gather_constructed_leave_funcs(
338 obj: object,
339) -> Dict[BaseMatcherNode, Sequence[Callable[..., Any]]]:
340 constructed_visitors: Dict[
341 BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
342 ] = {}
344 for funcname in dir(obj):
345 try:
346 possible_func = getattr(obj, funcname)
347 if not ismethod(possible_func):
348 continue
349 func = cast(Callable[[cst.CSTNode], None], possible_func)
350 except Exception:
351 # This could be a caculated property, and calling getattr() evaluates it.
352 # We have no control over the implementation detail, so if it raises, we
353 # should not crash.
354 continue
355 matchers = getattr(func, CONSTRUCTED_LEAVE_MATCHER_ATTR, [])
356 if matchers:
357 # Make sure that we aren't accidentally putting a @leave on a leave_Node.
358 _assert_not_concrete("leave", func)
359 for matcher in matchers:
360 casted_matcher = cast(BaseMatcherNode, matcher)
361 constructed_visitors[casted_matcher] = (
362 *constructed_visitors.get(casted_matcher, ()),
363 func,
364 )
366 return constructed_visitors
369def _visit_matchers(
370 matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]],
371 node: cst.CSTNode,
372 metadata_resolver: cst.MetadataDependent,
373) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
374 new_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {}
375 for matcher, existing_node in matchers.items():
376 # We don't care about visiting matchers that are already true.
377 if existing_node is None and matches(
378 node, matcher, metadata_resolver=metadata_resolver
379 ):
380 # This node matches! Remember which node it was so we can
381 # cancel it later.
382 new_matchers[matcher] = node
383 else:
384 new_matchers[matcher] = existing_node
385 return new_matchers
388def _leave_matchers(
389 matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]], node: cst.CSTNode
390) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
391 new_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {}
392 for matcher, existing_node in matchers.items():
393 if node is existing_node:
394 # This node matches, so we are no longer inside it.
395 new_matchers[matcher] = None
396 else:
397 # We aren't leaving this node.
398 new_matchers[matcher] = existing_node
399 return new_matchers
402def _all_positive_matchers_true(
403 all_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]], obj: object
404) -> bool:
405 requested_matchers = getattr(obj, VISIT_POSITIVE_MATCHER_ATTR, [])
406 for matcher in requested_matchers:
407 if all_matchers[matcher] is None:
408 # The passed in object has been decorated with a matcher that isn't
409 # active.
410 return False
411 return True
414def _all_negative_matchers_false(
415 all_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]], obj: object
416) -> bool:
417 requested_matchers = getattr(obj, VISIT_NEGATIVE_MATCHER_ATTR, [])
418 for matcher in requested_matchers:
419 if all_matchers[matcher] is not None:
420 # The passed in object has been decorated with a matcher that is active.
421 return False
422 return True
425def _should_allow_visit(
426 all_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]], obj: object
427) -> bool:
428 return _all_positive_matchers_true(
429 all_matchers, obj
430 ) and _all_negative_matchers_false(all_matchers, obj)
433def _visit_constructed_funcs(
434 visit_funcs: Dict[BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]],
435 all_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]],
436 node: cst.CSTNode,
437 metadata_resolver: cst.MetadataDependent,
438) -> None:
439 for matcher, visit_funcs in visit_funcs.items():
440 if matches(node, matcher, metadata_resolver=metadata_resolver):
441 for visit_func in visit_funcs:
442 if _should_allow_visit(all_matchers, visit_func):
443 visit_func(node)
446class MatcherDecoratableTransformer(CSTTransformer):
447 """
448 This class provides all of the features of a :class:`libcst.CSTTransformer`, and
449 additionally supports various decorators to control when methods get called when
450 traversing a tree. Use this instead of a :class:`libcst.CSTTransformer` if you
451 wish to do more powerful decorator-based visiting.
452 """
454 def __init__(self) -> None:
455 CSTTransformer.__init__(self)
456 # List of gating matchers that we need to track and evaluate. We use these
457 # in conjuction with the call_if_inside and call_if_not_inside decorators
458 # to determine whether or not to call a visit/leave function.
459 self._matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {
460 m: None for m in _gather_matchers(self)
461 }
462 # Mapping of matchers to functions. If in the course of visiting the tree,
463 # a node matches one of these matchers, the corresponding function will be
464 # called as if it was a visit_* method.
465 self._extra_visit_funcs: Dict[
466 BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
467 ] = _gather_constructed_visit_funcs(self)
468 # Mapping of matchers to functions. If in the course of leaving the tree,
469 # a node matches one of these matchers, the corresponding function will be
470 # called as if it was a leave_* method.
471 self._extra_leave_funcs: Dict[
472 BaseMatcherNode,
473 Sequence[
474 Callable[
475 [cst.CSTNode, cst.CSTNode], Union[cst.CSTNode, cst.RemovalSentinel]
476 ]
477 ],
478 ] = _gather_constructed_leave_funcs(self)
479 # Make sure visit/leave functions constructed with @visit and @leave decorators
480 # have correct type annotations.
481 _check_types(
482 self._extra_visit_funcs,
483 "visit",
484 expected_param_count=1,
485 expected_none_return=True,
486 )
487 _check_types(
488 self._extra_leave_funcs,
489 "leave",
490 expected_param_count=2,
491 expected_none_return=False,
492 )
494 def on_visit(self, node: cst.CSTNode) -> bool:
495 # First, evaluate any matchers that we have which we are not inside already.
496 self._matchers = _visit_matchers(self._matchers, node, self)
498 # Now, call any visitors that were hooked using a visit decorator.
499 _visit_constructed_funcs(self._extra_visit_funcs, self._matchers, node, self)
501 # Now, evaluate whether this current function has any matchers it requires.
502 if not _should_allow_visit(
503 self._matchers, getattr(self, f"visit_{type(node).__name__}", None)
504 ):
505 # We shouldn't visit this directly. However, we should continue
506 # visiting its children.
507 return True
509 # Either the visit_func doesn't exist, we have no matchers, or we passed all
510 # matchers. In either case, just call the superclass behavior.
511 return CSTTransformer.on_visit(self, node)
513 def on_leave(
514 self, original_node: CSTNodeT, updated_node: CSTNodeT
515 ) -> Union[CSTNodeT, cst.RemovalSentinel]:
516 # First, evaluate whether this current function has a decorator on it.
517 if _should_allow_visit(
518 self._matchers, getattr(self, f"leave_{type(original_node).__name__}", None)
519 ):
520 retval = CSTTransformer.on_leave(self, original_node, updated_node)
521 else:
522 retval = updated_node
524 # Now, call any visitors that were hooked using a leave decorator.
525 for matcher, leave_funcs in reversed(list(self._extra_leave_funcs.items())):
526 if not self.matches(original_node, matcher):
527 continue
528 for leave_func in leave_funcs:
529 if _should_allow_visit(self._matchers, leave_func) and isinstance(
530 retval, cst.CSTNode
531 ):
532 retval = leave_func(original_node, retval)
534 # Now, see if we have any matchers we should deactivate.
535 self._matchers = _leave_matchers(self._matchers, original_node)
537 # pyre-ignore The return value of on_leave is subtly wrong in that we can
538 # actually return any value that passes this node's parent's constructor
539 # validation. Fixing this is beyond the scope of this file, and would involve
540 # forcing a lot of ensure_type() checks across the codebase.
541 return retval
543 def on_visit_attribute(self, node: cst.CSTNode, attribute: str) -> None:
544 # Evaluate whether this current function has a decorator on it.
545 if _should_allow_visit(
546 self._matchers,
547 getattr(self, f"visit_{type(node).__name__}_{attribute}", None),
548 ):
549 # Either the visit_func doesn't exist, we have no matchers, or we passed all
550 # matchers. In either case, just call the superclass behavior.
551 return CSTTransformer.on_visit_attribute(self, node, attribute)
553 def on_leave_attribute(self, original_node: cst.CSTNode, attribute: str) -> None:
554 # Evaluate whether this current function has a decorator on it.
555 if _should_allow_visit(
556 self._matchers,
557 getattr(self, f"leave_{type(original_node).__name__}_{attribute}", None),
558 ):
559 # Either the visit_func doesn't exist, we have no matchers, or we passed all
560 # matchers. In either case, just call the superclass behavior.
561 CSTTransformer.on_leave_attribute(self, original_node, attribute)
563 def matches(
564 self,
565 node: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
566 matcher: BaseMatcherNode,
567 ) -> bool:
568 """
569 A convenience method to call :func:`~libcst.matchers.matches` without requiring
570 an explicit parameter for metadata. Since our instance is an instance of
571 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
572 documentation for :func:`~libcst.matchers.matches` as it is identical to this
573 function.
574 """
575 return matches(node, matcher, metadata_resolver=self)
577 def findall(
578 self,
579 tree: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
580 matcher: Union[
581 BaseMatcherNode,
582 MatchIfTrue[cst.CSTNode],
583 MatchMetadata,
584 MatchMetadataIfTrue,
585 ],
586 ) -> Sequence[cst.CSTNode]:
587 """
588 A convenience method to call :func:`~libcst.matchers.findall` without requiring
589 an explicit parameter for metadata. Since our instance is an instance of
590 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
591 documentation for :func:`~libcst.matchers.findall` as it is identical to this
592 function.
593 """
594 return findall(tree, matcher, metadata_resolver=self)
596 def extract(
597 self,
598 node: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
599 matcher: BaseMatcherNode,
600 ) -> Optional[Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]]]:
601 """
602 A convenience method to call :func:`~libcst.matchers.extract` without requiring
603 an explicit parameter for metadata. Since our instance is an instance of
604 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
605 documentation for :func:`~libcst.matchers.extract` as it is identical to this
606 function.
607 """
608 return extract(node, matcher, metadata_resolver=self)
610 def extractall(
611 self,
612 tree: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
613 matcher: Union[
614 BaseMatcherNode,
615 MatchIfTrue[cst.CSTNode],
616 MatchMetadata,
617 MatchMetadataIfTrue,
618 ],
619 ) -> Sequence[Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]]]:
620 """
621 A convenience method to call :func:`~libcst.matchers.extractall` without requiring
622 an explicit parameter for metadata. Since our instance is an instance of
623 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
624 documentation for :func:`~libcst.matchers.extractall` as it is identical to this
625 function.
626 """
627 return extractall(tree, matcher, metadata_resolver=self)
629 def replace(
630 self,
631 tree: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
632 matcher: Union[
633 BaseMatcherNode,
634 MatchIfTrue[cst.CSTNode],
635 MatchMetadata,
636 MatchMetadataIfTrue,
637 ],
638 replacement: Union[
639 cst.MaybeSentinel,
640 cst.RemovalSentinel,
641 cst.CSTNode,
642 Callable[
643 [cst.CSTNode, Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]]],
644 Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
645 ],
646 ],
647 ) -> Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode]:
648 """
649 A convenience method to call :func:`~libcst.matchers.replace` without requiring
650 an explicit parameter for metadata. Since our instance is an instance of
651 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
652 documentation for :func:`~libcst.matchers.replace` as it is identical to this
653 function.
654 """
655 return replace(tree, matcher, replacement, metadata_resolver=self)
658class MatcherDecoratableVisitor(CSTVisitor):
659 """
660 This class provides all of the features of a :class:`libcst.CSTVisitor`, and
661 additionally supports various decorators to control when methods get called
662 when traversing a tree. Use this instead of a :class:`libcst.CSTVisitor` if
663 you wish to do more powerful decorator-based visiting.
664 """
666 def __init__(self) -> None:
667 CSTVisitor.__init__(self)
668 # List of gating matchers that we need to track and evaluate. We use these
669 # in conjuction with the call_if_inside and call_if_not_inside decorators
670 # to determine whether or not to call a visit/leave function.
671 self._matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {
672 m: None for m in _gather_matchers(self)
673 }
674 # Mapping of matchers to functions. If in the course of visiting the tree,
675 # a node matches one of these matchers, the corresponding function will be
676 # called as if it was a visit_* method.
677 self._extra_visit_funcs: Dict[
678 BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
679 ] = _gather_constructed_visit_funcs(self)
680 # Mapping of matchers to functions. If in the course of leaving the tree,
681 # a node matches one of these matchers, the corresponding function will be
682 # called as if it was a leave_* method.
683 self._extra_leave_funcs: Dict[
684 BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
685 ] = _gather_constructed_leave_funcs(self)
686 # Make sure visit/leave functions constructed with @visit and @leave decorators
687 # have correct type annotations.
688 _check_types(
689 self._extra_visit_funcs,
690 "visit",
691 expected_param_count=1,
692 expected_none_return=True,
693 )
694 _check_types(
695 self._extra_leave_funcs,
696 "leave",
697 expected_param_count=1,
698 expected_none_return=True,
699 )
701 def on_visit(self, node: cst.CSTNode) -> bool:
702 # First, evaluate any matchers that we have which we are not inside already.
703 self._matchers = _visit_matchers(self._matchers, node, self)
705 # Now, call any visitors that were hooked using a visit decorator.
706 _visit_constructed_funcs(self._extra_visit_funcs, self._matchers, node, self)
708 # Now, evaluate whether this current function has a decorator on it.
709 if not _should_allow_visit(
710 self._matchers, getattr(self, f"visit_{type(node).__name__}", None)
711 ):
712 # We shouldn't visit this directly. However, we should continue
713 # visiting its children.
714 return True
716 # Either the visit_func doesn't exist, we have no matchers, or we passed all
717 # matchers. In either case, just call the superclass behavior.
718 return CSTVisitor.on_visit(self, node)
720 def on_leave(self, original_node: cst.CSTNode) -> None:
721 # First, evaluate whether this current function has a decorator on it.
722 if _should_allow_visit(
723 self._matchers, getattr(self, f"leave_{type(original_node).__name__}", None)
724 ):
725 CSTVisitor.on_leave(self, original_node)
727 # Now, call any visitors that were hooked using a leave decorator.
728 for matcher, leave_funcs in reversed(list(self._extra_leave_funcs.items())):
729 if not self.matches(original_node, matcher):
730 continue
731 for leave_func in leave_funcs:
732 if _should_allow_visit(self._matchers, leave_func):
733 leave_func(original_node)
735 # Now, see if we have any matchers we should deactivate.
736 self._matchers = _leave_matchers(self._matchers, original_node)
738 def on_visit_attribute(self, node: cst.CSTNode, attribute: str) -> None:
739 # Evaluate whether this current function has a decorator on it.
740 if _should_allow_visit(
741 self._matchers,
742 getattr(self, f"visit_{type(node).__name__}_{attribute}", None),
743 ):
744 # Either the visit_func doesn't exist, we have no matchers, or we passed all
745 # matchers. In either case, just call the superclass behavior.
746 return CSTVisitor.on_visit_attribute(self, node, attribute)
748 def on_leave_attribute(self, original_node: cst.CSTNode, attribute: str) -> None:
749 # Evaluate whether this current function has a decorator on it.
750 if _should_allow_visit(
751 self._matchers,
752 getattr(self, f"leave_{type(original_node).__name__}_{attribute}", None),
753 ):
754 # Either the visit_func doesn't exist, we have no matchers, or we passed all
755 # matchers. In either case, just call the superclass behavior.
756 CSTVisitor.on_leave_attribute(self, original_node, attribute)
758 def matches(
759 self,
760 node: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
761 matcher: BaseMatcherNode,
762 ) -> bool:
763 """
764 A convenience method to call :func:`~libcst.matchers.matches` without requiring
765 an explicit parameter for metadata. Since our instance is an instance of
766 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
767 documentation for :func:`~libcst.matchers.matches` as it is identical to this
768 function.
769 """
770 return matches(node, matcher, metadata_resolver=self)
772 def findall(
773 self,
774 tree: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
775 matcher: Union[
776 BaseMatcherNode,
777 MatchIfTrue[cst.CSTNode],
778 MatchMetadata,
779 MatchMetadataIfTrue,
780 ],
781 ) -> Sequence[cst.CSTNode]:
782 """
783 A convenience method to call :func:`~libcst.matchers.findall` without requiring
784 an explicit parameter for metadata. Since our instance is an instance of
785 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
786 documentation for :func:`~libcst.matchers.findall` as it is identical to this
787 function.
788 """
789 return findall(tree, matcher, metadata_resolver=self)
791 def extract(
792 self,
793 node: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
794 matcher: BaseMatcherNode,
795 ) -> Optional[Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]]]:
796 """
797 A convenience method to call :func:`~libcst.matchers.extract` without requiring
798 an explicit parameter for metadata. Since our instance is an instance of
799 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
800 documentation for :func:`~libcst.matchers.extract` as it is identical to this
801 function.
802 """
803 return extract(node, matcher, metadata_resolver=self)
805 def extractall(
806 self,
807 tree: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
808 matcher: Union[
809 BaseMatcherNode,
810 MatchIfTrue[cst.CSTNode],
811 MatchMetadata,
812 MatchMetadataIfTrue,
813 ],
814 ) -> Sequence[Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]]]:
815 """
816 A convenience method to call :func:`~libcst.matchers.extractall` without requiring
817 an explicit parameter for metadata. Since our instance is an instance of
818 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
819 documentation for :func:`~libcst.matchers.extractall` as it is identical to this
820 function.
821 """
822 return extractall(tree, matcher, metadata_resolver=self)
824 def replace(
825 self,
826 tree: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
827 matcher: Union[
828 BaseMatcherNode,
829 MatchIfTrue[cst.CSTNode],
830 MatchMetadata,
831 MatchMetadataIfTrue,
832 ],
833 replacement: Union[
834 cst.MaybeSentinel,
835 cst.RemovalSentinel,
836 cst.CSTNode,
837 Callable[
838 [cst.CSTNode, Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]]],
839 Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
840 ],
841 ],
842 ) -> Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode]:
843 """
844 A convenience method to call :func:`~libcst.matchers.replace` without requiring
845 an explicit parameter for metadata. Since our instance is an instance of
846 :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
847 documentation for :func:`~libcst.matchers.replace` as it is identical to this
848 function.
849 """
850 return replace(tree, matcher, replacement, metadata_resolver=self)