Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi/inference/docstrings.py: 18%

136 statements  

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

1""" 

2Docstrings are another source of information for functions and classes. 

3:mod:`jedi.inference.dynamic_params` tries to find all executions of functions, 

4while the docstring parsing is much easier. There are three different types of 

5docstrings that |jedi| understands: 

6 

7- `Sphinx <http://sphinx-doc.org/markup/desc.html#info-field-lists>`_ 

8- `Epydoc <http://epydoc.sourceforge.net/manual-fields.html>`_ 

9- `Numpydoc <https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt>`_ 

10 

11For example, the sphinx annotation ``:type foo: str`` clearly states that the 

12type of ``foo`` is ``str``. 

13 

14As an addition to parameter searching, this module also provides return 

15annotations. 

16""" 

17 

18import re 

19import warnings 

20 

21from parso import parse, ParserSyntaxError 

22 

23from jedi import debug 

24from jedi.inference.cache import inference_state_method_cache 

25from jedi.inference.base_value import iterator_to_value_set, ValueSet, \ 

26 NO_VALUES 

27from jedi.inference.lazy_value import LazyKnownValues 

28 

29 

30DOCSTRING_PARAM_PATTERNS = [ 

31 r'\s*:type\s+%s:\s*([^\n]+)', # Sphinx 

32 r'\s*:param\s+(\w+)\s+%s:[^\n]*', # Sphinx param with type 

33 r'\s*@type\s+%s:\s*([^\n]+)', # Epydoc 

34] 

35 

36DOCSTRING_RETURN_PATTERNS = [ 

37 re.compile(r'\s*:rtype:\s*([^\n]+)', re.M), # Sphinx 

38 re.compile(r'\s*@rtype:\s*([^\n]+)', re.M), # Epydoc 

39] 

40 

41REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`') 

42 

43 

44_numpy_doc_string_cache = None 

45 

46 

47def _get_numpy_doc_string_cls(): 

48 global _numpy_doc_string_cache 

49 if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)): 

50 raise _numpy_doc_string_cache 

51 from numpydoc.docscrape import NumpyDocString # type: ignore[import] 

52 _numpy_doc_string_cache = NumpyDocString 

53 return _numpy_doc_string_cache 

54 

55 

56def _search_param_in_numpydocstr(docstr, param_str): 

57 """Search `docstr` (in numpydoc format) for type(-s) of `param_str`.""" 

58 with warnings.catch_warnings(): 

59 warnings.simplefilter("ignore") 

60 try: 

61 # This is a non-public API. If it ever changes we should be 

62 # prepared and return gracefully. 

63 params = _get_numpy_doc_string_cls()(docstr)._parsed_data['Parameters'] 

64 except Exception: 

65 return [] 

66 for p_name, p_type, p_descr in params: 

67 if p_name == param_str: 

68 m = re.match(r'([^,]+(,[^,]+)*?)(,[ ]*optional)?$', p_type) 

69 if m: 

70 p_type = m.group(1) 

71 return list(_expand_typestr(p_type)) 

72 return [] 

73 

74 

75def _search_return_in_numpydocstr(docstr): 

76 """ 

77 Search `docstr` (in numpydoc format) for type(-s) of function returns. 

78 """ 

79 with warnings.catch_warnings(): 

80 warnings.simplefilter("ignore") 

81 try: 

82 doc = _get_numpy_doc_string_cls()(docstr) 

83 except Exception: 

84 return 

85 try: 

86 # This is a non-public API. If it ever changes we should be 

87 # prepared and return gracefully. 

88 returns = doc._parsed_data['Returns'] 

89 returns += doc._parsed_data['Yields'] 

90 except Exception: 

91 return 

92 for r_name, r_type, r_descr in returns: 

93 # Return names are optional and if so the type is in the name 

94 if not r_type: 

95 r_type = r_name 

96 yield from _expand_typestr(r_type) 

97 

98 

99def _expand_typestr(type_str): 

100 """ 

101 Attempts to interpret the possible types in `type_str` 

102 """ 

103 # Check if alternative types are specified with 'or' 

104 if re.search(r'\bor\b', type_str): 

105 for t in type_str.split('or'): 

106 yield t.split('of')[0].strip() 

107 # Check if like "list of `type`" and set type to list 

108 elif re.search(r'\bof\b', type_str): 

109 yield type_str.split('of')[0] 

110 # Check if type has is a set of valid literal values eg: {'C', 'F', 'A'} 

111 elif type_str.startswith('{'): 

112 node = parse(type_str, version='3.7').children[0] 

113 if node.type == 'atom': 

114 for leaf in getattr(node.children[1], "children", []): 

115 if leaf.type == 'number': 

116 if '.' in leaf.value: 

117 yield 'float' 

118 else: 

119 yield 'int' 

120 elif leaf.type == 'string': 

121 if 'b' in leaf.string_prefix.lower(): 

122 yield 'bytes' 

123 else: 

124 yield 'str' 

125 # Ignore everything else. 

126 

127 # Otherwise just work with what we have. 

128 else: 

129 yield type_str 

130 

131 

132def _search_param_in_docstr(docstr, param_str): 

133 """ 

134 Search `docstr` for type(-s) of `param_str`. 

135 

136 >>> _search_param_in_docstr(':type param: int', 'param') 

137 ['int'] 

138 >>> _search_param_in_docstr('@type param: int', 'param') 

139 ['int'] 

140 >>> _search_param_in_docstr( 

141 ... ':type param: :class:`threading.Thread`', 'param') 

142 ['threading.Thread'] 

143 >>> bool(_search_param_in_docstr('no document', 'param')) 

144 False 

145 >>> _search_param_in_docstr(':param int param: some description', 'param') 

146 ['int'] 

147 

148 """ 

149 # look at #40 to see definitions of those params 

150 patterns = [re.compile(p % re.escape(param_str)) 

151 for p in DOCSTRING_PARAM_PATTERNS] 

152 for pattern in patterns: 

153 match = pattern.search(docstr) 

154 if match: 

155 return [_strip_rst_role(match.group(1))] 

156 

157 return _search_param_in_numpydocstr(docstr, param_str) 

158 

159 

160def _strip_rst_role(type_str): 

161 """ 

162 Strip off the part looks like a ReST role in `type_str`. 

163 

164 >>> _strip_rst_role(':class:`ClassName`') # strip off :class: 

165 'ClassName' 

166 >>> _strip_rst_role(':py:obj:`module.Object`') # works with domain 

167 'module.Object' 

168 >>> _strip_rst_role('ClassName') # do nothing when not ReST role 

169 'ClassName' 

170 

171 See also: 

172 http://sphinx-doc.org/domains.html#cross-referencing-python-objects 

173 

174 """ 

175 match = REST_ROLE_PATTERN.match(type_str) 

176 if match: 

177 return match.group(1) 

178 else: 

179 return type_str 

180 

181 

182def _infer_for_statement_string(module_context, string): 

183 if string is None: 

184 return [] 

185 

186 potential_imports = re.findall(r'((?:\w+\.)*\w+)\.', string) 

187 # Try to import module part in dotted name. 

188 # (e.g., 'threading' in 'threading.Thread'). 

189 imports = "\n".join(f"import {p}" for p in potential_imports) 

190 string = f'{imports}\n{string}' 

191 

192 debug.dbg('Parse docstring code %s', string, color='BLUE') 

193 grammar = module_context.inference_state.grammar 

194 try: 

195 module = grammar.parse(string, error_recovery=False) 

196 except ParserSyntaxError: 

197 return [] 

198 try: 

199 # It's not the last item, because that's an end marker. 

200 stmt = module.children[-2] 

201 except (AttributeError, IndexError): 

202 return [] 

203 

204 if stmt.type not in ('name', 'atom', 'atom_expr'): 

205 return [] 

206 

207 # Here we basically use a fake module that also uses the filters in 

208 # the actual module. 

209 from jedi.inference.docstring_utils import DocstringModule 

210 m = DocstringModule( 

211 in_module_context=module_context, 

212 inference_state=module_context.inference_state, 

213 module_node=module, 

214 code_lines=[], 

215 ) 

216 return list(_execute_types_in_stmt(m.as_context(), stmt)) 

217 

218 

219def _execute_types_in_stmt(module_context, stmt): 

220 """ 

221 Executing all types or general elements that we find in a statement. This 

222 doesn't include tuple, list and dict literals, because the stuff they 

223 contain is executed. (Used as type information). 

224 """ 

225 definitions = module_context.infer_node(stmt) 

226 return ValueSet.from_sets( 

227 _execute_array_values(module_context.inference_state, d) 

228 for d in definitions 

229 ) 

230 

231 

232def _execute_array_values(inference_state, array): 

233 """ 

234 Tuples indicate that there's not just one return value, but the listed 

235 ones. `(str, int)` means that it returns a tuple with both types. 

236 """ 

237 from jedi.inference.value.iterable import SequenceLiteralValue, FakeTuple, FakeList 

238 if isinstance(array, SequenceLiteralValue) and array.array_type in ('tuple', 'list'): 

239 values = [] 

240 for lazy_value in array.py__iter__(): 

241 objects = ValueSet.from_sets( 

242 _execute_array_values(inference_state, typ) 

243 for typ in lazy_value.infer() 

244 ) 

245 values.append(LazyKnownValues(objects)) 

246 cls = FakeTuple if array.array_type == 'tuple' else FakeList 

247 return {cls(inference_state, values)} 

248 else: 

249 return array.execute_annotation() 

250 

251 

252@inference_state_method_cache() 

253def infer_param(function_value, param): 

254 def infer_docstring(docstring): 

255 return ValueSet( 

256 p 

257 for param_str in _search_param_in_docstr(docstring, param.name.value) 

258 for p in _infer_for_statement_string(module_context, param_str) 

259 ) 

260 module_context = function_value.get_root_context() 

261 func = param.get_parent_function() 

262 if func.type == 'lambdef': 

263 return NO_VALUES 

264 

265 types = infer_docstring(function_value.py__doc__()) 

266 if function_value.is_bound_method() \ 

267 and function_value.py__name__() == '__init__': 

268 types |= infer_docstring(function_value.class_context.py__doc__()) 

269 

270 debug.dbg('Found param types for docstring: %s', types, color='BLUE') 

271 return types 

272 

273 

274@inference_state_method_cache() 

275@iterator_to_value_set 

276def infer_return_types(function_value): 

277 def search_return_in_docstr(code): 

278 for p in DOCSTRING_RETURN_PATTERNS: 

279 match = p.search(code) 

280 if match: 

281 yield _strip_rst_role(match.group(1)) 

282 # Check for numpy style return hint 

283 yield from _search_return_in_numpydocstr(code) 

284 

285 for type_str in search_return_in_docstr(function_value.py__doc__()): 

286 yield from _infer_for_statement_string(function_value.get_root_context(), type_str)