Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/codemod/_codemod.py: 75%

48 statements  

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

6from abc import ABC, abstractmethod 

7from contextlib import contextmanager 

8from dataclasses import replace 

9from typing import Generator 

10 

11from libcst import MetadataDependent, MetadataWrapper, Module 

12from libcst.codemod._context import CodemodContext 

13 

14 

15class Codemod(MetadataDependent, ABC): 

16 """ 

17 Abstract base class that all codemods must subclass from. Classes wishing 

18 to perform arbitrary, non-visitor-based mutations on a tree should subclass 

19 from this class directly. Classes wishing to perform visitor-based mutation 

20 should instead subclass from :class:`~libcst.codemod.ContextAwareTransformer`. 

21 

22 Note that a :class:`~libcst.codemod.Codemod` is a subclass of 

23 :class:`~libcst.MetadataDependent`, meaning that you can declare metadata 

24 dependencies with the :attr:`~libcst.MetadataDependent.METADATA_DEPENDENCIES` 

25 class property and while you are executing a transform you can call 

26 :meth:`~libcst.MetadataDependent.get_metadata` to retrieve 

27 the resolved metadata. 

28 """ 

29 

30 def __init__(self, context: CodemodContext) -> None: 

31 MetadataDependent.__init__(self) 

32 self.context: CodemodContext = context 

33 

34 def should_allow_multiple_passes(self) -> bool: 

35 """ 

36 Override this and return ``True`` to allow your transform to be called 

37 repeatedly until the tree doesn't change between passes. By default, 

38 this is off, and should suffice for most transforms. 

39 """ 

40 return False 

41 

42 def warn(self, warning: str) -> None: 

43 """ 

44 Emit a warning that is displayed to the user who has invoked this codemod. 

45 """ 

46 self.context.warnings.append(warning) 

47 

48 @property 

49 def module(self) -> Module: 

50 """ 

51 Reference to the currently-traversed module. Note that this is only available 

52 during the execution of a codemod. The module reference is particularly 

53 handy if you want to use :meth:`libcst.Module.code_for_node` or 

54 :attr:`libcst.Module.config_for_parsing` and don't wish to track a reference 

55 to the top-level module manually. 

56 """ 

57 module = self.context.module 

58 if module is None: 

59 raise Exception( 

60 f"Attempted access of {self.__class__.__name__}.module outside of " 

61 + "transform_module()." 

62 ) 

63 return module 

64 

65 @abstractmethod 

66 def transform_module_impl(self, tree: Module) -> Module: 

67 """ 

68 Override this with your transform. You should take in the tree, optionally 

69 mutate it and then return the mutated version. The module reference and all 

70 calculated metadata are available for the lifetime of this function. 

71 """ 

72 ... 

73 

74 @contextmanager 

75 def _handle_metadata_reference( 

76 self, module: Module 

77 ) -> Generator[Module, None, None]: 

78 oldwrapper = self.context.wrapper 

79 metadata_manager = self.context.metadata_manager 

80 filename = self.context.filename 

81 if metadata_manager is not None and filename: 

82 # We can look up full-repo metadata for this codemod! 

83 cache = metadata_manager.get_cache_for_path(filename) 

84 wrapper = MetadataWrapper(module, cache=cache) 

85 else: 

86 # We are missing either the repo manager or the current path, 

87 # which can happen when we are codemodding from stdin or when 

88 # an upstream dependency manually instantiates us. 

89 wrapper = MetadataWrapper(module) 

90 

91 with self.resolve(wrapper): 

92 self.context = replace(self.context, wrapper=wrapper) 

93 try: 

94 yield wrapper.module 

95 finally: 

96 self.context = replace(self.context, wrapper=oldwrapper) 

97 

98 def transform_module(self, tree: Module) -> Module: 

99 """ 

100 Transform entrypoint which handles multi-pass logic and metadata calculation 

101 for you. This is the method that you should call if you wish to invoke a 

102 codemod directly. This is the method that is called by 

103 :func:`~libcst.codemod.transform_module`. 

104 """ 

105 

106 if not self.should_allow_multiple_passes(): 

107 with self._handle_metadata_reference(tree) as tree_with_metadata: 

108 return self.transform_module_impl(tree_with_metadata) 

109 

110 # We allow multiple passes, so we execute 1+ passes until there are 

111 # no more changes. 

112 previous: Module = tree 

113 while True: 

114 with self._handle_metadata_reference(tree) as tree_with_metadata: 

115 tree = self.transform_module_impl(tree_with_metadata) 

116 if tree.deep_equals(previous): 

117 break 

118 previous = tree 

119 return tree