File: _visitor.py

package info (click to toggle)
python-libcst 1.4.0-1.2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,928 kB
  • sloc: python: 76,235; makefile: 10; sh: 2
file content (108 lines) | stat: -rw-r--r-- 4,958 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#
from typing import Mapping

import libcst as cst
from libcst import MetadataDependent
from libcst.codemod._codemod import Codemod
from libcst.codemod._context import CodemodContext
from libcst.matchers import MatcherDecoratableTransformer, MatcherDecoratableVisitor
from libcst.metadata import ProviderT


class ContextAwareTransformer(Codemod, MatcherDecoratableTransformer):
    """
    A transformer which visits using LibCST. Allows visitor-based mutation of a tree.
    Classes wishing to do arbitrary non-visitor-based mutation on a tree should
    instead subclass from :class:`Codemod` and implement
    :meth:`~Codemod.transform_module_impl`. This is a subclass of
    :class:`~libcst.matchers.MatcherDecoratableTransformer` so all features of matchers
    as well as :class:`~libcst.CSTTransformer` are available to subclasses of this
    class.
    """

    def __init__(self, context: CodemodContext) -> None:
        Codemod.__init__(self, context)
        MatcherDecoratableTransformer.__init__(self)

    def transform_module_impl(self, tree: cst.Module) -> cst.Module:
        return tree.visit(self)


class ContextAwareVisitor(MatcherDecoratableVisitor, MetadataDependent):
    """
    A visitor which visits using LibCST. Allows visitor-based collecting of info
    on a tree. All codemods which wish to implement an information collector should
    subclass from this instead of directly from
    :class:`~libcst.matchers.MatcherDecoratableVisitor` or :class:`~libcst.CSTVisitor`
    since this provides access to the current codemod context. As a result, this
    class allows access to metadata which was calculated in a parent
    :class:`~libcst.codemod.Codemod` through the
    :meth:`~libcst.MetadataDependent.get_metadata` method.

    Note that you cannot directly run a :class:`~libcst.codemod.ContextAwareVisitor`
    using :func:`~libcst.codemod.transform_module` because visitors by definition
    do not transform trees. However, you can instantiate a
    :class:`~libcst.codemod.ContextAwareVisitor` inside a codemod and pass it to the
    :class:`~libcst.CSTNode.visit` method on any node in order to run information
    gathering with metadata and context support.

    Remember that a :class:`~libcst.codemod.ContextAwareVisitor` is a subclass of
    :class:`~libcst.MetadataDependent`, meaning that you still need to declare
    your metadata dependencies with
    :attr:`~libcst.MetadataDependent.METADATA_DEPENDENCIES` before you can retrieve
    metadata using :meth:`~libcst.MetadataDependent.get_metadata`, even if the parent
    codemod has listed its own metadata dependencies. Note also that the dependencies
    listed on this class must be a strict subset of the dependencies listed in the
    parent codemod.
    """

    def __init__(self, context: CodemodContext) -> None:
        MetadataDependent.__init__(self)
        MatcherDecoratableVisitor.__init__(self)
        self.context = context

        dependencies = self.get_inherited_dependencies()
        if dependencies:
            wrapper = self.context.wrapper
            if wrapper is None:
                raise Exception(
                    f"Attempting to instantiate {self.__class__.__name__} outside of "
                    + "an active transform. This means that metadata hasn't been "
                    + "calculated and we cannot successfully create this visitor."
                )
            for dep in dependencies:
                if dep not in wrapper._metadata:
                    raise Exception(
                        f"Attempting to access metadata {dep.__name__} that was not a "
                        + "declared dependency of parent transform! This means it is "
                        + "not possible to compute this value. Please ensure that all "
                        + f"parent transforms of {self.__class__.__name__} declare "
                        + f"{dep.__name__} as a metadata dependency."
                    )
            self.metadata: Mapping[ProviderT, Mapping[cst.CSTNode, object]] = {
                dep: wrapper._metadata[dep] for dep in dependencies
            }

    def warn(self, warning: str) -> None:
        """
        Emit a warning that is displayed to the user who has invoked this codemod.
        """
        self.context.warnings.append(warning)

    @property
    def module(self) -> cst.Module:
        """
        Reference to the currently-traversed module. Note that this is only available
        during a transform itself.
        """
        module = self.context.module
        if module is None:
            raise Exception(
                f"Attempted access of {self.__class__.__name__}.module outside of "
                + "transform_module()."
            )
        return module