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, )
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, )
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, ), )
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, ), ) )