Source code for libcst.codemod._runner

# 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.


[docs] @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
[docs] @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
[docs] @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] = ()
[docs] 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"
[docs] @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] = ()
[docs] 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 ]
[docs] 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, )