mirror of
https://github.com/Instagram/LibCST.git
synced 2025-12-23 10:35:53 +00:00
160 lines
5.3 KiB
Python
160 lines
5.3 KiB
Python
# 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.
|
|
#
|
|
|
|
"""
|
|
Provides everything needed to run a CodemodCommand.
|
|
"""
|
|
|
|
import traceback
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
from typing import Optional, Sequence, Union
|
|
|
|
from libcst import parse_module, PartialParserConfig
|
|
from libcst.codemod._codemod import Codemod
|
|
|
|
# All datastructures defined in this class are pickleable so that they can be used
|
|
# as a return value with the multiprocessing module.
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransformSuccess:
|
|
"""
|
|
A :class:`~libcst.codemod.TransformResult` used when the codemod was successful.
|
|
Stores all the information we might need to display to the user upon success, as
|
|
well as the transformed file contents.
|
|
"""
|
|
|
|
#: All warning messages that were generated during the codemod.
|
|
warning_messages: Sequence[str]
|
|
|
|
#: The updated code, post-codemod.
|
|
code: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransformFailure:
|
|
"""
|
|
A :class:`~libcst.codemod.TransformResult` used when the codemod failed.
|
|
Stores all the information we might need to display to the user upon a failure.
|
|
"""
|
|
|
|
#: All warning messages that were generated before the codemod crashed.
|
|
warning_messages: Sequence[str]
|
|
|
|
#: The exception that was raised during the codemod.
|
|
error: Exception
|
|
|
|
#: The traceback string that was recorded at the time of exception.
|
|
traceback_str: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransformExit:
|
|
"""
|
|
A :class:`~libcst.codemod.TransformResult` used when the codemod was interrupted
|
|
by the user (e.g. KeyboardInterrupt).
|
|
"""
|
|
|
|
#: An empty list of warnings, included so that all
|
|
#: :class:`~libcst.codemod.TransformResult` have a ``warning_messages`` attribute.
|
|
warning_messages: Sequence[str] = ()
|
|
|
|
|
|
class SkipReason(Enum):
|
|
"""
|
|
An enumeration of all valid reasons for a codemod to skip.
|
|
"""
|
|
|
|
#: The module was skipped because we detected that it was generated code, and
|
|
#: we were configured to skip generated files.
|
|
GENERATED = "generated"
|
|
|
|
#: The module was skipped because we detected that it was blacklisted, and we
|
|
#: were configured to skip blacklisted files.
|
|
BLACKLISTED = "blacklisted"
|
|
|
|
#: The module was skipped because the codemod requested us to skip using the
|
|
#: :class:`~libcst.codemod.SkipFile` exception.
|
|
OTHER = "other"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransformSkip:
|
|
"""
|
|
A :class:`~libcst.codemod.TransformResult` used when the codemod requested to
|
|
be skipped. This could be because it's a generated file, or due to filename
|
|
blacklist, or because the transform raised :class:`~libcst.codemod.SkipFile`.
|
|
"""
|
|
|
|
#: The reason that we skipped codemodding this module.
|
|
skip_reason: SkipReason
|
|
|
|
#: The description populated from the :class:`~libcst.codemod.SkipFile` exception.
|
|
skip_description: str
|
|
|
|
#: All warning messages that were generated before the codemod decided to skip.
|
|
warning_messages: Sequence[str] = ()
|
|
|
|
|
|
class SkipFile(Exception):
|
|
"""
|
|
Raise this exception to skip codemodding the current file.
|
|
|
|
The exception message should be the reason for skipping.
|
|
"""
|
|
|
|
|
|
TransformResult = Union[
|
|
TransformSuccess, TransformFailure, TransformExit, TransformSkip
|
|
]
|
|
|
|
|
|
def transform_module(
|
|
transformer: Codemod, code: str, *, python_version: Optional[str] = None
|
|
) -> TransformResult:
|
|
"""
|
|
Given a module as represented by a string and a :class:`~libcst.codemod.Codemod`
|
|
that we wish to run, execute the codemod on the code and return a
|
|
:class:`~libcst.codemod.TransformResult`. This should never raise an exception.
|
|
On success, this returns a :class:`~libcst.codemod.TransformSuccess` containing
|
|
any generated warnings as well as the transformed code. If the codemod is
|
|
interrupted with a Ctrl+C, this returns a :class:`~libcst.codemod.TransformExit`.
|
|
If the codemod elected to skip by throwing a :class:`~libcst.codemod.SkipFile`
|
|
exception, this will return a :class:`~libcst.codemod.TransformSkip` containing
|
|
the reason for skipping as well as any warnings that were generated before
|
|
the codemod decided to skip. If the codemod throws an unexpected exception,
|
|
this will return a :class:`~libcst.codemod.TransformFailure` containing the
|
|
exception that occured as well as any warnings that were generated before the
|
|
codemod crashed.
|
|
"""
|
|
try:
|
|
input_tree = parse_module(
|
|
code,
|
|
config=(
|
|
PartialParserConfig(python_version=python_version)
|
|
if python_version is not None
|
|
else PartialParserConfig()
|
|
),
|
|
)
|
|
output_tree = transformer.transform_module(input_tree)
|
|
return TransformSuccess(
|
|
code=output_tree.code, warning_messages=transformer.context.warnings
|
|
)
|
|
except KeyboardInterrupt:
|
|
return TransformExit()
|
|
except SkipFile as ex:
|
|
return TransformSkip(
|
|
skip_description=str(ex),
|
|
skip_reason=SkipReason.OTHER,
|
|
warning_messages=transformer.context.warnings,
|
|
)
|
|
except Exception as ex:
|
|
return TransformFailure(
|
|
error=ex,
|
|
traceback_str=traceback.format_exc(),
|
|
warning_messages=transformer.context.warnings,
|
|
)
|