mirror of
https://github.com/Instagram/LibCST.git
synced 2025-12-23 10:35:53 +00:00
This introduces a few new gotchas (namely attribute access and a bug with Union[Callable]), but it also removes a whole host of pyre-fixmes and gets us updated to a release of pyre that came out after May.
144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
# pyre-strict
|
|
import inspect
|
|
from typing import (
|
|
TYPE_CHECKING,
|
|
Callable,
|
|
Iterable,
|
|
List,
|
|
Mapping,
|
|
MutableMapping,
|
|
Optional,
|
|
cast,
|
|
)
|
|
|
|
from libcst._metadata_dependent import MetadataDependent
|
|
from libcst._typed_visitor import CSTTypedVisitorFunctions
|
|
from libcst._visitors import CSTNodeT, CSTVisitor
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from libcst._nodes.base import CSTNode # noqa: F401
|
|
|
|
VisitorMethod = Callable[["CSTNode"], None]
|
|
_VisitorMethodCollection = Mapping[str, List[VisitorMethod]]
|
|
|
|
|
|
class BatchableCSTVisitor(CSTTypedVisitorFunctions, MetadataDependent):
|
|
"""
|
|
The low-level base visitor class for traversing a CST as part of a batched
|
|
set of traversals. This should be used in conjunction with the
|
|
:func:`~libcst.visit_batched` function or the
|
|
:func:`~libcst.MetadataWrapper.visit_batched` method from
|
|
:class:`~libcst.MetadataWrapper` to visit a tree.
|
|
Instances of this class cannot modify the tree.
|
|
"""
|
|
|
|
def get_visitors(self) -> Mapping[str, VisitorMethod]:
|
|
"""
|
|
Returns a mapping of all the ``visit_<Type[CSTNode]>`` and ``leave_<Type[CSTNode]>``
|
|
methods defined by this visitor, excluding all empty stubs.
|
|
"""
|
|
|
|
methods = inspect.getmembers(
|
|
self,
|
|
lambda m: (
|
|
inspect.ismethod(m)
|
|
and (m.__name__.startswith("visit_") or m.__name__.startswith("leave_"))
|
|
and not getattr(m, "_is_no_op", False)
|
|
),
|
|
)
|
|
|
|
# TODO: verify all visitor methods reference valid node classes.
|
|
# for name, __ in methods:
|
|
# ...
|
|
|
|
return dict(methods)
|
|
|
|
|
|
def visit_batched(
|
|
node: CSTNodeT,
|
|
batchable_visitors: Iterable[BatchableCSTVisitor],
|
|
before_visit: Optional[VisitorMethod] = None,
|
|
after_leave: Optional[VisitorMethod] = None,
|
|
) -> CSTNodeT:
|
|
"""
|
|
Do a batched traversal over ``node`` with all ``visitors``.
|
|
|
|
``before_visit`` and ``after_leave`` are provided as optional hooks to
|
|
execute before the ``visit_<Type[CSTNode]>`` and ``leave_<Type[CSTNode]>``
|
|
methods from each visitor in ``visitor`` are executed by the batched visitor.
|
|
|
|
This function does not handle metadata dependency resolution for ``visitors``.
|
|
See :func:`~libcst.MetadataWrapper.visit_batched` from
|
|
:class:`~libcst.MetadataWrapper` for batched traversal with metadata dependency
|
|
resolution.
|
|
"""
|
|
visitor_methods = _get_visitor_methods(batchable_visitors)
|
|
batched_visitor = _BatchedCSTVisitor(
|
|
visitor_methods, before_visit=before_visit, after_leave=after_leave
|
|
)
|
|
return cast(CSTNodeT, node.visit(batched_visitor))
|
|
|
|
|
|
def _get_visitor_methods(
|
|
batchable_visitors: Iterable[BatchableCSTVisitor]
|
|
) -> _VisitorMethodCollection:
|
|
"""
|
|
Gather all ``visit_<Type[CSTNode]>`` and ``leave_<Type[CSTNode]>`` methods
|
|
from ``batchabled_visitors``.
|
|
"""
|
|
visitor_methods: MutableMapping[str, List[VisitorMethod]] = {}
|
|
for bv in batchable_visitors:
|
|
for name, fn in bv.get_visitors().items():
|
|
visitor_methods.setdefault(name, []).append(fn)
|
|
return visitor_methods
|
|
|
|
|
|
class _BatchedCSTVisitor(CSTVisitor):
|
|
"""
|
|
Internal visitor class to perform batched traversal over a tree.
|
|
"""
|
|
|
|
visitor_methods: _VisitorMethodCollection
|
|
before_visit: Optional[VisitorMethod]
|
|
after_leave: Optional[VisitorMethod]
|
|
|
|
def __init__(
|
|
self,
|
|
visitor_methods: _VisitorMethodCollection,
|
|
*,
|
|
before_visit: Optional[VisitorMethod] = None,
|
|
after_leave: Optional[VisitorMethod] = None,
|
|
) -> None:
|
|
super().__init__()
|
|
self.visitor_methods = visitor_methods
|
|
self.before_visit = before_visit
|
|
self.after_leave = after_leave
|
|
|
|
def on_visit(self, node: "CSTNode") -> bool:
|
|
"""
|
|
Call appropriate visit methods on node before visiting children.
|
|
"""
|
|
before_visit = self.before_visit
|
|
if before_visit is not None:
|
|
before_visit(node)
|
|
type_name = type(node).__name__
|
|
for v in self.visitor_methods.get(f"visit_{type_name}", []):
|
|
v(node)
|
|
return True
|
|
|
|
def on_leave(self, original_node: "CSTNode") -> None:
|
|
"""
|
|
Call appropriate leave methods on node after visiting children.
|
|
"""
|
|
type_name = type(original_node).__name__
|
|
for v in self.visitor_methods.get(f"leave_{type_name}", []):
|
|
v(original_node)
|
|
after_leave = self.after_leave
|
|
if after_leave is not None:
|
|
after_leave(original_node)
|