Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/codemod/_runner.py: 76%
41 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.
5#
7"""
8Provides everything needed to run a CodemodCommand.
9"""
11import traceback
12from dataclasses import dataclass
13from enum import Enum
14from typing import Optional, Sequence, Union
16from libcst import parse_module, PartialParserConfig
17from libcst.codemod._codemod import Codemod
19# All datastructures defined in this class are pickleable so that they can be used
20# as a return value with the multiprocessing module.
23@dataclass(frozen=True)
24class TransformSuccess:
25 """
26 A :class:`~libcst.codemod.TransformResult` used when the codemod was successful.
27 Stores all the information we might need to display to the user upon success, as
28 well as the transformed file contents.
29 """
31 #: All warning messages that were generated during the codemod.
32 warning_messages: Sequence[str]
34 #: The updated code, post-codemod.
35 code: str
38@dataclass(frozen=True)
39class TransformFailure:
40 """
41 A :class:`~libcst.codemod.TransformResult` used when the codemod failed.
42 Stores all the information we might need to display to the user upon a failure.
43 """
45 #: All warning messages that were generated before the codemod crashed.
46 warning_messages: Sequence[str]
48 #: The exception that was raised during the codemod.
49 error: Exception
51 #: The traceback string that was recorded at the time of exception.
52 traceback_str: str
55@dataclass(frozen=True)
56class TransformExit:
57 """
58 A :class:`~libcst.codemod.TransformResult` used when the codemod was interrupted
59 by the user (e.g. KeyboardInterrupt).
60 """
62 #: An empty list of warnings, included so that all
63 #: :class:`~libcst.codemod.TransformResult` have a ``warning_messages`` attribute.
64 warning_messages: Sequence[str] = ()
67class SkipReason(Enum):
68 """
69 An enumeration of all valid reasons for a codemod to skip.
70 """
72 #: The module was skipped because we detected that it was generated code, and
73 #: we were configured to skip generated files.
74 GENERATED = "generated"
76 #: The module was skipped because we detected that it was blacklisted, and we
77 #: were configured to skip blacklisted files.
78 BLACKLISTED = "blacklisted"
80 #: The module was skipped because the codemod requested us to skip using the
81 #: :class:`~libcst.codemod.SkipFile` exception.
82 OTHER = "other"
85@dataclass(frozen=True)
86class TransformSkip:
87 """
88 A :class:`~libcst.codemod.TransformResult` used when the codemod requested to
89 be skipped. This could be because it's a generated file, or due to filename
90 blacklist, or because the transform raised :class:`~libcst.codemod.SkipFile`.
91 """
93 #: The reason that we skipped codemodding this module.
94 skip_reason: SkipReason
96 #: The description populated from the :class:`~libcst.codemod.SkipFile` exception.
97 skip_description: str
99 #: All warning messages that were generated before the codemod decided to skip.
100 warning_messages: Sequence[str] = ()
103class SkipFile(Exception):
104 """
105 Raise this exception to skip codemodding the current file.
107 The exception message should be the reason for skipping.
108 """
111TransformResult = Union[
112 TransformSuccess, TransformFailure, TransformExit, TransformSkip
113]
116def transform_module(
117 transformer: Codemod, code: str, *, python_version: Optional[str] = None
118) -> TransformResult:
119 """
120 Given a module as represented by a string and a :class:`~libcst.codemod.Codemod`
121 that we wish to run, execute the codemod on the code and return a
122 :class:`~libcst.codemod.TransformResult`. This should never raise an exception.
123 On success, this returns a :class:`~libcst.codemod.TransformSuccess` containing
124 any generated warnings as well as the transformed code. If the codemod is
125 interrupted with a Ctrl+C, this returns a :class:`~libcst.codemod.TransformExit`.
126 If the codemod elected to skip by throwing a :class:`~libcst.codemod.SkipFile`
127 exception, this will return a :class:`~libcst.codemod.TransformSkip` containing
128 the reason for skipping as well as any warnings that were generated before
129 the codemod decided to skip. If the codemod throws an unexpected exception,
130 this will return a :class:`~libcst.codemod.TransformFailure` containing the
131 exception that occured as well as any warnings that were generated before the
132 codemod crashed.
133 """
134 try:
135 input_tree = parse_module(
136 code,
137 config=(
138 PartialParserConfig(python_version=python_version)
139 if python_version is not None
140 else PartialParserConfig()
141 ),
142 )
143 output_tree = transformer.transform_module(input_tree)
144 return TransformSuccess(
145 code=output_tree.code, warning_messages=transformer.context.warnings
146 )
147 except KeyboardInterrupt:
148 return TransformExit()
149 except SkipFile as ex:
150 return TransformSkip(
151 skip_description=str(ex),
152 skip_reason=SkipReason.OTHER,
153 warning_messages=transformer.context.warnings,
154 )
155 except Exception as ex:
156 return TransformFailure(
157 error=ex,
158 traceback_str=traceback.format_exc(),
159 warning_messages=transformer.context.warnings,
160 )