Beispiel #1
0
def transform_module(transformer: Codemod, code: str) -> TransformResult:
    """
    Given a module in a string and a Codemod to transform the module with,
    execute the codemod on the code and return a TransformResult. This will
    never raise an exception, instead will return a TransformFailure.
    """
    try:
        input_tree = parse_module(code)
        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,
        )
Beispiel #2
0
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,
        )
Beispiel #3
0
def _execute_transform(  # noqa: C901
    transformer: Codemod,
    filename: str,
    config: ExecutionConfig,
) -> ExecutionResult:
    for pattern in config.blacklist_patterns:
        if re.fullmatch(pattern, filename):
            return ExecutionResult(
                filename=filename,
                changed=False,
                transform_result=TransformSkip(
                    skip_reason=SkipReason.BLACKLISTED,
                    skip_description=f"Blacklisted by pattern {pattern}.",
                ),
            )

    try:
        with open(filename, "rb") as fp:
            oldcode = fp.read()

        # Skip generated files
        if (not config.include_generated
                and config.generated_code_marker.encode("utf-8") in oldcode):
            return ExecutionResult(
                filename=filename,
                changed=False,
                transform_result=TransformSkip(
                    skip_reason=SkipReason.GENERATED,
                    skip_description="Generated file.",
                ),
            )

        # Somewhat gross hack to provide the filename in the transform's context.
        # We do this after the fork so that a context that was initialized with
        # some defaults before calling parallel_exec_transform_with_prettyprint
        # will be updated per-file.
        transformer.context = replace(
            transformer.context,
            filename=filename,
            full_module_name=_calculate_module(config.repo_root, filename),
        )

        # Run the transform, bail if we failed or if we aren't formatting code
        try:
            input_tree = parse_module(
                oldcode,
                config=(PartialParserConfig(
                    python_version=str(config.python_version))
                        if config.python_version is not None else
                        PartialParserConfig()),
            )
            output_tree = transformer.transform_module(input_tree)
            newcode = output_tree.bytes
            encoding = output_tree.encoding
        except KeyboardInterrupt:
            return ExecutionResult(filename=filename,
                                   changed=False,
                                   transform_result=TransformExit())
        except SkipFile as ex:
            return ExecutionResult(
                filename=filename,
                changed=False,
                transform_result=TransformSkip(
                    skip_reason=SkipReason.OTHER,
                    skip_description=str(ex),
                    warning_messages=transformer.context.warnings,
                ),
            )
        except Exception as ex:
            return ExecutionResult(
                filename=filename,
                changed=False,
                transform_result=TransformFailure(
                    error=ex,
                    traceback_str=traceback.format_exc(),
                    warning_messages=transformer.context.warnings,
                ),
            )

        # Call formatter if needed, but only if we actually changed something in this
        # file
        if config.format_code and newcode != oldcode:
            try:
                newcode = invoke_formatter(config.formatter_args, newcode)
            except KeyboardInterrupt:
                return ExecutionResult(
                    filename=filename,
                    changed=False,
                    transform_result=TransformExit(),
                )
            except Exception as ex:
                return ExecutionResult(
                    filename=filename,
                    changed=False,
                    transform_result=TransformFailure(
                        error=ex,
                        traceback_str=traceback.format_exc(),
                        warning_messages=transformer.context.warnings,
                    ),
                )

        # Format as unified diff if needed, otherwise save it back
        changed = oldcode != newcode
        if config.unified_diff:
            newcode = diff_code(
                oldcode.decode(encoding),
                newcode.decode(encoding),
                config.unified_diff,
                filename=filename,
            )
        else:
            # Write back if we changed
            if changed:
                with open(filename, "wb") as fp:
                    fp.write(newcode)
            # Not strictly necessary, but saves space in pickle since we won't use it
            newcode = ""

        # Inform success
        return ExecutionResult(
            filename=filename,
            changed=changed,
            transform_result=TransformSuccess(
                warning_messages=transformer.context.warnings, code=newcode),
        )
    except KeyboardInterrupt:
        return ExecutionResult(filename=filename,
                               changed=False,
                               transform_result=TransformExit())
    except Exception as ex:
        return ExecutionResult(
            filename=filename,
            changed=False,
            transform_result=TransformFailure(
                error=ex,
                traceback_str=traceback.format_exc(),
                warning_messages=transformer.context.warnings,
            ),
        )
Beispiel #4
0
def _parallel_exec_process_stub(  # noqa: C901
    result_queue: "Queue[ParallelExecResult]",
    transformer: Codemod,
    filename: str,
    unified_diff: Optional[int],
    include_generated: bool,
    generated_code_marker: str,
    format_code: bool,
    formatter_args: Sequence[str],
    blacklist_patterns: Sequence[str],
) -> None:
    for pattern in blacklist_patterns:
        if re.fullmatch(pattern, filename):
            result_queue.put(
                ParallelExecResult(
                    filename=filename,
                    changed=False,
                    transform_result=TransformSkip(
                        skip_reason=SkipReason.BLACKLISTED,
                        skip_description=f"Blacklisted by pattern {pattern}.",
                    ),
                )
            )
            return

    try:
        with open(filename, "rb") as fp:
            oldcode = fp.read()

        # Skip generated files
        if not include_generated and generated_code_marker.encode("utf-8") in oldcode:
            result_queue.put(
                ParallelExecResult(
                    filename=filename,
                    changed=False,
                    transform_result=TransformSkip(
                        skip_reason=SkipReason.GENERATED,
                        skip_description="Generated file.",
                    ),
                )
            )
            return

        # Somewhat gross hack to provide the filename in the transform's context.
        # We do this after the fork so that a context that was initialized with
        # some defaults before calling parallel_exec_transform_with_prettyprint
        # will be updated per-file.
        transformer.context = replace(transformer.context, filename=filename)

        # Run the transform, bail if we failed or if we aren't formatting code
        try:
            input_tree = parse_module(oldcode)
            output_tree = transformer.transform_module(input_tree)
            newcode = output_tree.bytes
            encoding = output_tree.encoding
        except KeyboardInterrupt:
            result_queue.put(
                ParallelExecResult(
                    filename=filename, changed=False, transform_result=TransformExit()
                )
            )
            return
        except SkipFile as ex:
            result_queue.put(
                ParallelExecResult(
                    filename=filename,
                    changed=False,
                    transform_result=TransformSkip(
                        skip_reason=SkipReason.OTHER,
                        skip_description=str(ex),
                        warning_messages=transformer.context.warnings,
                    ),
                )
            )
            return
        except Exception as ex:
            result_queue.put(
                ParallelExecResult(
                    filename=filename,
                    changed=False,
                    transform_result=TransformFailure(
                        error=ex,
                        traceback_str=traceback.format_exc(),
                        warning_messages=transformer.context.warnings,
                    ),
                )
            )
            return

        # Call formatter if needed, but only if we actually changed something in this
        # file
        if format_code and newcode != oldcode:
            try:
                newcode = invoke_formatter(formatter_args, newcode)
            except KeyboardInterrupt:
                result_queue.put(
                    ParallelExecResult(
                        filename=filename,
                        changed=False,
                        transform_result=TransformExit(),
                    )
                )
                return
            except Exception as ex:
                result_queue.put(
                    ParallelExecResult(
                        filename=filename,
                        changed=False,
                        transform_result=TransformFailure(
                            error=ex,
                            traceback_str=traceback.format_exc(),
                            warning_messages=transformer.context.warnings,
                        ),
                    )
                )
                return

        # Format as unified diff if needed, otherwise save it back
        changed = oldcode != newcode
        if unified_diff:
            newcode = diff_code(
                oldcode.decode(encoding),
                newcode.decode(encoding),
                unified_diff,
                filename=filename,
            )
        else:
            # Write back if we changed
            if changed:
                with open(filename, "wb") as fp:
                    fp.write(newcode)
            # Not strictly necessary, but saves space in pickle since we won't use it
            newcode = ""

        # Inform success
        result_queue.put(
            ParallelExecResult(
                filename=filename,
                changed=changed,
                transform_result=TransformSuccess(
                    warning_messages=transformer.context.warnings, code=newcode
                ),
            )
        )
    except KeyboardInterrupt:
        result_queue.put(
            ParallelExecResult(
                filename=filename, changed=False, transform_result=TransformExit()
            )
        )
    except Exception as ex:
        result_queue.put(
            ParallelExecResult(
                filename=filename,
                changed=False,
                transform_result=TransformFailure(
                    error=ex,
                    traceback_str=traceback.format_exc(),
                    warning_messages=transformer.context.warnings,
                ),
            )
        )