Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/helpers/module.py: 27%

71 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 dataclasses import dataclass 

7from itertools import islice 

8from pathlib import PurePath 

9from typing import List, Optional 

10 

11from libcst import Comment, EmptyLine, ImportFrom, Module 

12from libcst._types import StrPath 

13from libcst.helpers.expression import get_full_name_for_node 

14 

15 

16def insert_header_comments(node: Module, comments: List[str]) -> Module: 

17 """ 

18 Insert comments after last non-empty line in header. Use this to insert one or more 

19 comments after any copyright preamble in a :class:`~libcst.Module`. Each comment in 

20 the list of ``comments`` must start with a ``#`` and will be placed on its own line 

21 in the appropriate location. 

22 """ 

23 # Split the lines up into a contiguous comment-containing section and 

24 # the empty whitespace section that follows 

25 last_comment_index = -1 

26 for i, line in enumerate(node.header): 

27 if line.comment is not None: 

28 last_comment_index = i 

29 

30 comment_lines = islice(node.header, last_comment_index + 1) 

31 empty_lines = islice(node.header, last_comment_index + 1, None) 

32 inserted_lines = [EmptyLine(comment=Comment(value=comment)) for comment in comments] 

33 # pyre-fixme[60]: Concatenation not yet support for multiple variadic tuples: 

34 # `*comment_lines, *inserted_lines, *empty_lines`. 

35 return node.with_changes(header=(*comment_lines, *inserted_lines, *empty_lines)) 

36 

37 

38def get_absolute_module( 

39 current_module: Optional[str], module_name: Optional[str], num_dots: int 

40) -> Optional[str]: 

41 if num_dots == 0: 

42 # This is an absolute import, so the module is correct. 

43 return module_name 

44 if current_module is None: 

45 # We don't actually have the current module available, so we can't compute 

46 # the absolute module from relative. 

47 return None 

48 # We have the current module, as well as the relative, let's compute the base. 

49 modules = current_module.split(".") 

50 if len(modules) < num_dots: 

51 # This relative import goes past the base of the repository, so we can't calculate it. 

52 return None 

53 base_module = ".".join(modules[:-num_dots]) 

54 # Finally, if the module name was supplied, append it to the end. 

55 if module_name is not None: 

56 # If we went all the way to the top, the base module should be empty, so we 

57 # should return the relative bit as absolute. Otherwise, combine the base 

58 # module and module name using a dot separator. 

59 base_module = ( 

60 f"{base_module}.{module_name}" if len(base_module) > 0 else module_name 

61 ) 

62 # If they tried to import all the way to the root, return None. Otherwise, 

63 # return the module itself. 

64 return base_module if len(base_module) > 0 else None 

65 

66 

67def get_absolute_module_for_import( 

68 current_module: Optional[str], import_node: ImportFrom 

69) -> Optional[str]: 

70 # First, let's try to grab the module name, regardless of relative status. 

71 module = import_node.module 

72 module_name = get_full_name_for_node(module) if module is not None else None 

73 # Now, get the relative import location if it exists. 

74 num_dots = len(import_node.relative) 

75 return get_absolute_module(current_module, module_name, num_dots) 

76 

77 

78def get_absolute_module_for_import_or_raise( 

79 current_module: Optional[str], import_node: ImportFrom 

80) -> str: 

81 module = get_absolute_module_for_import(current_module, import_node) 

82 if module is None: 

83 raise Exception(f"Unable to compute absolute module for {import_node}") 

84 return module 

85 

86 

87def get_absolute_module_from_package( 

88 current_package: Optional[str], module_name: Optional[str], num_dots: int 

89) -> Optional[str]: 

90 if num_dots == 0: 

91 # This is an absolute import, so the module is correct. 

92 return module_name 

93 if current_package is None or current_package == "": 

94 # We don't actually have the current module available, so we can't compute 

95 # the absolute module from relative. 

96 return None 

97 

98 # see importlib._bootstrap._resolve_name 

99 # https://github.com/python/cpython/blob/3.10/Lib/importlib/_bootstrap.py#L902 

100 bits = current_package.rsplit(".", num_dots - 1) 

101 if len(bits) < num_dots: 

102 return None 

103 

104 base = bits[0] 

105 return "{}.{}".format(base, module_name) if module_name else base 

106 

107 

108def get_absolute_module_from_package_for_import( 

109 current_package: Optional[str], import_node: ImportFrom 

110) -> Optional[str]: 

111 # First, let's try to grab the module name, regardless of relative status. 

112 module = import_node.module 

113 module_name = get_full_name_for_node(module) if module is not None else None 

114 # Now, get the relative import location if it exists. 

115 num_dots = len(import_node.relative) 

116 return get_absolute_module_from_package(current_package, module_name, num_dots) 

117 

118 

119def get_absolute_module_from_package_for_import_or_raise( 

120 current_package: Optional[str], import_node: ImportFrom 

121) -> str: 

122 module = get_absolute_module_from_package_for_import(current_package, import_node) 

123 if module is None: 

124 raise Exception(f"Unable to compute absolute module for {import_node}") 

125 return module 

126 

127 

128@dataclass(frozen=True) 

129class ModuleNameAndPackage: 

130 name: str 

131 package: str 

132 

133 

134def calculate_module_and_package( 

135 repo_root: StrPath, filename: StrPath 

136) -> ModuleNameAndPackage: 

137 # Given an absolute repo_root and an absolute filename, calculate the 

138 # python module name for the file. 

139 relative_filename = PurePath(filename).relative_to(repo_root) 

140 relative_filename = relative_filename.with_suffix("") 

141 

142 # handle special cases 

143 if relative_filename.stem in ["__init__", "__main__"]: 

144 relative_filename = relative_filename.parent 

145 package = name = ".".join(relative_filename.parts) 

146 else: 

147 name = ".".join(relative_filename.parts) 

148 package = ".".join(relative_filename.parts[:-1]) 

149 

150 return ModuleNameAndPackage(name, package)