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
« 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
11from libcst import Comment, EmptyLine, ImportFrom, Module
12from libcst._types import StrPath
13from libcst.helpers.expression import get_full_name_for_node
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
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))
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
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)
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
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
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
104 base = bits[0]
105 return "{}.{}".format(base, module_name) if module_name else base
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)
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
128@dataclass(frozen=True)
129class ModuleNameAndPackage:
130 name: str
131 package: str
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("")
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])
150 return ModuleNameAndPackage(name, package)