_check_docs_utils.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. # Copyright (c) 2016-2019, 2021 Ashley Whetter <ashley@awhetter.co.uk>
  2. # Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com>
  3. # Copyright (c) 2016 Yuri Bochkarev <baltazar.bz@gmail.com>
  4. # Copyright (c) 2016 Glenn Matthews <glenn@e-dad.net>
  5. # Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
  6. # Copyright (c) 2017, 2020 hippo91 <guillaume.peillex@gmail.com>
  7. # Copyright (c) 2017 Mitar <mitar.github@tnode.com>
  8. # Copyright (c) 2018, 2020 Anthony Sottile <asottile@umich.edu>
  9. # Copyright (c) 2018 Jim Robertson <jrobertson98atx@gmail.com>
  10. # Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
  11. # Copyright (c) 2018 Mitchell T.H. Young <mitchelly@gmail.com>
  12. # Copyright (c) 2018 Adrian Chirieac <chirieacam@gmail.com>
  13. # Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
  14. # Copyright (c) 2019 Danny Hermes <daniel.j.hermes@gmail.com>
  15. # Copyright (c) 2019 Zeb Nicholls <zebedee.nicholls@climate-energy-college.org>
  16. # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  17. # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  18. # Copyright (c) 2021 allanc65 <95424144+allanc65@users.noreply.github.com>
  19. # Copyright (c) 2021 Konstantina Saketou <56515303+ksaketou@users.noreply.github.com>
  20. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  21. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  22. # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
  23. """Utility methods for docstring checking."""
  24. import re
  25. from typing import List, Set, Tuple
  26. import astroid
  27. from astroid import nodes
  28. from pylint.checkers import utils
  29. def space_indentation(s):
  30. """The number of leading spaces in a string
  31. :param str s: input string
  32. :rtype: int
  33. :return: number of leading spaces
  34. """
  35. return len(s) - len(s.lstrip(" "))
  36. def get_setters_property_name(node):
  37. """Get the name of the property that the given node is a setter for.
  38. :param node: The node to get the property name for.
  39. :type node: str
  40. :rtype: str or None
  41. :returns: The name of the property that the node is a setter for,
  42. or None if one could not be found.
  43. """
  44. decorators = node.decorators.nodes if node.decorators else []
  45. for decorator in decorators:
  46. if (
  47. isinstance(decorator, nodes.Attribute)
  48. and decorator.attrname == "setter"
  49. and isinstance(decorator.expr, nodes.Name)
  50. ):
  51. return decorator.expr.name
  52. return None
  53. def get_setters_property(node):
  54. """Get the property node for the given setter node.
  55. :param node: The node to get the property for.
  56. :type node: nodes.FunctionDef
  57. :rtype: nodes.FunctionDef or None
  58. :returns: The node relating to the property of the given setter node,
  59. or None if one could not be found.
  60. """
  61. property_ = None
  62. property_name = get_setters_property_name(node)
  63. class_node = utils.node_frame_class(node)
  64. if property_name and class_node:
  65. class_attrs = class_node.getattr(node.name)
  66. for attr in class_attrs:
  67. if utils.decorated_with_property(attr):
  68. property_ = attr
  69. break
  70. return property_
  71. def returns_something(return_node):
  72. """Check if a return node returns a value other than None.
  73. :param return_node: The return node to check.
  74. :type return_node: astroid.Return
  75. :rtype: bool
  76. :return: True if the return node returns a value other than None,
  77. False otherwise.
  78. """
  79. returns = return_node.value
  80. if returns is None:
  81. return False
  82. return not (isinstance(returns, nodes.Const) and returns.value is None)
  83. def _get_raise_target(node):
  84. if isinstance(node.exc, nodes.Call):
  85. func = node.exc.func
  86. if isinstance(func, (nodes.Name, nodes.Attribute)):
  87. return utils.safe_infer(func)
  88. return None
  89. def _split_multiple_exc_types(target: str) -> List[str]:
  90. delimiters = r"(\s*,(?:\s*or\s)?\s*|\s+or\s+)"
  91. return re.split(delimiters, target)
  92. def possible_exc_types(node):
  93. """
  94. Gets all of the possible raised exception types for the given raise node.
  95. .. note::
  96. Caught exception types are ignored.
  97. :param node: The raise node to find exception types for.
  98. :type node: nodes.NodeNG
  99. :returns: A list of exception types possibly raised by :param:`node`.
  100. :rtype: set(str)
  101. """
  102. excs = []
  103. if isinstance(node.exc, nodes.Name):
  104. inferred = utils.safe_infer(node.exc)
  105. if inferred:
  106. excs = [inferred.name]
  107. elif node.exc is None:
  108. handler = node.parent
  109. while handler and not isinstance(handler, nodes.ExceptHandler):
  110. handler = handler.parent
  111. if handler and handler.type:
  112. inferred_excs = astroid.unpack_infer(handler.type)
  113. excs = (exc.name for exc in inferred_excs if exc is not astroid.Uninferable)
  114. else:
  115. target = _get_raise_target(node)
  116. if isinstance(target, nodes.ClassDef):
  117. excs = [target.name]
  118. elif isinstance(target, nodes.FunctionDef):
  119. for ret in target.nodes_of_class(nodes.Return):
  120. if ret.frame() != target:
  121. # return from inner function - ignore it
  122. continue
  123. val = utils.safe_infer(ret.value)
  124. if (
  125. val
  126. and isinstance(val, (astroid.Instance, nodes.ClassDef))
  127. and utils.inherit_from_std_ex(val)
  128. ):
  129. excs.append(val.name)
  130. try:
  131. return {exc for exc in excs if not utils.node_ignores_exception(node, exc)}
  132. except astroid.InferenceError:
  133. return set()
  134. def docstringify(docstring, default_type="default"):
  135. for docstring_type in (
  136. SphinxDocstring,
  137. EpytextDocstring,
  138. GoogleDocstring,
  139. NumpyDocstring,
  140. ):
  141. instance = docstring_type(docstring)
  142. if instance.is_valid():
  143. return instance
  144. docstring_type = DOCSTRING_TYPES.get(default_type, Docstring)
  145. return docstring_type(docstring)
  146. class Docstring:
  147. re_for_parameters_see = re.compile(
  148. r"""
  149. For\s+the\s+(other)?\s*parameters\s*,\s+see
  150. """,
  151. re.X | re.S,
  152. )
  153. supports_yields: bool = False
  154. """True if the docstring supports a "yield" section.
  155. False if the docstring uses the returns section to document generators.
  156. """
  157. # These methods are designed to be overridden
  158. # pylint: disable=no-self-use
  159. def __init__(self, doc):
  160. doc = doc or ""
  161. self.doc = doc.expandtabs()
  162. def __repr__(self) -> str:
  163. return f"<{self.__class__.__name__}:'''{self.doc}'''>"
  164. def is_valid(self):
  165. return False
  166. def exceptions(self):
  167. return set()
  168. def has_params(self):
  169. return False
  170. def has_returns(self):
  171. return False
  172. def has_rtype(self):
  173. return False
  174. def has_property_returns(self):
  175. return False
  176. def has_property_type(self):
  177. return False
  178. def has_yields(self):
  179. return False
  180. def has_yields_type(self):
  181. return False
  182. def match_param_docs(self):
  183. return set(), set()
  184. def params_documented_elsewhere(self):
  185. return self.re_for_parameters_see.search(self.doc) is not None
  186. class SphinxDocstring(Docstring):
  187. re_type = r"""
  188. [~!.]? # Optional link style prefix
  189. \w(?:\w|\.[^\.])* # Valid python name
  190. """
  191. re_simple_container_type = fr"""
  192. {re_type} # a container type
  193. [\(\[] [^\n\s]+ [\)\]] # with the contents of the container
  194. """
  195. re_multiple_simple_type = r"""
  196. (?:{container_type}|{type})
  197. (?:(?:\s+(?:of|or)\s+|\s*,\s*)(?:{container_type}|{type}))*
  198. """.format(
  199. type=re_type, container_type=re_simple_container_type
  200. )
  201. re_xref = fr"""
  202. (?::\w+:)? # optional tag
  203. `{re_type}` # what to reference
  204. """
  205. re_param_raw = fr"""
  206. : # initial colon
  207. (?: # Sphinx keywords
  208. param|parameter|
  209. arg|argument|
  210. key|keyword
  211. )
  212. \s+ # whitespace
  213. (?: # optional type declaration
  214. ({re_type}|{re_simple_container_type})
  215. \s+
  216. )?
  217. ((\\\*{{1,2}}\w+)|(\w+)) # Parameter name with potential asterisks
  218. \s* # whitespace
  219. : # final colon
  220. """
  221. re_param_in_docstring = re.compile(re_param_raw, re.X | re.S)
  222. re_type_raw = fr"""
  223. :type # Sphinx keyword
  224. \s+ # whitespace
  225. ({re_multiple_simple_type}) # Parameter name
  226. \s* # whitespace
  227. : # final colon
  228. """
  229. re_type_in_docstring = re.compile(re_type_raw, re.X | re.S)
  230. re_property_type_raw = fr"""
  231. :type: # Sphinx keyword
  232. \s+ # whitespace
  233. {re_multiple_simple_type} # type declaration
  234. """
  235. re_property_type_in_docstring = re.compile(re_property_type_raw, re.X | re.S)
  236. re_raise_raw = fr"""
  237. : # initial colon
  238. (?: # Sphinx keyword
  239. raises?|
  240. except|exception
  241. )
  242. \s+ # whitespace
  243. ({re_multiple_simple_type}) # exception type
  244. \s* # whitespace
  245. : # final colon
  246. """
  247. re_raise_in_docstring = re.compile(re_raise_raw, re.X | re.S)
  248. re_rtype_in_docstring = re.compile(r":rtype:")
  249. re_returns_in_docstring = re.compile(r":returns?:")
  250. supports_yields = False
  251. def is_valid(self):
  252. return bool(
  253. self.re_param_in_docstring.search(self.doc)
  254. or self.re_raise_in_docstring.search(self.doc)
  255. or self.re_rtype_in_docstring.search(self.doc)
  256. or self.re_returns_in_docstring.search(self.doc)
  257. or self.re_property_type_in_docstring.search(self.doc)
  258. )
  259. def exceptions(self):
  260. types = set()
  261. for match in re.finditer(self.re_raise_in_docstring, self.doc):
  262. raise_type = match.group(1)
  263. types.update(_split_multiple_exc_types(raise_type))
  264. return types
  265. def has_params(self):
  266. if not self.doc:
  267. return False
  268. return self.re_param_in_docstring.search(self.doc) is not None
  269. def has_returns(self):
  270. if not self.doc:
  271. return False
  272. return bool(self.re_returns_in_docstring.search(self.doc))
  273. def has_rtype(self):
  274. if not self.doc:
  275. return False
  276. return bool(self.re_rtype_in_docstring.search(self.doc))
  277. def has_property_returns(self):
  278. if not self.doc:
  279. return False
  280. # The summary line is the return doc,
  281. # so the first line must not be a known directive.
  282. return not self.doc.lstrip().startswith(":")
  283. def has_property_type(self):
  284. if not self.doc:
  285. return False
  286. return bool(self.re_property_type_in_docstring.search(self.doc))
  287. def match_param_docs(self):
  288. params_with_doc = set()
  289. params_with_type = set()
  290. for match in re.finditer(self.re_param_in_docstring, self.doc):
  291. name = match.group(2)
  292. # Remove escape characters necessary for asterisks
  293. name = name.replace("\\", "")
  294. params_with_doc.add(name)
  295. param_type = match.group(1)
  296. if param_type is not None:
  297. params_with_type.add(name)
  298. params_with_type.update(re.findall(self.re_type_in_docstring, self.doc))
  299. return params_with_doc, params_with_type
  300. class EpytextDocstring(SphinxDocstring):
  301. """
  302. Epytext is similar to Sphinx. See the docs:
  303. http://epydoc.sourceforge.net/epytext.html
  304. http://epydoc.sourceforge.net/fields.html#fields
  305. It's used in PyCharm:
  306. https://www.jetbrains.com/help/pycharm/2016.1/creating-documentation-comments.html#d848203e314
  307. https://www.jetbrains.com/help/pycharm/2016.1/using-docstrings-to-specify-types.html
  308. """
  309. re_param_in_docstring = re.compile(
  310. SphinxDocstring.re_param_raw.replace(":", "@", 1), re.X | re.S
  311. )
  312. re_type_in_docstring = re.compile(
  313. SphinxDocstring.re_type_raw.replace(":", "@", 1), re.X | re.S
  314. )
  315. re_property_type_in_docstring = re.compile(
  316. SphinxDocstring.re_property_type_raw.replace(":", "@", 1), re.X | re.S
  317. )
  318. re_raise_in_docstring = re.compile(
  319. SphinxDocstring.re_raise_raw.replace(":", "@", 1), re.X | re.S
  320. )
  321. re_rtype_in_docstring = re.compile(
  322. r"""
  323. @ # initial "at" symbol
  324. (?: # Epytext keyword
  325. rtype|returntype
  326. )
  327. : # final colon
  328. """,
  329. re.X | re.S,
  330. )
  331. re_returns_in_docstring = re.compile(r"@returns?:")
  332. def has_property_returns(self):
  333. if not self.doc:
  334. return False
  335. # If this is a property docstring, the summary is the return doc.
  336. if self.has_property_type():
  337. # The summary line is the return doc,
  338. # so the first line must not be a known directive.
  339. return not self.doc.lstrip().startswith("@")
  340. return False
  341. class GoogleDocstring(Docstring):
  342. re_type = SphinxDocstring.re_type
  343. re_xref = SphinxDocstring.re_xref
  344. re_container_type = fr"""
  345. (?:{re_type}|{re_xref}) # a container type
  346. [\(\[] [^\n]+ [\)\]] # with the contents of the container
  347. """
  348. re_multiple_type = r"""
  349. (?:{container_type}|{type}|{xref})
  350. (?:(?:\s+(?:of|or)\s+|\s*,\s*)(?:{container_type}|{type}|{xref}))*
  351. """.format(
  352. type=re_type, xref=re_xref, container_type=re_container_type
  353. )
  354. _re_section_template = r"""
  355. ^([ ]*) {0} \s*: \s*$ # Google parameter header
  356. ( .* ) # section
  357. """
  358. re_param_section = re.compile(
  359. _re_section_template.format(r"(?:Args|Arguments|Parameters)"),
  360. re.X | re.S | re.M,
  361. )
  362. re_keyword_param_section = re.compile(
  363. _re_section_template.format(r"Keyword\s(?:Args|Arguments|Parameters)"),
  364. re.X | re.S | re.M,
  365. )
  366. re_param_line = re.compile(
  367. fr"""
  368. \s* (\*{{0,2}}\w+) # identifier potentially with asterisks
  369. \s* ( [(]
  370. {re_multiple_type}
  371. (?:,\s+optional)?
  372. [)] )? \s* : # optional type declaration
  373. \s* (.*) # beginning of optional description
  374. """,
  375. re.X | re.S | re.M,
  376. )
  377. re_raise_section = re.compile(
  378. _re_section_template.format(r"Raises"), re.X | re.S | re.M
  379. )
  380. re_raise_line = re.compile(
  381. fr"""
  382. \s* ({re_multiple_type}) \s* : # identifier
  383. \s* (.*) # beginning of optional description
  384. """,
  385. re.X | re.S | re.M,
  386. )
  387. re_returns_section = re.compile(
  388. _re_section_template.format(r"Returns?"), re.X | re.S | re.M
  389. )
  390. re_returns_line = re.compile(
  391. fr"""
  392. \s* ({re_multiple_type}:)? # identifier
  393. \s* (.*) # beginning of description
  394. """,
  395. re.X | re.S | re.M,
  396. )
  397. re_property_returns_line = re.compile(
  398. fr"""
  399. ^{re_multiple_type}: # indentifier
  400. \s* (.*) # Summary line / description
  401. """,
  402. re.X | re.S | re.M,
  403. )
  404. re_yields_section = re.compile(
  405. _re_section_template.format(r"Yields?"), re.X | re.S | re.M
  406. )
  407. re_yields_line = re_returns_line
  408. supports_yields = True
  409. def is_valid(self):
  410. return bool(
  411. self.re_param_section.search(self.doc)
  412. or self.re_raise_section.search(self.doc)
  413. or self.re_returns_section.search(self.doc)
  414. or self.re_yields_section.search(self.doc)
  415. or self.re_property_returns_line.search(self._first_line())
  416. )
  417. def has_params(self):
  418. if not self.doc:
  419. return False
  420. return self.re_param_section.search(self.doc) is not None
  421. def has_returns(self):
  422. if not self.doc:
  423. return False
  424. entries = self._parse_section(self.re_returns_section)
  425. for entry in entries:
  426. match = self.re_returns_line.match(entry)
  427. if not match:
  428. continue
  429. return_desc = match.group(2)
  430. if return_desc:
  431. return True
  432. return False
  433. def has_rtype(self):
  434. if not self.doc:
  435. return False
  436. entries = self._parse_section(self.re_returns_section)
  437. for entry in entries:
  438. match = self.re_returns_line.match(entry)
  439. if not match:
  440. continue
  441. return_type = match.group(1)
  442. if return_type:
  443. return True
  444. return False
  445. def has_property_returns(self):
  446. # The summary line is the return doc,
  447. # so the first line must not be a known directive.
  448. first_line = self._first_line()
  449. return not bool(
  450. self.re_param_section.search(first_line)
  451. or self.re_raise_section.search(first_line)
  452. or self.re_returns_section.search(first_line)
  453. or self.re_yields_section.search(first_line)
  454. )
  455. def has_property_type(self):
  456. if not self.doc:
  457. return False
  458. return bool(self.re_property_returns_line.match(self._first_line()))
  459. def has_yields(self):
  460. if not self.doc:
  461. return False
  462. entries = self._parse_section(self.re_yields_section)
  463. for entry in entries:
  464. match = self.re_yields_line.match(entry)
  465. if not match:
  466. continue
  467. yield_desc = match.group(2)
  468. if yield_desc:
  469. return True
  470. return False
  471. def has_yields_type(self):
  472. if not self.doc:
  473. return False
  474. entries = self._parse_section(self.re_yields_section)
  475. for entry in entries:
  476. match = self.re_yields_line.match(entry)
  477. if not match:
  478. continue
  479. yield_type = match.group(1)
  480. if yield_type:
  481. return True
  482. return False
  483. def exceptions(self):
  484. types = set()
  485. entries = self._parse_section(self.re_raise_section)
  486. for entry in entries:
  487. match = self.re_raise_line.match(entry)
  488. if not match:
  489. continue
  490. exc_type = match.group(1)
  491. exc_desc = match.group(2)
  492. if exc_desc:
  493. types.update(_split_multiple_exc_types(exc_type))
  494. return types
  495. def match_param_docs(self):
  496. params_with_doc = set()
  497. params_with_type = set()
  498. entries = self._parse_section(self.re_param_section)
  499. entries.extend(self._parse_section(self.re_keyword_param_section))
  500. for entry in entries:
  501. match = self.re_param_line.match(entry)
  502. if not match:
  503. continue
  504. param_name = match.group(1)
  505. param_type = match.group(2)
  506. param_desc = match.group(3)
  507. if param_type:
  508. params_with_type.add(param_name)
  509. if param_desc:
  510. params_with_doc.add(param_name)
  511. return params_with_doc, params_with_type
  512. def _first_line(self):
  513. return self.doc.lstrip().split("\n", 1)[0]
  514. @staticmethod
  515. def min_section_indent(section_match):
  516. return len(section_match.group(1)) + 1
  517. @staticmethod
  518. def _is_section_header(_):
  519. # Google parsing does not need to detect section headers,
  520. # because it works off of indentation level only
  521. return False
  522. def _parse_section(self, section_re):
  523. section_match = section_re.search(self.doc)
  524. if section_match is None:
  525. return []
  526. min_indentation = self.min_section_indent(section_match)
  527. entries = []
  528. entry = []
  529. is_first = True
  530. for line in section_match.group(2).splitlines():
  531. if not line.strip():
  532. continue
  533. indentation = space_indentation(line)
  534. if indentation < min_indentation:
  535. break
  536. # The first line after the header defines the minimum
  537. # indentation.
  538. if is_first:
  539. min_indentation = indentation
  540. is_first = False
  541. if indentation == min_indentation:
  542. if self._is_section_header(line):
  543. break
  544. # Lines with minimum indentation must contain the beginning
  545. # of a new parameter documentation.
  546. if entry:
  547. entries.append("\n".join(entry))
  548. entry = []
  549. entry.append(line)
  550. if entry:
  551. entries.append("\n".join(entry))
  552. return entries
  553. class NumpyDocstring(GoogleDocstring):
  554. _re_section_template = r"""
  555. ^([ ]*) {0} \s*?$ # Numpy parameters header
  556. \s* [-=]+ \s*?$ # underline
  557. ( .* ) # section
  558. """
  559. re_param_section = re.compile(
  560. _re_section_template.format(r"(?:Args|Arguments|Parameters)"),
  561. re.X | re.S | re.M,
  562. )
  563. re_param_line = re.compile(
  564. fr"""
  565. \s* (\*{{0,2}}\w+)(\s?(:|\n)) # identifier with potential asterisks
  566. \s* (?:({GoogleDocstring.re_multiple_type})(?:,\s+optional)?\n)? # optional type declaration
  567. \s* (.*) # optional description
  568. """,
  569. re.X | re.S,
  570. )
  571. re_raise_section = re.compile(
  572. _re_section_template.format(r"Raises"), re.X | re.S | re.M
  573. )
  574. re_raise_line = re.compile(
  575. fr"""
  576. \s* ({GoogleDocstring.re_type})$ # type declaration
  577. \s* (.*) # optional description
  578. """,
  579. re.X | re.S | re.M,
  580. )
  581. re_returns_section = re.compile(
  582. _re_section_template.format(r"Returns?"), re.X | re.S | re.M
  583. )
  584. re_returns_line = re.compile(
  585. fr"""
  586. \s* (?:\w+\s+:\s+)? # optional name
  587. ({GoogleDocstring.re_multiple_type})$ # type declaration
  588. \s* (.*) # optional description
  589. """,
  590. re.X | re.S | re.M,
  591. )
  592. re_yields_section = re.compile(
  593. _re_section_template.format(r"Yields?"), re.X | re.S | re.M
  594. )
  595. re_yields_line = re_returns_line
  596. supports_yields = True
  597. def match_param_docs(self) -> Tuple[Set[str], Set[str]]:
  598. """Matches parameter documentation section to parameter documentation rules"""
  599. params_with_doc = set()
  600. params_with_type = set()
  601. entries = self._parse_section(self.re_param_section)
  602. entries.extend(self._parse_section(self.re_keyword_param_section))
  603. for entry in entries:
  604. match = self.re_param_line.match(entry)
  605. if not match:
  606. continue
  607. # check if parameter has description only
  608. re_only_desc = re.match(r"\s* (\*{0,2}\w+)\s*:?\n", entry)
  609. if re_only_desc:
  610. param_name = match.group(1)
  611. param_desc = match.group(2)
  612. param_type = None
  613. else:
  614. param_name = match.group(1)
  615. param_type = match.group(3)
  616. param_desc = match.group(4)
  617. if param_type:
  618. params_with_type.add(param_name)
  619. if param_desc:
  620. params_with_doc.add(param_name)
  621. return params_with_doc, params_with_type
  622. @staticmethod
  623. def min_section_indent(section_match):
  624. return len(section_match.group(1))
  625. @staticmethod
  626. def _is_section_header(line):
  627. return bool(re.match(r"\s*-+$", line))
  628. DOCSTRING_TYPES = {
  629. "sphinx": SphinxDocstring,
  630. "epytext": EpytextDocstring,
  631. "google": GoogleDocstring,
  632. "numpy": NumpyDocstring,
  633. "default": Docstring,
  634. }
  635. """A map of the name of the docstring type to its class.
  636. :type: dict(str, type)
  637. """