docparams.py 25 KB

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