Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/_parser/py_whitespace_parser.py: 12%

114 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 typing import List, Optional, Sequence, Tuple, Union 

7 

8from libcst._nodes.whitespace import ( 

9 Comment, 

10 COMMENT_RE, 

11 EmptyLine, 

12 Newline, 

13 NEWLINE_RE, 

14 ParenthesizedWhitespace, 

15 SIMPLE_WHITESPACE_RE, 

16 SimpleWhitespace, 

17 TrailingWhitespace, 

18) 

19from libcst._parser.types.config import BaseWhitespaceParserConfig 

20from libcst._parser.types.whitespace_state import WhitespaceState as State 

21 

22# BEGIN PARSER ENTRYPOINTS 

23 

24 

25def parse_simple_whitespace( 

26 config: BaseWhitespaceParserConfig, state: State 

27) -> SimpleWhitespace: 

28 # The match never fails because the pattern can match an empty string 

29 lines = config.lines 

30 # pyre-fixme[16]: Optional type has no attribute `group`. 

31 ws_line = SIMPLE_WHITESPACE_RE.match(lines[state.line - 1], state.column).group(0) 

32 ws_line_list = [ws_line] 

33 while "\\" in ws_line: 

34 # continuation character 

35 state.line += 1 

36 state.column = 0 

37 ws_line = SIMPLE_WHITESPACE_RE.match(lines[state.line - 1], state.column).group( 

38 0 

39 ) 

40 ws_line_list.append(ws_line) 

41 

42 # TODO: we could special-case the common case where there's no continuation 

43 # character to avoid list construction and joining. 

44 

45 # once we've finished collecting continuation characters 

46 state.column += len(ws_line) 

47 return SimpleWhitespace("".join(ws_line_list)) 

48 

49 

50def parse_empty_lines( 

51 config: BaseWhitespaceParserConfig, 

52 state: State, 

53 *, 

54 override_absolute_indent: Optional[str] = None, 

55) -> Sequence[EmptyLine]: 

56 # If override_absolute_indent is true, then we need to parse all lines up 

57 # to and including the last line that is indented at our level. These all 

58 # belong to the footer and not to the next line's leading_lines. All lines 

59 # that have indent=False and come after the last line where indent=True 

60 # do not belong to this node. 

61 state_for_line = State( 

62 state.line, state.column, state.absolute_indent, state.is_parenthesized 

63 ) 

64 lines: List[Tuple[State, EmptyLine]] = [] 

65 while True: 

66 el = _parse_empty_line( 

67 config, state_for_line, override_absolute_indent=override_absolute_indent 

68 ) 

69 if el is None: 

70 break 

71 

72 # Store the updated state with the element we parsed. Then make a new state 

73 # clone for the next element. 

74 lines.append((state_for_line, el)) 

75 state_for_line = State( 

76 state_for_line.line, 

77 state_for_line.column, 

78 state.absolute_indent, 

79 state.is_parenthesized, 

80 ) 

81 

82 if override_absolute_indent is not None: 

83 # We need to find the last element that is indented, and then split the list 

84 # at that point. 

85 for i in range(len(lines) - 1, -1, -1): 

86 if lines[i][1].indent: 

87 lines = lines[: (i + 1)] 

88 break 

89 else: 

90 # We didn't find any lines, throw them all away 

91 lines = [] 

92 

93 if lines: 

94 # Update the state line and column to match the last line actually parsed. 

95 final_state: State = lines[-1][0] 

96 state.line = final_state.line 

97 state.column = final_state.column 

98 return [r[1] for r in lines] 

99 

100 

101def parse_trailing_whitespace( 

102 config: BaseWhitespaceParserConfig, state: State 

103) -> TrailingWhitespace: 

104 trailing_whitespace = _parse_trailing_whitespace(config, state) 

105 if trailing_whitespace is None: 

106 raise Exception( 

107 "Internal Error: Failed to parse TrailingWhitespace. This should never " 

108 + "happen because a TrailingWhitespace is never optional in the grammar, " 

109 + "so this error should've been caught by parso first." 

110 ) 

111 return trailing_whitespace 

112 

113 

114def parse_parenthesizable_whitespace( 

115 config: BaseWhitespaceParserConfig, state: State 

116) -> Union[SimpleWhitespace, ParenthesizedWhitespace]: 

117 if state.is_parenthesized: 

118 # First, try parenthesized (don't need speculation because it either 

119 # parses or doesn't modify state). 

120 parenthesized_whitespace = _parse_parenthesized_whitespace(config, state) 

121 if parenthesized_whitespace is not None: 

122 return parenthesized_whitespace 

123 # Now, just parse and return a simple whitespace 

124 return parse_simple_whitespace(config, state) 

125 

126 

127# END PARSER ENTRYPOINTS 

128# BEGIN PARSER INTERNAL PRODUCTIONS 

129 

130 

131def _parse_empty_line( 

132 config: BaseWhitespaceParserConfig, 

133 state: State, 

134 *, 

135 override_absolute_indent: Optional[str] = None, 

136) -> Optional[EmptyLine]: 

137 # begin speculative parsing 

138 speculative_state = State( 

139 state.line, state.column, state.absolute_indent, state.is_parenthesized 

140 ) 

141 try: 

142 indent = _parse_indent( 

143 config, speculative_state, override_absolute_indent=override_absolute_indent 

144 ) 

145 except Exception: 

146 # We aren't on a new line, speculative parsing failed 

147 return None 

148 whitespace = parse_simple_whitespace(config, speculative_state) 

149 comment = _parse_comment(config, speculative_state) 

150 newline = _parse_newline(config, speculative_state) 

151 if newline is None: 

152 # speculative parsing failed 

153 return None 

154 # speculative parsing succeeded 

155 state.line = speculative_state.line 

156 state.column = speculative_state.column 

157 # don't need to copy absolute_indent/is_parenthesized because they don't change. 

158 return EmptyLine(indent, whitespace, comment, newline) 

159 

160 

161def _parse_indent( 

162 config: BaseWhitespaceParserConfig, 

163 state: State, 

164 *, 

165 override_absolute_indent: Optional[str] = None, 

166) -> bool: 

167 """ 

168 Returns True if indentation was found, otherwise False. 

169 """ 

170 absolute_indent = ( 

171 override_absolute_indent 

172 if override_absolute_indent is not None 

173 else state.absolute_indent 

174 ) 

175 line_str = config.lines[state.line - 1] 

176 if state.column != 0: 

177 if state.column == len(line_str) and state.line == len(config.lines): 

178 # We're at EOF, treat this as a failed speculative parse 

179 return False 

180 raise Exception("Internal Error: Column should be 0 when parsing an indent.") 

181 if line_str.startswith(absolute_indent, state.column): 

182 state.column += len(absolute_indent) 

183 return True 

184 return False 

185 

186 

187def _parse_comment( 

188 config: BaseWhitespaceParserConfig, state: State 

189) -> Optional[Comment]: 

190 comment_match = COMMENT_RE.match(config.lines[state.line - 1], state.column) 

191 if comment_match is None: 

192 return None 

193 comment = comment_match.group(0) 

194 state.column += len(comment) 

195 return Comment(comment) 

196 

197 

198def _parse_newline( 

199 config: BaseWhitespaceParserConfig, state: State 

200) -> Optional[Newline]: 

201 # begin speculative parsing 

202 line_str = config.lines[state.line - 1] 

203 newline_match = NEWLINE_RE.match(line_str, state.column) 

204 if newline_match is not None: 

205 # speculative parsing succeeded 

206 newline_str = newline_match.group(0) 

207 state.column += len(newline_str) 

208 if state.column != len(line_str): 

209 raise Exception("Internal Error: Found a newline, but it wasn't the EOL.") 

210 if state.line < len(config.lines): 

211 # this newline was the end of a line, and there's another line, 

212 # therefore we should move to the next line 

213 state.line += 1 

214 state.column = 0 

215 if newline_str == config.default_newline: 

216 # Just inherit it from the Module instead of explicitly setting it. 

217 return Newline() 

218 else: 

219 return Newline(newline_str) 

220 else: # no newline was found, speculative parsing failed 

221 return None 

222 

223 

224def _parse_trailing_whitespace( 

225 config: BaseWhitespaceParserConfig, state: State 

226) -> Optional[TrailingWhitespace]: 

227 # Begin speculative parsing 

228 speculative_state = State( 

229 state.line, state.column, state.absolute_indent, state.is_parenthesized 

230 ) 

231 whitespace = parse_simple_whitespace(config, speculative_state) 

232 comment = _parse_comment(config, speculative_state) 

233 newline = _parse_newline(config, speculative_state) 

234 if newline is None: 

235 # Speculative parsing failed 

236 return None 

237 # Speculative parsing succeeded 

238 state.line = speculative_state.line 

239 state.column = speculative_state.column 

240 # don't need to copy absolute_indent/is_parenthesized because they don't change. 

241 return TrailingWhitespace(whitespace, comment, newline) 

242 

243 

244def _parse_parenthesized_whitespace( 

245 config: BaseWhitespaceParserConfig, state: State 

246) -> Optional[ParenthesizedWhitespace]: 

247 first_line = _parse_trailing_whitespace(config, state) 

248 if first_line is None: 

249 # Speculative parsing failed 

250 return None 

251 empty_lines = () 

252 while True: 

253 empty_line = _parse_empty_line(config, state) 

254 if empty_line is None: 

255 # This isn't an empty line, so parse it below 

256 break 

257 empty_lines = empty_lines + (empty_line,) 

258 indent = _parse_indent(config, state) 

259 last_line = parse_simple_whitespace(config, state) 

260 return ParenthesizedWhitespace(first_line, empty_lines, indent, last_line)