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.

docparams.py 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2014-2015 Bruno Daniel <bruno.daniel@blue-yonder.com>
  3. # Copyright (c) 2015-2017 Claudiu Popa <pcmanticore@gmail.com>
  4. # Copyright (c) 2016-2018 Ashley Whetter <ashley@awhetter.co.uk>
  5. # Copyright (c) 2016 Glenn Matthews <glenn@e-dad.net>
  6. # Copyright (c) 2016 Glenn Matthews <glmatthe@cisco.com>
  7. # Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
  8. # Copyright (c) 2017 Ville Skyttä <ville.skytta@iki.fi>
  9. # Copyright (c) 2017 John Paraskevopoulos <io.paraskev@gmail.com>
  10. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  11. # For details: https://github.com/PyCQA/pylint/blob/master/COPYING
  12. """Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings
  13. """
  14. from __future__ import print_function, division, absolute_import
  15. import astroid
  16. from pylint.interfaces import IAstroidChecker
  17. from pylint.checkers import BaseChecker
  18. from pylint.checkers import utils as checker_utils
  19. import pylint.extensions._check_docs_utils as utils
  20. class DocstringParameterChecker(BaseChecker):
  21. """Checker for Sphinx, Google, or Numpy style docstrings
  22. * Check that all function, method and constructor parameters are mentioned
  23. in the params and types part of the docstring. Constructor parameters
  24. can be documented in either the class docstring or ``__init__`` docstring,
  25. but not both.
  26. * Check that there are no naming inconsistencies between the signature and
  27. the documentation, i.e. also report documented parameters that are missing
  28. in the signature. This is important to find cases where parameters are
  29. renamed only in the code, not in the documentation.
  30. * Check that all explicitly raised exceptions in a function are documented
  31. in the function docstring. Caught exceptions are ignored.
  32. Activate this checker by adding the line::
  33. load-plugins=pylint.extensions.docparams
  34. to the ``MASTER`` section of your ``.pylintrc``.
  35. :param linter: linter object
  36. :type linter: :class:`pylint.lint.PyLinter`
  37. """
  38. __implements__ = IAstroidChecker
  39. name = 'parameter_documentation'
  40. msgs = {
  41. 'W9005': ('"%s" has constructor parameters documented in class and __init__',
  42. 'multiple-constructor-doc',
  43. 'Please remove parameter declarations in the class or constructor.'),
  44. 'W9006': ('"%s" not documented as being raised',
  45. 'missing-raises-doc',
  46. 'Please document exceptions for all raised exception types.'),
  47. 'W9008': ('Redundant returns documentation',
  48. 'redundant-returns-doc',
  49. 'Please remove the return/rtype documentation from this method.'),
  50. 'W9010': ('Redundant yields documentation',
  51. 'redundant-yields-doc',
  52. 'Please remove the yields documentation from this method.'),
  53. 'W9011': ('Missing return documentation',
  54. 'missing-return-doc',
  55. 'Please add documentation about what this method returns.',
  56. {'old_names': [('W9007', 'missing-returns-doc')]}),
  57. 'W9012': ('Missing return type documentation',
  58. 'missing-return-type-doc',
  59. 'Please document the type returned by this method.',
  60. # we can't use the same old_name for two different warnings
  61. # {'old_names': [('W9007', 'missing-returns-doc')]},
  62. ),
  63. 'W9013': ('Missing yield documentation',
  64. 'missing-yield-doc',
  65. 'Please add documentation about what this generator yields.',
  66. {'old_names': [('W9009', 'missing-yields-doc')]}),
  67. 'W9014': ('Missing yield type documentation',
  68. 'missing-yield-type-doc',
  69. 'Please document the type yielded by this method.',
  70. # we can't use the same old_name for two different warnings
  71. # {'old_names': [('W9009', 'missing-yields-doc')]},
  72. ),
  73. 'W9015': ('"%s" missing in parameter documentation',
  74. 'missing-param-doc',
  75. 'Please add parameter declarations for all parameters.',
  76. {'old_names': [('W9003', 'missing-param-doc')]}),
  77. 'W9016': ('"%s" missing in parameter type documentation',
  78. 'missing-type-doc',
  79. 'Please add parameter type declarations for all parameters.',
  80. {'old_names': [('W9004', 'missing-type-doc')]}),
  81. 'W9017': ('"%s" differing in parameter documentation',
  82. 'differing-param-doc',
  83. 'Please check parameter names in declarations.',
  84. ),
  85. 'W9018': ('"%s" differing in parameter type documentation',
  86. 'differing-type-doc',
  87. 'Please check parameter names in type declarations.',
  88. ),
  89. }
  90. options = (('accept-no-param-doc',
  91. {'default': True, 'type' : 'yn', 'metavar' : '<y or n>',
  92. 'help': 'Whether to accept totally missing parameter '
  93. 'documentation in the docstring of a function that has '
  94. 'parameters.'
  95. }),
  96. ('accept-no-raise-doc',
  97. {'default': True, 'type' : 'yn', 'metavar' : '<y or n>',
  98. 'help': 'Whether to accept totally missing raises '
  99. 'documentation in the docstring of a function that '
  100. 'raises an exception.'
  101. }),
  102. ('accept-no-return-doc',
  103. {'default': True, 'type' : 'yn', 'metavar' : '<y or n>',
  104. 'help': 'Whether to accept totally missing return '
  105. 'documentation in the docstring of a function that '
  106. 'returns a statement.'
  107. }),
  108. ('accept-no-yields-doc',
  109. {'default': True, 'type' : 'yn', 'metavar': '<y or n>',
  110. 'help': 'Whether to accept totally missing yields '
  111. 'documentation in the docstring of a generator.'
  112. }),
  113. )
  114. priority = -2
  115. constructor_names = {'__init__', '__new__'}
  116. not_needed_param_in_docstring = {'self', 'cls'}
  117. def visit_functiondef(self, node):
  118. """Called for function and method definitions (def).
  119. :param node: Node for a function or method definition in the AST
  120. :type node: :class:`astroid.scoped_nodes.Function`
  121. """
  122. node_doc = utils.docstringify(node.doc)
  123. self.check_functiondef_params(node, node_doc)
  124. self.check_functiondef_returns(node, node_doc)
  125. self.check_functiondef_yields(node, node_doc)
  126. def check_functiondef_params(self, node, node_doc):
  127. node_allow_no_param = None
  128. if node.name in self.constructor_names:
  129. class_node = checker_utils.node_frame_class(node)
  130. if class_node is not None:
  131. class_doc = utils.docstringify(class_node.doc)
  132. self.check_single_constructor_params(class_doc, node_doc, class_node)
  133. # __init__ or class docstrings can have no parameters documented
  134. # as long as the other documents them.
  135. node_allow_no_param = (
  136. class_doc.has_params() or
  137. class_doc.params_documented_elsewhere() or
  138. None
  139. )
  140. class_allow_no_param = (
  141. node_doc.has_params() or
  142. node_doc.params_documented_elsewhere() or
  143. None
  144. )
  145. self.check_arguments_in_docstring(
  146. class_doc, node.args, class_node, class_allow_no_param)
  147. self.check_arguments_in_docstring(
  148. node_doc, node.args, node, node_allow_no_param)
  149. def check_functiondef_returns(self, node, node_doc):
  150. if ((not node_doc.supports_yields and node.is_generator())
  151. or node.is_abstract()):
  152. return
  153. return_nodes = node.nodes_of_class(astroid.Return)
  154. if ((node_doc.has_returns() or node_doc.has_rtype()) and
  155. not any(utils.returns_something(ret_node) for ret_node in return_nodes)):
  156. self.add_message(
  157. 'redundant-returns-doc',
  158. node=node)
  159. def check_functiondef_yields(self, node, node_doc):
  160. if not node_doc.supports_yields or node.is_abstract():
  161. return
  162. if ((node_doc.has_yields() or node_doc.has_yields_type()) and
  163. not node.is_generator()):
  164. self.add_message(
  165. 'redundant-yields-doc',
  166. node=node)
  167. def visit_raise(self, node):
  168. func_node = node.frame()
  169. if not isinstance(func_node, astroid.FunctionDef):
  170. return
  171. expected_excs = utils.possible_exc_types(node)
  172. if not expected_excs:
  173. return
  174. if not func_node.doc:
  175. # If this is a property setter,
  176. # the property should have the docstring instead.
  177. property_ = utils.get_setters_property(func_node)
  178. if property_:
  179. func_node = property_
  180. doc = utils.docstringify(func_node.doc)
  181. if not doc.is_valid():
  182. if doc.doc:
  183. self._handle_no_raise_doc(expected_excs, func_node)
  184. return
  185. found_excs = doc.exceptions()
  186. missing_excs = expected_excs - found_excs
  187. self._add_raise_message(missing_excs, func_node)
  188. def visit_return(self, node):
  189. if not utils.returns_something(node):
  190. return
  191. func_node = node.frame()
  192. if not isinstance(func_node, astroid.FunctionDef):
  193. return
  194. doc = utils.docstringify(func_node.doc)
  195. if not doc.is_valid() and self.config.accept_no_return_doc:
  196. return
  197. is_property = checker_utils.decorated_with_property(func_node)
  198. if not (doc.has_returns() or
  199. (doc.has_property_returns() and is_property)):
  200. self.add_message(
  201. 'missing-return-doc',
  202. node=func_node
  203. )
  204. if not (doc.has_rtype() or
  205. (doc.has_property_type() and is_property)):
  206. self.add_message(
  207. 'missing-return-type-doc',
  208. node=func_node
  209. )
  210. def visit_yield(self, node):
  211. func_node = node.frame()
  212. if not isinstance(func_node, astroid.FunctionDef):
  213. return
  214. doc = utils.docstringify(func_node.doc)
  215. if not doc.is_valid() and self.config.accept_no_yields_doc:
  216. return
  217. if doc.supports_yields:
  218. doc_has_yields = doc.has_yields()
  219. doc_has_yields_type = doc.has_yields_type()
  220. else:
  221. doc_has_yields = doc.has_returns()
  222. doc_has_yields_type = doc.has_rtype()
  223. if not doc_has_yields:
  224. self.add_message(
  225. 'missing-yield-doc',
  226. node=func_node
  227. )
  228. if not doc_has_yields_type:
  229. self.add_message(
  230. 'missing-yield-type-doc',
  231. node=func_node
  232. )
  233. def visit_yieldfrom(self, node):
  234. self.visit_yield(node)
  235. def check_arguments_in_docstring(self, doc, arguments_node, warning_node,
  236. accept_no_param_doc=None):
  237. """Check that all parameters in a function, method or class constructor
  238. on the one hand and the parameters mentioned in the parameter
  239. documentation (e.g. the Sphinx tags 'param' and 'type') on the other
  240. hand are consistent with each other.
  241. * Undocumented parameters except 'self' are noticed.
  242. * Undocumented parameter types except for 'self' and the ``*<args>``
  243. and ``**<kwargs>`` parameters are noticed.
  244. * Parameters mentioned in the parameter documentation that don't or no
  245. longer exist in the function parameter list are noticed.
  246. * If the text "For the parameters, see" or "For the other parameters,
  247. see" (ignoring additional whitespace) is mentioned in the docstring,
  248. missing parameter documentation is tolerated.
  249. * If there's no Sphinx style, Google style or NumPy style parameter
  250. documentation at all, i.e. ``:param`` is never mentioned etc., the
  251. checker assumes that the parameters are documented in another format
  252. and the absence is tolerated.
  253. :param doc: Docstring for the function, method or class.
  254. :type doc: str
  255. :param arguments_node: Arguments node for the function, method or
  256. class constructor.
  257. :type arguments_node: :class:`astroid.scoped_nodes.Arguments`
  258. :param warning_node: The node to assign the warnings to
  259. :type warning_node: :class:`astroid.scoped_nodes.Node`
  260. :param accept_no_param_doc: Whether or not to allow no parameters
  261. to be documented.
  262. If None then this value is read from the configuration.
  263. :type accept_no_param_doc: bool or None
  264. """
  265. # Tolerate missing param or type declarations if there is a link to
  266. # another method carrying the same name.
  267. if not doc.doc:
  268. return
  269. if accept_no_param_doc is None:
  270. accept_no_param_doc = self.config.accept_no_param_doc
  271. tolerate_missing_params = doc.params_documented_elsewhere()
  272. # Collect the function arguments.
  273. expected_argument_names = set(arg.name for arg in arguments_node.args)
  274. expected_argument_names.update(arg.name for arg in arguments_node.kwonlyargs)
  275. not_needed_type_in_docstring = (
  276. self.not_needed_param_in_docstring.copy())
  277. if arguments_node.vararg is not None:
  278. expected_argument_names.add(arguments_node.vararg)
  279. not_needed_type_in_docstring.add(arguments_node.vararg)
  280. if arguments_node.kwarg is not None:
  281. expected_argument_names.add(arguments_node.kwarg)
  282. not_needed_type_in_docstring.add(arguments_node.kwarg)
  283. params_with_doc, params_with_type = doc.match_param_docs()
  284. # Tolerate no parameter documentation at all.
  285. if (not params_with_doc and not params_with_type
  286. and accept_no_param_doc):
  287. tolerate_missing_params = True
  288. def _compare_missing_args(found_argument_names, message_id,
  289. not_needed_names):
  290. """Compare the found argument names with the expected ones and
  291. generate a message if there are arguments missing.
  292. :param set found_argument_names: argument names found in the
  293. docstring
  294. :param str message_id: pylint message id
  295. :param not_needed_names: names that may be omitted
  296. :type not_needed_names: set of str
  297. """
  298. if not tolerate_missing_params:
  299. missing_argument_names = (
  300. (expected_argument_names - found_argument_names)
  301. - not_needed_names)
  302. if missing_argument_names:
  303. self.add_message(
  304. message_id,
  305. args=(', '.join(
  306. sorted(missing_argument_names)),),
  307. node=warning_node)
  308. def _compare_different_args(found_argument_names, message_id,
  309. not_needed_names):
  310. """Compare the found argument names with the expected ones and
  311. generate a message if there are extra arguments found.
  312. :param set found_argument_names: argument names found in the
  313. docstring
  314. :param str message_id: pylint message id
  315. :param not_needed_names: names that may be omitted
  316. :type not_needed_names: set of str
  317. """
  318. differing_argument_names = (
  319. (expected_argument_names ^ found_argument_names)
  320. - not_needed_names - expected_argument_names)
  321. if differing_argument_names:
  322. self.add_message(
  323. message_id,
  324. args=(', '.join(
  325. sorted(differing_argument_names)),),
  326. node=warning_node)
  327. _compare_missing_args(params_with_doc, 'missing-param-doc',
  328. self.not_needed_param_in_docstring)
  329. _compare_missing_args(params_with_type, 'missing-type-doc',
  330. not_needed_type_in_docstring)
  331. _compare_different_args(params_with_doc, 'differing-param-doc',
  332. self.not_needed_param_in_docstring)
  333. _compare_different_args(params_with_type, 'differing-type-doc',
  334. not_needed_type_in_docstring)
  335. def check_single_constructor_params(self, class_doc, init_doc, class_node):
  336. if class_doc.has_params() and init_doc.has_params():
  337. self.add_message(
  338. 'multiple-constructor-doc',
  339. args=(class_node.name,),
  340. node=class_node)
  341. def _handle_no_raise_doc(self, excs, node):
  342. if self.config.accept_no_raise_doc:
  343. return
  344. self._add_raise_message(excs, node)
  345. def _add_raise_message(self, missing_excs, node):
  346. """
  347. Adds a message on :param:`node` for the missing exception type.
  348. :param missing_excs: A list of missing exception types.
  349. :type missing_excs: set(str)
  350. :param node: The node show the message on.
  351. :type node: astroid.node_classes.NodeNG
  352. """
  353. if node.is_abstract():
  354. try:
  355. missing_excs.remove('NotImplementedError')
  356. except KeyError:
  357. pass
  358. if not missing_excs:
  359. return
  360. self.add_message(
  361. 'missing-raises-doc',
  362. args=(', '.join(sorted(missing_excs)),),
  363. node=node)
  364. def register(linter):
  365. """Required method to auto register this checker.
  366. :param linter: Main interface object for Pylint plugins
  367. :type linter: Pylint object
  368. """
  369. linter.register_checker(DocstringParameterChecker(linter))