docparams.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. """Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings."""
  5. from __future__ import annotations
  6. import re
  7. from typing import TYPE_CHECKING
  8. from astroid import nodes
  9. from pylint.checkers import BaseChecker
  10. from pylint.checkers import utils as checker_utils
  11. from pylint.extensions import _check_docs_utils as utils
  12. from pylint.extensions._check_docs_utils import Docstring
  13. from pylint.interfaces import HIGH
  14. if TYPE_CHECKING:
  15. from pylint.lint import PyLinter
  16. class DocstringParameterChecker(BaseChecker):
  17. """Checker for Sphinx, Google, or Numpy style docstrings.
  18. * Check that all function, method and constructor parameters are mentioned
  19. in the params and types part of the docstring. Constructor parameters
  20. can be documented in either the class docstring or ``__init__`` docstring,
  21. but not both.
  22. * Check that there are no naming inconsistencies between the signature and
  23. the documentation, i.e. also report documented parameters that are missing
  24. in the signature. This is important to find cases where parameters are
  25. renamed only in the code, not in the documentation.
  26. * Check that all explicitly raised exceptions in a function are documented
  27. in the function docstring. Caught exceptions are ignored.
  28. Activate this checker by adding the line::
  29. load-plugins=pylint.extensions.docparams
  30. to the ``MAIN`` section of your ``.pylintrc``.
  31. """
  32. name = "parameter_documentation"
  33. msgs = {
  34. "W9005": (
  35. '"%s" has constructor parameters documented in class and __init__',
  36. "multiple-constructor-doc",
  37. "Please remove parameter declarations in the class or constructor.",
  38. ),
  39. "W9006": (
  40. '"%s" not documented as being raised',
  41. "missing-raises-doc",
  42. "Please document exceptions for all raised exception types.",
  43. ),
  44. "W9008": (
  45. "Redundant returns documentation",
  46. "redundant-returns-doc",
  47. "Please remove the return/rtype documentation from this method.",
  48. ),
  49. "W9010": (
  50. "Redundant yields documentation",
  51. "redundant-yields-doc",
  52. "Please remove the yields documentation from this method.",
  53. ),
  54. "W9011": (
  55. "Missing return documentation",
  56. "missing-return-doc",
  57. "Please add documentation about what this method returns.",
  58. {"old_names": [("W9007", "old-missing-returns-doc")]},
  59. ),
  60. "W9012": (
  61. "Missing return type documentation",
  62. "missing-return-type-doc",
  63. "Please document the type returned by this method.",
  64. # we can't use the same old_name for two different warnings
  65. # {'old_names': [('W9007', 'missing-returns-doc')]},
  66. ),
  67. "W9013": (
  68. "Missing yield documentation",
  69. "missing-yield-doc",
  70. "Please add documentation about what this generator yields.",
  71. {"old_names": [("W9009", "old-missing-yields-doc")]},
  72. ),
  73. "W9014": (
  74. "Missing yield type documentation",
  75. "missing-yield-type-doc",
  76. "Please document the type yielded by this method.",
  77. # we can't use the same old_name for two different warnings
  78. # {'old_names': [('W9009', 'missing-yields-doc')]},
  79. ),
  80. "W9015": (
  81. '"%s" missing in parameter documentation',
  82. "missing-param-doc",
  83. "Please add parameter declarations for all parameters.",
  84. {"old_names": [("W9003", "old-missing-param-doc")]},
  85. ),
  86. "W9016": (
  87. '"%s" missing in parameter type documentation',
  88. "missing-type-doc",
  89. "Please add parameter type declarations for all parameters.",
  90. {"old_names": [("W9004", "old-missing-type-doc")]},
  91. ),
  92. "W9017": (
  93. '"%s" differing in parameter documentation',
  94. "differing-param-doc",
  95. "Please check parameter names in declarations.",
  96. ),
  97. "W9018": (
  98. '"%s" differing in parameter type documentation',
  99. "differing-type-doc",
  100. "Please check parameter names in type declarations.",
  101. ),
  102. "W9019": (
  103. '"%s" useless ignored parameter documentation',
  104. "useless-param-doc",
  105. "Please remove the ignored parameter documentation.",
  106. ),
  107. "W9020": (
  108. '"%s" useless ignored parameter type documentation',
  109. "useless-type-doc",
  110. "Please remove the ignored parameter type documentation.",
  111. ),
  112. "W9021": (
  113. 'Missing any documentation in "%s"',
  114. "missing-any-param-doc",
  115. "Please add parameter and/or type documentation.",
  116. ),
  117. }
  118. options = (
  119. (
  120. "accept-no-param-doc",
  121. {
  122. "default": True,
  123. "type": "yn",
  124. "metavar": "<y or n>",
  125. "help": "Whether to accept totally missing parameter "
  126. "documentation in the docstring of a function that has "
  127. "parameters.",
  128. },
  129. ),
  130. (
  131. "accept-no-raise-doc",
  132. {
  133. "default": True,
  134. "type": "yn",
  135. "metavar": "<y or n>",
  136. "help": "Whether to accept totally missing raises "
  137. "documentation in the docstring of a function that "
  138. "raises an exception.",
  139. },
  140. ),
  141. (
  142. "accept-no-return-doc",
  143. {
  144. "default": True,
  145. "type": "yn",
  146. "metavar": "<y or n>",
  147. "help": "Whether to accept totally missing return "
  148. "documentation in the docstring of a function that "
  149. "returns a statement.",
  150. },
  151. ),
  152. (
  153. "accept-no-yields-doc",
  154. {
  155. "default": True,
  156. "type": "yn",
  157. "metavar": "<y or n>",
  158. "help": "Whether to accept totally missing yields "
  159. "documentation in the docstring of a generator.",
  160. },
  161. ),
  162. (
  163. "default-docstring-type",
  164. {
  165. "type": "choice",
  166. "default": "default",
  167. "metavar": "<docstring type>",
  168. "choices": list(utils.DOCSTRING_TYPES),
  169. "help": "If the docstring type cannot be guessed "
  170. "the specified docstring type will be used.",
  171. },
  172. ),
  173. )
  174. constructor_names = {"__init__", "__new__"}
  175. not_needed_param_in_docstring = {"self", "cls"}
  176. def visit_functiondef(self, node: nodes.FunctionDef) -> None:
  177. """Called for function and method definitions (def).
  178. :param node: Node for a function or method definition in the AST
  179. :type node: :class:`astroid.scoped_nodes.Function`
  180. """
  181. if checker_utils.is_overload_stub(node):
  182. return
  183. node_doc = utils.docstringify(
  184. node.doc_node, self.linter.config.default_docstring_type
  185. )
  186. # skip functions that match the 'no-docstring-rgx' config option
  187. no_docstring_rgx = self.linter.config.no_docstring_rgx
  188. if no_docstring_rgx and re.match(no_docstring_rgx, node.name):
  189. return
  190. # skip functions smaller than 'docstring-min-length'
  191. if self._is_shorter_than_min_length(node):
  192. return
  193. self.check_functiondef_params(node, node_doc)
  194. self.check_functiondef_returns(node, node_doc)
  195. self.check_functiondef_yields(node, node_doc)
  196. visit_asyncfunctiondef = visit_functiondef
  197. def check_functiondef_params(
  198. self, node: nodes.FunctionDef, node_doc: Docstring
  199. ) -> None:
  200. node_allow_no_param = None
  201. if node.name in self.constructor_names:
  202. class_node = checker_utils.node_frame_class(node)
  203. if class_node is not None:
  204. class_doc = utils.docstringify(
  205. class_node.doc_node, self.linter.config.default_docstring_type
  206. )
  207. self.check_single_constructor_params(class_doc, node_doc, class_node)
  208. # __init__ or class docstrings can have no parameters documented
  209. # as long as the other documents them.
  210. node_allow_no_param = (
  211. class_doc.has_params()
  212. or class_doc.params_documented_elsewhere()
  213. or None
  214. )
  215. class_allow_no_param = (
  216. node_doc.has_params()
  217. or node_doc.params_documented_elsewhere()
  218. or None
  219. )
  220. self.check_arguments_in_docstring(
  221. class_doc, node.args, class_node, class_allow_no_param
  222. )
  223. self.check_arguments_in_docstring(
  224. node_doc, node.args, node, node_allow_no_param
  225. )
  226. def check_functiondef_returns(
  227. self, node: nodes.FunctionDef, node_doc: Docstring
  228. ) -> None:
  229. if (not node_doc.supports_yields and node.is_generator()) or node.is_abstract():
  230. return
  231. return_nodes = node.nodes_of_class(nodes.Return)
  232. if (node_doc.has_returns() or node_doc.has_rtype()) and not any(
  233. utils.returns_something(ret_node) for ret_node in return_nodes
  234. ):
  235. self.add_message("redundant-returns-doc", node=node, confidence=HIGH)
  236. def check_functiondef_yields(
  237. self, node: nodes.FunctionDef, node_doc: Docstring
  238. ) -> None:
  239. if not node_doc.supports_yields or node.is_abstract():
  240. return
  241. if (
  242. node_doc.has_yields() or node_doc.has_yields_type()
  243. ) and not node.is_generator():
  244. self.add_message("redundant-yields-doc", node=node)
  245. def visit_raise(self, node: nodes.Raise) -> None:
  246. func_node = node.frame()
  247. if not isinstance(func_node, nodes.FunctionDef):
  248. return
  249. # skip functions smaller than 'docstring-min-length'
  250. if self._is_shorter_than_min_length(node):
  251. return
  252. # skip functions that match the 'no-docstring-rgx' config option
  253. no_docstring_rgx = self.linter.config.no_docstring_rgx
  254. if no_docstring_rgx and re.match(no_docstring_rgx, func_node.name):
  255. return
  256. expected_excs = utils.possible_exc_types(node)
  257. if not expected_excs:
  258. return
  259. if not func_node.doc_node:
  260. # If this is a property setter,
  261. # the property should have the docstring instead.
  262. property_ = utils.get_setters_property(func_node)
  263. if property_:
  264. func_node = property_
  265. doc = utils.docstringify(
  266. func_node.doc_node, self.linter.config.default_docstring_type
  267. )
  268. if self.linter.config.accept_no_raise_doc and not doc.exceptions():
  269. return
  270. if not doc.matching_sections():
  271. if doc.doc:
  272. missing = {exc.name for exc in expected_excs}
  273. self._add_raise_message(missing, func_node)
  274. return
  275. found_excs_full_names = doc.exceptions()
  276. # Extract just the class name, e.g. "error" from "re.error"
  277. found_excs_class_names = {exc.split(".")[-1] for exc in found_excs_full_names}
  278. missing_excs = set()
  279. for expected in expected_excs:
  280. for found_exc in found_excs_class_names:
  281. if found_exc == expected.name:
  282. break
  283. if found_exc == "error" and expected.name == "PatternError":
  284. # Python 3.13: re.error aliases re.PatternError
  285. break
  286. if any(found_exc == ancestor.name for ancestor in expected.ancestors()):
  287. break
  288. else:
  289. missing_excs.add(expected.name)
  290. self._add_raise_message(missing_excs, func_node)
  291. def visit_return(self, node: nodes.Return) -> None:
  292. if not utils.returns_something(node):
  293. return
  294. if self.linter.config.accept_no_return_doc:
  295. return
  296. func_node: nodes.FunctionDef = node.frame()
  297. # skip functions that match the 'no-docstring-rgx' config option
  298. no_docstring_rgx = self.linter.config.no_docstring_rgx
  299. if no_docstring_rgx and re.match(no_docstring_rgx, func_node.name):
  300. return
  301. doc = utils.docstringify(
  302. func_node.doc_node, self.linter.config.default_docstring_type
  303. )
  304. is_property = checker_utils.decorated_with_property(func_node)
  305. if not (doc.has_returns() or (doc.has_property_returns() and is_property)):
  306. self.add_message("missing-return-doc", node=func_node, confidence=HIGH)
  307. if func_node.returns or func_node.type_comment_returns:
  308. return
  309. if not (doc.has_rtype() or (doc.has_property_type() and is_property)):
  310. self.add_message("missing-return-type-doc", node=func_node, confidence=HIGH)
  311. def visit_yield(self, node: nodes.Yield | nodes.YieldFrom) -> None:
  312. if self.linter.config.accept_no_yields_doc:
  313. return
  314. # skip functions smaller than 'docstring-min-length'
  315. if self._is_shorter_than_min_length(node):
  316. return
  317. func_node: nodes.FunctionDef = node.frame()
  318. # skip functions that match the 'no-docstring-rgx' config option
  319. no_docstring_rgx = self.linter.config.no_docstring_rgx
  320. if no_docstring_rgx and re.match(no_docstring_rgx, func_node.name):
  321. return
  322. doc = utils.docstringify(
  323. func_node.doc_node, self.linter.config.default_docstring_type
  324. )
  325. if doc.supports_yields:
  326. doc_has_yields = doc.has_yields()
  327. doc_has_yields_type = doc.has_yields_type()
  328. else:
  329. doc_has_yields = doc.has_returns()
  330. doc_has_yields_type = doc.has_rtype()
  331. if not doc_has_yields:
  332. self.add_message("missing-yield-doc", node=func_node, confidence=HIGH)
  333. if not (
  334. doc_has_yields_type or func_node.returns or func_node.type_comment_returns
  335. ):
  336. self.add_message("missing-yield-type-doc", node=func_node, confidence=HIGH)
  337. visit_yieldfrom = visit_yield
  338. def _compare_missing_args(
  339. self,
  340. found_argument_names: set[str],
  341. message_id: str,
  342. not_needed_names: set[str],
  343. expected_argument_names: set[str],
  344. warning_node: nodes.NodeNG,
  345. ) -> None:
  346. """Compare the found argument names with the expected ones and
  347. generate a message if there are arguments missing.
  348. :param found_argument_names: argument names found in the docstring
  349. :param message_id: pylint message id
  350. :param not_needed_names: names that may be omitted
  351. :param expected_argument_names: Expected argument names
  352. :param warning_node: The node to be analyzed
  353. """
  354. potential_missing_argument_names = (
  355. expected_argument_names - found_argument_names
  356. ) - not_needed_names
  357. # Handle variadic and keyword args without asterisks
  358. missing_argument_names = set()
  359. for name in potential_missing_argument_names:
  360. if name.replace("*", "") in found_argument_names:
  361. continue
  362. missing_argument_names.add(name)
  363. if missing_argument_names:
  364. self.add_message(
  365. message_id,
  366. args=(", ".join(sorted(missing_argument_names)),),
  367. node=warning_node,
  368. confidence=HIGH,
  369. )
  370. def _compare_different_args(
  371. self,
  372. found_argument_names: set[str],
  373. message_id: str,
  374. not_needed_names: set[str],
  375. expected_argument_names: set[str],
  376. warning_node: nodes.NodeNG,
  377. ) -> None:
  378. """Compare the found argument names with the expected ones and
  379. generate a message if there are extra arguments found.
  380. :param found_argument_names: argument names found in the docstring
  381. :param message_id: pylint message id
  382. :param not_needed_names: names that may be omitted
  383. :param expected_argument_names: Expected argument names
  384. :param warning_node: The node to be analyzed
  385. """
  386. # Handle variadic and keyword args without asterisks
  387. modified_expected_argument_names: set[str] = set()
  388. for name in expected_argument_names:
  389. if name.replace("*", "") in found_argument_names:
  390. modified_expected_argument_names.add(name.replace("*", ""))
  391. else:
  392. modified_expected_argument_names.add(name)
  393. differing_argument_names = (
  394. (modified_expected_argument_names ^ found_argument_names)
  395. - not_needed_names
  396. - expected_argument_names
  397. )
  398. if differing_argument_names:
  399. self.add_message(
  400. message_id,
  401. args=(", ".join(sorted(differing_argument_names)),),
  402. node=warning_node,
  403. confidence=HIGH,
  404. )
  405. def _compare_ignored_args( # pylint: disable=useless-param-doc
  406. self,
  407. found_argument_names: set[str],
  408. message_id: str,
  409. ignored_argument_names: set[str],
  410. warning_node: nodes.NodeNG,
  411. ) -> None:
  412. """Compare the found argument names with the ignored ones and
  413. generate a message if there are ignored arguments found.
  414. :param found_argument_names: argument names found in the docstring
  415. :param message_id: pylint message id
  416. :param ignored_argument_names: Expected argument names
  417. :param warning_node: The node to be analyzed
  418. """
  419. existing_ignored_argument_names = ignored_argument_names & found_argument_names
  420. if existing_ignored_argument_names:
  421. self.add_message(
  422. message_id,
  423. args=(", ".join(sorted(existing_ignored_argument_names)),),
  424. node=warning_node,
  425. confidence=HIGH,
  426. )
  427. def check_arguments_in_docstring(
  428. self,
  429. doc: Docstring,
  430. arguments_node: nodes.Arguments,
  431. warning_node: nodes.NodeNG,
  432. accept_no_param_doc: bool | None = None,
  433. ) -> None:
  434. """Check that all parameters are consistent with the parameters mentioned
  435. in the parameter documentation (e.g. the Sphinx tags 'param' and 'type').
  436. * Undocumented parameters except 'self' are noticed.
  437. * Undocumented parameter types except for 'self' and the ``*<args>``
  438. and ``**<kwargs>`` parameters are noticed.
  439. * Parameters mentioned in the parameter documentation that don't or no
  440. longer exist in the function parameter list are noticed.
  441. * If the text "For the parameters, see" or "For the other parameters,
  442. see" (ignoring additional white-space) is mentioned in the docstring,
  443. missing parameter documentation is tolerated.
  444. * If there's no Sphinx style, Google style or NumPy style parameter
  445. documentation at all, i.e. ``:param`` is never mentioned etc., the
  446. checker assumes that the parameters are documented in another format
  447. and the absence is tolerated.
  448. :param doc: Docstring for the function, method or class.
  449. :type doc: :class:`Docstring`
  450. :param arguments_node: Arguments node for the function, method or
  451. class constructor.
  452. :type arguments_node: :class:`astroid.nodes.Arguments`
  453. :param warning_node: The node to assign the warnings to
  454. :type warning_node: :class:`astroid.nodes.NodeNG`
  455. :param accept_no_param_doc: Whether to allow no parameters to be
  456. documented. If None then this value is read from the configuration.
  457. :type accept_no_param_doc: bool or None
  458. """
  459. # Tolerate missing param or type declarations if there is a link to
  460. # another method carrying the same name.
  461. if not doc.doc:
  462. return
  463. if accept_no_param_doc is None:
  464. accept_no_param_doc = self.linter.config.accept_no_param_doc
  465. tolerate_missing_params = doc.params_documented_elsewhere()
  466. # Collect the function arguments.
  467. expected_argument_names = {arg.name for arg in arguments_node.args}
  468. expected_argument_names.update(
  469. a.name for a in arguments_node.posonlyargs + arguments_node.kwonlyargs
  470. )
  471. not_needed_type_in_docstring = self.not_needed_param_in_docstring.copy()
  472. expected_but_ignored_argument_names = set()
  473. ignored_argument_names = self.linter.config.ignored_argument_names
  474. if ignored_argument_names:
  475. expected_but_ignored_argument_names = {
  476. arg
  477. for arg in expected_argument_names
  478. if ignored_argument_names.match(arg)
  479. }
  480. if arguments_node.vararg is not None:
  481. expected_argument_names.add(f"*{arguments_node.vararg}")
  482. not_needed_type_in_docstring.add(f"*{arguments_node.vararg}")
  483. if arguments_node.kwarg is not None:
  484. expected_argument_names.add(f"**{arguments_node.kwarg}")
  485. not_needed_type_in_docstring.add(f"**{arguments_node.kwarg}")
  486. params_with_doc, params_with_type = doc.match_param_docs()
  487. # Tolerate no parameter documentation at all.
  488. if not params_with_doc and not params_with_type and accept_no_param_doc:
  489. tolerate_missing_params = True
  490. # This is before the update of params_with_type because this must check only
  491. # the type documented in a docstring, not the one using pep484
  492. # See #4117 and #4593
  493. self._compare_ignored_args(
  494. params_with_type,
  495. "useless-type-doc",
  496. expected_but_ignored_argument_names,
  497. warning_node,
  498. )
  499. params_with_type |= utils.args_with_annotation(arguments_node)
  500. if not tolerate_missing_params:
  501. missing_param_doc = (expected_argument_names - params_with_doc) - (
  502. self.not_needed_param_in_docstring | expected_but_ignored_argument_names
  503. )
  504. missing_type_doc = (expected_argument_names - params_with_type) - (
  505. not_needed_type_in_docstring | expected_but_ignored_argument_names
  506. )
  507. if (
  508. missing_param_doc == expected_argument_names == missing_type_doc
  509. and len(expected_argument_names) != 0
  510. ):
  511. self.add_message(
  512. "missing-any-param-doc",
  513. args=(warning_node.name,),
  514. node=warning_node,
  515. confidence=HIGH,
  516. )
  517. else:
  518. self._compare_missing_args(
  519. params_with_doc,
  520. "missing-param-doc",
  521. self.not_needed_param_in_docstring
  522. | expected_but_ignored_argument_names,
  523. expected_argument_names,
  524. warning_node,
  525. )
  526. self._compare_missing_args(
  527. params_with_type,
  528. "missing-type-doc",
  529. not_needed_type_in_docstring | expected_but_ignored_argument_names,
  530. expected_argument_names,
  531. warning_node,
  532. )
  533. self._compare_different_args(
  534. params_with_doc,
  535. "differing-param-doc",
  536. self.not_needed_param_in_docstring,
  537. expected_argument_names,
  538. warning_node,
  539. )
  540. self._compare_different_args(
  541. params_with_type,
  542. "differing-type-doc",
  543. not_needed_type_in_docstring,
  544. expected_argument_names,
  545. warning_node,
  546. )
  547. self._compare_ignored_args(
  548. params_with_doc,
  549. "useless-param-doc",
  550. expected_but_ignored_argument_names,
  551. warning_node,
  552. )
  553. def check_single_constructor_params(
  554. self, class_doc: Docstring, init_doc: Docstring, class_node: nodes.ClassDef
  555. ) -> None:
  556. if class_doc.has_params() and init_doc.has_params():
  557. self.add_message(
  558. "multiple-constructor-doc",
  559. args=(class_node.name,),
  560. node=class_node,
  561. confidence=HIGH,
  562. )
  563. def _add_raise_message(
  564. self, missing_exceptions: set[str], node: nodes.FunctionDef
  565. ) -> None:
  566. """Adds a message on :param:`node` for the missing exception type.
  567. :param missing_exceptions: A list of missing exception types.
  568. :param node: The node to show the message on.
  569. """
  570. if node.is_abstract():
  571. try:
  572. missing_exceptions.remove("NotImplementedError")
  573. except KeyError:
  574. pass
  575. if missing_exceptions:
  576. self.add_message(
  577. "missing-raises-doc",
  578. args=(", ".join(sorted(missing_exceptions)),),
  579. node=node,
  580. confidence=HIGH,
  581. )
  582. def _is_shorter_than_min_length(self, node: nodes.FunctionDef) -> bool:
  583. """Returns true on functions smaller than 'docstring-min-length'.
  584. :param node: Node for a function or method definition in the AST
  585. :type node: :class:`astroid.nodes.FunctionDef`
  586. :rtype: bool
  587. """
  588. node_line_count = checker_utils.get_node_last_lineno(node) - node.lineno
  589. min_lines = self.linter.config.docstring_min_length
  590. result = -1 < node_line_count < min_lines
  591. assert isinstance(
  592. result, bool
  593. ), "Result of int comparison should have been a boolean"
  594. return result
  595. def register(linter: PyLinter) -> None:
  596. linter.register_checker(DocstringParameterChecker(linter))