Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_check_docs_utils.py 21KB

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