Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi/parser_utils.py: 19%

199 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1import re 

2import textwrap 

3from ast import literal_eval 

4from inspect import cleandoc 

5from weakref import WeakKeyDictionary 

6 

7from parso.python import tree 

8from parso.cache import parser_cache 

9from parso import split_lines 

10 

11_EXECUTE_NODES = {'funcdef', 'classdef', 'import_from', 'import_name', 'test', 

12 'or_test', 'and_test', 'not_test', 'comparison', 'expr', 

13 'xor_expr', 'and_expr', 'shift_expr', 'arith_expr', 

14 'atom_expr', 'term', 'factor', 'power', 'atom'} 

15 

16_FLOW_KEYWORDS = ( 

17 'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while' 

18) 

19 

20 

21def get_executable_nodes(node, last_added=False): 

22 """ 

23 For static analysis. 

24 """ 

25 result = [] 

26 typ = node.type 

27 if typ == 'name': 

28 next_leaf = node.get_next_leaf() 

29 if last_added is False and node.parent.type != 'param' and next_leaf != '=': 

30 result.append(node) 

31 elif typ == 'expr_stmt': 

32 # I think inferring the statement (and possibly returned arrays), 

33 # should be enough for static analysis. 

34 result.append(node) 

35 for child in node.children: 

36 result += get_executable_nodes(child, last_added=True) 

37 elif typ == 'decorator': 

38 # decorator 

39 if node.children[-2] == ')': 

40 node = node.children[-3] 

41 if node != '(': 

42 result += get_executable_nodes(node) 

43 else: 

44 try: 

45 children = node.children 

46 except AttributeError: 

47 pass 

48 else: 

49 if node.type in _EXECUTE_NODES and not last_added: 

50 result.append(node) 

51 

52 for child in children: 

53 result += get_executable_nodes(child, last_added) 

54 

55 return result 

56 

57 

58def get_sync_comp_fors(comp_for): 

59 yield comp_for 

60 last = comp_for.children[-1] 

61 while True: 

62 if last.type == 'comp_for': 

63 yield last.children[1] # Ignore the async. 

64 elif last.type == 'sync_comp_for': 

65 yield last 

66 elif not last.type == 'comp_if': 

67 break 

68 last = last.children[-1] 

69 

70 

71def for_stmt_defines_one_name(for_stmt): 

72 """ 

73 Returns True if only one name is returned: ``for x in y``. 

74 Returns False if the for loop is more complicated: ``for x, z in y``. 

75 

76 :returns: bool 

77 """ 

78 return for_stmt.children[1].type == 'name' 

79 

80 

81def get_flow_branch_keyword(flow_node, node): 

82 start_pos = node.start_pos 

83 if not (flow_node.start_pos < start_pos <= flow_node.end_pos): 

84 raise ValueError('The node is not part of the flow.') 

85 

86 keyword = None 

87 for i, child in enumerate(flow_node.children): 

88 if start_pos < child.start_pos: 

89 return keyword 

90 first_leaf = child.get_first_leaf() 

91 if first_leaf in _FLOW_KEYWORDS: 

92 keyword = first_leaf 

93 return None 

94 

95 

96def clean_scope_docstring(scope_node): 

97 """ Returns a cleaned version of the docstring token. """ 

98 node = scope_node.get_doc_node() 

99 if node is not None: 

100 # TODO We have to check next leaves until there are no new 

101 # leaves anymore that might be part of the docstring. A 

102 # docstring can also look like this: ``'foo' 'bar' 

103 # Returns a literal cleaned version of the ``Token``. 

104 return cleandoc(safe_literal_eval(node.value)) 

105 return '' 

106 

107 

108def find_statement_documentation(tree_node): 

109 if tree_node.type == 'expr_stmt': 

110 tree_node = tree_node.parent # simple_stmt 

111 maybe_string = tree_node.get_next_sibling() 

112 if maybe_string is not None: 

113 if maybe_string.type == 'simple_stmt': 

114 maybe_string = maybe_string.children[0] 

115 if maybe_string.type == 'string': 

116 return cleandoc(safe_literal_eval(maybe_string.value)) 

117 return '' 

118 

119 

120def safe_literal_eval(value): 

121 first_two = value[:2].lower() 

122 if first_two[0] == 'f' or first_two in ('fr', 'rf'): 

123 # literal_eval is not able to resovle f literals. We have to do that 

124 # manually, but that's right now not implemented. 

125 return '' 

126 

127 return literal_eval(value) 

128 

129 

130def get_signature(funcdef, width=72, call_string=None, 

131 omit_first_param=False, omit_return_annotation=False): 

132 """ 

133 Generate a string signature of a function. 

134 

135 :param width: Fold lines if a line is longer than this value. 

136 :type width: int 

137 :arg func_name: Override function name when given. 

138 :type func_name: str 

139 

140 :rtype: str 

141 """ 

142 # Lambdas have no name. 

143 if call_string is None: 

144 if funcdef.type == 'lambdef': 

145 call_string = '<lambda>' 

146 else: 

147 call_string = funcdef.name.value 

148 params = funcdef.get_params() 

149 if omit_first_param: 

150 params = params[1:] 

151 p = '(' + ''.join(param.get_code() for param in params).strip() + ')' 

152 # TODO this is pretty bad, we should probably just normalize. 

153 p = re.sub(r'\s+', ' ', p) 

154 if funcdef.annotation and not omit_return_annotation: 

155 rtype = " ->" + funcdef.annotation.get_code() 

156 else: 

157 rtype = "" 

158 code = call_string + p + rtype 

159 

160 return '\n'.join(textwrap.wrap(code, width)) 

161 

162 

163def move(node, line_offset): 

164 """ 

165 Move the `Node` start_pos. 

166 """ 

167 try: 

168 children = node.children 

169 except AttributeError: 

170 node.line += line_offset 

171 else: 

172 for c in children: 

173 move(c, line_offset) 

174 

175 

176def get_following_comment_same_line(node): 

177 """ 

178 returns (as string) any comment that appears on the same line, 

179 after the node, including the # 

180 """ 

181 try: 

182 if node.type == 'for_stmt': 

183 whitespace = node.children[5].get_first_leaf().prefix 

184 elif node.type == 'with_stmt': 

185 whitespace = node.children[3].get_first_leaf().prefix 

186 elif node.type == 'funcdef': 

187 # actually on the next line 

188 whitespace = node.children[4].get_first_leaf().get_next_leaf().prefix 

189 else: 

190 whitespace = node.get_last_leaf().get_next_leaf().prefix 

191 except AttributeError: 

192 return None 

193 except ValueError: 

194 # TODO in some particular cases, the tree doesn't seem to be linked 

195 # correctly 

196 return None 

197 if "#" not in whitespace: 

198 return None 

199 comment = whitespace[whitespace.index("#"):] 

200 if "\r" in comment: 

201 comment = comment[:comment.index("\r")] 

202 if "\n" in comment: 

203 comment = comment[:comment.index("\n")] 

204 return comment 

205 

206 

207def is_scope(node): 

208 t = node.type 

209 if t == 'comp_for': 

210 # Starting with Python 3.8, async is outside of the statement. 

211 return node.children[1].type != 'sync_comp_for' 

212 

213 return t in ('file_input', 'classdef', 'funcdef', 'lambdef', 'sync_comp_for') 

214 

215 

216def _get_parent_scope_cache(func): 

217 cache = WeakKeyDictionary() 

218 

219 def wrapper(parso_cache_node, node, include_flows=False): 

220 if parso_cache_node is None: 

221 return func(node, include_flows) 

222 

223 try: 

224 for_module = cache[parso_cache_node] 

225 except KeyError: 

226 for_module = cache[parso_cache_node] = {} 

227 

228 try: 

229 return for_module[node] 

230 except KeyError: 

231 result = for_module[node] = func(node, include_flows) 

232 return result 

233 return wrapper 

234 

235 

236def get_parent_scope(node, include_flows=False): 

237 """ 

238 Returns the underlying scope. 

239 """ 

240 scope = node.parent 

241 if scope is None: 

242 return None # It's a module already. 

243 

244 while True: 

245 if is_scope(scope): 

246 if scope.type in ('classdef', 'funcdef', 'lambdef'): 

247 index = scope.children.index(':') 

248 if scope.children[index].start_pos >= node.start_pos: 

249 if node.parent.type == 'param' and node.parent.name == node: 

250 pass 

251 elif node.parent.type == 'tfpdef' and node.parent.children[0] == node: 

252 pass 

253 else: 

254 scope = scope.parent 

255 continue 

256 return scope 

257 elif include_flows and isinstance(scope, tree.Flow): 

258 # The cursor might be on `if foo`, so the parent scope will not be 

259 # the if, but the parent of the if. 

260 if not (scope.type == 'if_stmt' 

261 and any(n.start_pos <= node.start_pos < n.end_pos 

262 for n in scope.get_test_nodes())): 

263 return scope 

264 

265 scope = scope.parent 

266 

267 

268get_cached_parent_scope = _get_parent_scope_cache(get_parent_scope) 

269 

270 

271def get_cached_code_lines(grammar, path): 

272 """ 

273 Basically access the cached code lines in parso. This is not the nicest way 

274 to do this, but we avoid splitting all the lines again. 

275 """ 

276 return get_parso_cache_node(grammar, path).lines 

277 

278 

279def get_parso_cache_node(grammar, path): 

280 """ 

281 This is of course not public. But as long as I control parso, this 

282 shouldn't be a problem. ~ Dave 

283 

284 The reason for this is mostly caching. This is obviously also a sign of a 

285 broken caching architecture. 

286 """ 

287 return parser_cache[grammar._hashed][path] 

288 

289 

290def cut_value_at_position(leaf, position): 

291 """ 

292 Cuts of the value of the leaf at position 

293 """ 

294 lines = split_lines(leaf.value, keepends=True)[:position[0] - leaf.line + 1] 

295 column = position[1] 

296 if leaf.line == position[0]: 

297 column -= leaf.column 

298 if not lines: 

299 return '' 

300 lines[-1] = lines[-1][:column] 

301 return ''.join(lines) 

302 

303 

304def expr_is_dotted(node): 

305 """ 

306 Checks if a path looks like `name` or `name.foo.bar` and not `name()`. 

307 """ 

308 if node.type == 'atom': 

309 if len(node.children) == 3 and node.children[0] == '(': 

310 return expr_is_dotted(node.children[1]) 

311 return False 

312 if node.type == 'atom_expr': 

313 children = node.children 

314 if children[0] == 'await': 

315 return False 

316 if not expr_is_dotted(children[0]): 

317 return False 

318 # Check trailers 

319 return all(c.children[0] == '.' for c in children[1:]) 

320 return node.type == 'name' 

321 

322 

323def _function_is_x_method(*method_names): 

324 def wrapper(function_node): 

325 """ 

326 This is a heuristic. It will not hold ALL the times, but it will be 

327 correct pretty much for anyone that doesn't try to beat it. 

328 staticmethod/classmethod are builtins and unless overwritten, this will 

329 be correct. 

330 """ 

331 for decorator in function_node.get_decorators(): 

332 dotted_name = decorator.children[1] 

333 if dotted_name.get_code() in method_names: 

334 return True 

335 return False 

336 return wrapper 

337 

338 

339function_is_staticmethod = _function_is_x_method('staticmethod') 

340function_is_classmethod = _function_is_x_method('classmethod') 

341function_is_property = _function_is_x_method('property', 'cached_property')