Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/_nodes/internal.py: 77%

96 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 

6 

7from contextlib import contextmanager 

8from dataclasses import dataclass, field 

9from typing import Iterable, Iterator, List, Optional, Sequence, TYPE_CHECKING, Union 

10 

11from libcst._add_slots import add_slots 

12from libcst._flatten_sentinel import FlattenSentinel 

13from libcst._maybe_sentinel import MaybeSentinel 

14from libcst._removal_sentinel import RemovalSentinel 

15from libcst._types import CSTNodeT 

16 

17if TYPE_CHECKING: 

18 # These are circular dependencies only used for typing purposes 

19 from libcst._nodes.base import CSTNode # noqa: F401 

20 from libcst._visitors import CSTVisitorT 

21 

22 

23@add_slots 

24@dataclass(frozen=False) 

25class CodegenState: 

26 # These are derived from a Module 

27 default_indent: str 

28 default_newline: str 

29 provider: object = None # overridden by libcst.metadata.position_provider 

30 

31 indent_tokens: List[str] = field(default_factory=list) 

32 tokens: List[str] = field(default_factory=list) 

33 

34 def increase_indent(self, value: str) -> None: 

35 self.indent_tokens.append(value) 

36 

37 def decrease_indent(self) -> None: 

38 self.indent_tokens.pop() 

39 

40 def add_indent_tokens(self) -> None: 

41 self.tokens.extend(self.indent_tokens) 

42 

43 def add_token(self, value: str) -> None: 

44 self.tokens.append(value) 

45 

46 def before_codegen(self, node: "CSTNode") -> None: 

47 pass 

48 

49 def after_codegen(self, node: "CSTNode") -> None: 

50 pass 

51 

52 def pop_trailing_newline(self) -> None: 

53 """ 

54 Called by :meth:`libcst.Module._codegen_impl` at the end of the file to remove 

55 the last token (a trailing newline), assuming the file isn't empty. 

56 """ 

57 if len(self.tokens) > 0: 

58 # EmptyLine and all statements generate newlines, so we can be sure that the 

59 # last token (if we're not an empty file) is a newline. 

60 self.tokens.pop() 

61 

62 @contextmanager 

63 def record_syntactic_position( 

64 self, 

65 node: "CSTNode", 

66 *, 

67 start_node: Optional["CSTNode"] = None, 

68 end_node: Optional["CSTNode"] = None, 

69 ) -> Iterator[None]: 

70 yield 

71 

72 

73def visit_required( 

74 parent: "CSTNode", fieldname: str, node: CSTNodeT, visitor: "CSTVisitorT" 

75) -> CSTNodeT: 

76 """ 

77 Given a node, visits the node using `visitor`. If removal is attempted by the 

78 visitor, an exception is raised. 

79 """ 

80 visitor.on_visit_attribute(parent, fieldname) 

81 result = node.visit(visitor) 

82 if isinstance(result, RemovalSentinel): 

83 raise TypeError( 

84 f"We got a RemovalSentinel while visiting a {type(node).__name__}. This " 

85 + "node's parent does not allow it to be removed." 

86 ) 

87 elif isinstance(result, FlattenSentinel): 

88 raise TypeError( 

89 f"We got a FlattenSentinel while visiting a {type(node).__name__}. This " 

90 + "node's parent does not allow for it to be it to be replaced with a " 

91 + "sequence." 

92 ) 

93 

94 visitor.on_leave_attribute(parent, fieldname) 

95 return result 

96 

97 

98def visit_optional( 

99 parent: "CSTNode", fieldname: str, node: Optional[CSTNodeT], visitor: "CSTVisitorT" 

100) -> Optional[CSTNodeT]: 

101 """ 

102 Given an optional node, visits the node if it exists with `visitor`. If the node is 

103 removed, returns None. 

104 """ 

105 if node is None: 

106 visitor.on_visit_attribute(parent, fieldname) 

107 visitor.on_leave_attribute(parent, fieldname) 

108 return None 

109 visitor.on_visit_attribute(parent, fieldname) 

110 result = node.visit(visitor) 

111 if isinstance(result, FlattenSentinel): 

112 raise TypeError( 

113 f"We got a FlattenSentinel while visiting a {type(node).__name__}. This " 

114 + "node's parent does not allow for it to be it to be replaced with a " 

115 + "sequence." 

116 ) 

117 visitor.on_leave_attribute(parent, fieldname) 

118 return None if isinstance(result, RemovalSentinel) else result 

119 

120 

121def visit_sentinel( 

122 parent: "CSTNode", 

123 fieldname: str, 

124 node: Union[CSTNodeT, MaybeSentinel], 

125 visitor: "CSTVisitorT", 

126) -> Union[CSTNodeT, MaybeSentinel]: 

127 """ 

128 Given a node that can be a real value or a sentinel value, visits the node if it 

129 is real with `visitor`. If the node is removed, returns MaybeSentinel. 

130 """ 

131 if isinstance(node, MaybeSentinel): 

132 visitor.on_visit_attribute(parent, fieldname) 

133 visitor.on_leave_attribute(parent, fieldname) 

134 return MaybeSentinel.DEFAULT 

135 visitor.on_visit_attribute(parent, fieldname) 

136 result = node.visit(visitor) 

137 if isinstance(result, FlattenSentinel): 

138 raise TypeError( 

139 f"We got a FlattenSentinel while visiting a {type(node).__name__}. This " 

140 + "node's parent does not allow for it to be it to be replaced with a " 

141 + "sequence." 

142 ) 

143 visitor.on_leave_attribute(parent, fieldname) 

144 return MaybeSentinel.DEFAULT if isinstance(result, RemovalSentinel) else result 

145 

146 

147def visit_iterable( 

148 parent: "CSTNode", 

149 fieldname: str, 

150 children: Iterable[CSTNodeT], 

151 visitor: "CSTVisitorT", 

152) -> Iterable[CSTNodeT]: 

153 """ 

154 Given an iterable of children, visits each child with `visitor`, and yields the new 

155 children with any `RemovalSentinel` values removed. 

156 """ 

157 visitor.on_visit_attribute(parent, fieldname) 

158 for child in children: 

159 new_child = child.visit(visitor) 

160 if isinstance(new_child, FlattenSentinel): 

161 yield from new_child 

162 elif not isinstance(new_child, RemovalSentinel): 

163 yield new_child 

164 visitor.on_leave_attribute(parent, fieldname) 

165 

166 

167def visit_sequence( 

168 parent: "CSTNode", 

169 fieldname: str, 

170 children: Sequence[CSTNodeT], 

171 visitor: "CSTVisitorT", 

172) -> Sequence[CSTNodeT]: 

173 """ 

174 A convenience wrapper for `visit_iterable` that returns a sequence instead of an 

175 iterable. 

176 """ 

177 return tuple(visit_iterable(parent, fieldname, children, visitor)) 

178 

179 

180def visit_body_iterable( 

181 parent: "CSTNode", 

182 fieldname: str, 

183 children: Sequence[CSTNodeT], 

184 visitor: "CSTVisitorT", 

185) -> Iterable[CSTNodeT]: 

186 """ 

187 Similar to visit_iterable above, but capable of discarding empty SimpleStatementLine 

188 nodes in order to preserve correct pass insertion behavior. 

189 """ 

190 

191 visitor.on_visit_attribute(parent, fieldname) 

192 for child in children: 

193 new_child = child.visit(visitor) 

194 

195 # Don't yield a child if we removed it. 

196 if isinstance(new_child, RemovalSentinel): 

197 continue 

198 

199 # Don't yield a child if the old child wasn't empty 

200 # and the new child is. This means a RemovalSentinel 

201 # caused a child of this node to be dropped, and it 

202 # is now useless. 

203 

204 if isinstance(new_child, FlattenSentinel): 

205 for child_ in new_child: 

206 if (not child._is_removable()) and child_._is_removable(): 

207 continue 

208 yield child_ 

209 else: 

210 if (not child._is_removable()) and new_child._is_removable(): 

211 continue 

212 # Safe to yield child in this case. 

213 yield new_child 

214 visitor.on_leave_attribute(parent, fieldname) 

215 

216 

217def visit_body_sequence( 

218 parent: "CSTNode", 

219 fieldname: str, 

220 children: Sequence[CSTNodeT], 

221 visitor: "CSTVisitorT", 

222) -> Sequence[CSTNodeT]: 

223 """ 

224 A convenience wrapper for `visit_body_iterable` that returns a sequence 

225 instead of an iterable. 

226 """ 

227 return tuple(visit_body_iterable(parent, fieldname, children, visitor))