Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/codemod/visitors/_gather_exports.py: 26%
82 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 typing import Set, Union
8import libcst as cst
9import libcst.matchers as m
10from libcst.codemod._context import CodemodContext
11from libcst.codemod._visitor import ContextAwareVisitor
12from libcst.helpers import get_full_name_for_node
15class GatherExportsVisitor(ContextAwareVisitor):
16 """
17 Gathers all explicit exports in a module and stores them as attributes on the
18 instance. Intended to be instantiated and passed to a :class:`~libcst.Module`
19 :meth:`~libcst.CSTNode.visit` method in order to gather up information about
20 exports specified in an ``__all__`` variable inside a module.
22 After visiting a module the following attributes will be populated:
24 explicit_exported_objects
25 A sequence of strings representing objects that the module exports
26 directly. Note that when ``__all__`` is absent, this attribute does not
27 store default exported objects by name.
29 For more information on ``__all__``, please see Python's `Modules Documentation
30 <https://docs.python.org/3/tutorial/modules.html>`_.
31 """
33 def __init__(self, context: CodemodContext) -> None:
34 super().__init__(context)
35 # Track any re-exported objects in an __all__ reference and whether
36 # they're defined or not
37 self.explicit_exported_objects: Set[str] = set()
39 # Presumably at some point in the future it would be useful to grab
40 # a list of all implicitly exported objects. That would go here as
41 # well and would follow Python's rule for importing objects that
42 # do not start with an underscore. Because of that, I named the above
43 # `explicit_exported_objects` instead of just `exported_objects` so
44 # that we have a reasonable place to put implicit objects in the future.
46 # Internal bookkeeping
47 self._is_assigned_export: Set[Union[cst.Tuple, cst.List, cst.Set]] = set()
48 self._in_assigned_export: Set[Union[cst.Tuple, cst.List, cst.Set]] = set()
50 def visit_AnnAssign(self, node: cst.AnnAssign) -> bool:
51 value = node.value
52 if value:
53 if self._handle_assign_target(node.target, value):
54 return True
55 return False
57 def visit_AugAssign(self, node: cst.AugAssign) -> bool:
58 if m.matches(
59 node,
60 m.AugAssign(
61 target=m.Name("__all__"),
62 operator=m.AddAssign(),
63 value=m.List() | m.Tuple(),
64 ),
65 ):
66 value = node.value
67 if isinstance(value, (cst.List, cst.Tuple)):
68 self._is_assigned_export.add(value)
69 return True
70 return False
72 def visit_Assign(self, node: cst.Assign) -> bool:
73 for target_node in node.targets:
74 if self._handle_assign_target(target_node.target, node.value):
75 return True
76 return False
78 def _handle_assign_target(
79 self, target: cst.BaseExpression, value: cst.BaseExpression
80 ) -> bool:
81 target_name = get_full_name_for_node(target)
82 if target_name == "__all__":
83 # Assignments such as `__all__ = ["os"]`
84 # or `__all__ = exports = ["os"]`
85 if isinstance(value, (cst.List, cst.Tuple, cst.Set)):
86 self._is_assigned_export.add(value)
87 return True
88 elif isinstance(target, cst.Tuple) and isinstance(value, cst.Tuple):
89 # Assignments such as `__all__, x = ["os"], []`
90 for element_idx, element_node in enumerate(target.elements):
91 element_name = get_full_name_for_node(element_node.value)
92 if element_name == "__all__":
93 element_value = value.elements[element_idx].value
94 if isinstance(element_value, (cst.List, cst.Tuple, cst.Set)):
95 self._is_assigned_export.add(value)
96 self._is_assigned_export.add(element_value)
97 return True
98 return False
100 def visit_List(self, node: cst.List) -> bool:
101 if node in self._is_assigned_export:
102 self._in_assigned_export.add(node)
103 return True
104 return False
106 def leave_List(self, original_node: cst.List) -> None:
107 self._is_assigned_export.discard(original_node)
108 self._in_assigned_export.discard(original_node)
110 def visit_Tuple(self, node: cst.Tuple) -> bool:
111 if node in self._is_assigned_export:
112 self._in_assigned_export.add(node)
113 return True
114 return False
116 def leave_Tuple(self, original_node: cst.Tuple) -> None:
117 self._is_assigned_export.discard(original_node)
118 self._in_assigned_export.discard(original_node)
120 def visit_Set(self, node: cst.Set) -> bool:
121 if node in self._is_assigned_export:
122 self._in_assigned_export.add(node)
123 return True
124 return False
126 def leave_Set(self, original_node: cst.Set) -> None:
127 self._is_assigned_export.discard(original_node)
128 self._in_assigned_export.discard(original_node)
130 def visit_SimpleString(self, node: cst.SimpleString) -> bool:
131 self._handle_string_export(node)
132 return False
134 def visit_ConcatenatedString(self, node: cst.ConcatenatedString) -> bool:
135 self._handle_string_export(node)
136 return False
138 def _handle_string_export(
139 self, node: Union[cst.SimpleString, cst.ConcatenatedString]
140 ) -> None:
141 if self._in_assigned_export:
142 name = node.evaluated_value
143 if not isinstance(name, str):
144 return
145 self.explicit_exported_objects.add(name)