docstring_checker.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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. """Docstring checker from the basic checker."""
  5. from __future__ import annotations
  6. import re
  7. from typing import Literal
  8. import astroid
  9. from astroid import nodes
  10. from pylint import interfaces
  11. from pylint.checkers import utils
  12. from pylint.checkers.base.basic_checker import _BasicChecker
  13. from pylint.checkers.utils import (
  14. is_overload_stub,
  15. is_property_deleter,
  16. is_property_setter,
  17. )
  18. # do not require a doc string on private/system methods
  19. NO_REQUIRED_DOC_RGX = re.compile("^_")
  20. def _infer_dunder_doc_attribute(
  21. node: nodes.Module | nodes.ClassDef | nodes.FunctionDef,
  22. ) -> str | None:
  23. # Try to see if we have a `__doc__` attribute.
  24. try:
  25. docstring = node["__doc__"]
  26. except KeyError:
  27. return None
  28. docstring = utils.safe_infer(docstring)
  29. if isinstance(docstring, nodes.Const):
  30. return str(docstring.value)
  31. return None
  32. class DocStringChecker(_BasicChecker):
  33. msgs = {
  34. "C0112": (
  35. "Empty %s docstring",
  36. "empty-docstring",
  37. "Used when a module, function, class or method has an empty "
  38. "docstring (it would be too easy ;).",
  39. {"old_names": [("W0132", "old-empty-docstring")]},
  40. ),
  41. "C0114": (
  42. "Missing module docstring",
  43. "missing-module-docstring",
  44. "Used when a module has no docstring. "
  45. "Empty modules do not require a docstring.",
  46. {"old_names": [("C0111", "missing-docstring")]},
  47. ),
  48. "C0115": (
  49. "Missing class docstring",
  50. "missing-class-docstring",
  51. "Used when a class has no docstring. "
  52. "Even an empty class must have a docstring.",
  53. {"old_names": [("C0111", "missing-docstring")]},
  54. ),
  55. "C0116": (
  56. "Missing function or method docstring",
  57. "missing-function-docstring",
  58. "Used when a function or method has no docstring. "
  59. "Some special methods like __init__ do not require a "
  60. "docstring.",
  61. {"old_names": [("C0111", "missing-docstring")]},
  62. ),
  63. }
  64. options = (
  65. (
  66. "no-docstring-rgx",
  67. {
  68. "default": NO_REQUIRED_DOC_RGX,
  69. "type": "regexp",
  70. "metavar": "<regexp>",
  71. "help": "Regular expression which should only match "
  72. "function or class names that do not require a "
  73. "docstring.",
  74. },
  75. ),
  76. (
  77. "docstring-min-length",
  78. {
  79. "default": -1,
  80. "type": "int",
  81. "metavar": "<int>",
  82. "help": (
  83. "Minimum line length for functions/classes that"
  84. " require docstrings, shorter ones are exempt."
  85. ),
  86. },
  87. ),
  88. )
  89. def open(self) -> None:
  90. self.linter.stats.reset_undocumented()
  91. @utils.only_required_for_messages("missing-module-docstring", "empty-docstring")
  92. def visit_module(self, node: nodes.Module) -> None:
  93. self._check_docstring("module", node)
  94. @utils.only_required_for_messages("missing-class-docstring", "empty-docstring")
  95. def visit_classdef(self, node: nodes.ClassDef) -> None:
  96. if self.linter.config.no_docstring_rgx.match(node.name) is None:
  97. self._check_docstring("class", node)
  98. @utils.only_required_for_messages("missing-function-docstring", "empty-docstring")
  99. def visit_functiondef(self, node: nodes.FunctionDef) -> None:
  100. if self.linter.config.no_docstring_rgx.match(node.name) is None:
  101. ftype = "method" if node.is_method() else "function"
  102. if (
  103. is_property_setter(node)
  104. or is_property_deleter(node)
  105. or is_overload_stub(node)
  106. ):
  107. return
  108. if isinstance(node.parent.frame(), nodes.ClassDef):
  109. overridden = False
  110. confidence = (
  111. interfaces.INFERENCE
  112. if utils.has_known_bases(node.parent.frame())
  113. else interfaces.INFERENCE_FAILURE
  114. )
  115. # check if node is from a method overridden by its ancestor
  116. for ancestor in node.parent.frame().ancestors():
  117. if ancestor.qname() == "builtins.object":
  118. continue
  119. if node.name in ancestor and isinstance(
  120. ancestor[node.name], nodes.FunctionDef
  121. ):
  122. overridden = True
  123. break
  124. self._check_docstring(
  125. ftype, node, report_missing=not overridden, confidence=confidence # type: ignore[arg-type]
  126. )
  127. elif isinstance(node.parent.frame(), nodes.Module):
  128. self._check_docstring(ftype, node) # type: ignore[arg-type]
  129. else:
  130. return
  131. visit_asyncfunctiondef = visit_functiondef
  132. def _check_docstring(
  133. self,
  134. node_type: Literal["class", "function", "method", "module"],
  135. node: nodes.Module | nodes.ClassDef | nodes.FunctionDef,
  136. report_missing: bool = True,
  137. confidence: interfaces.Confidence = interfaces.HIGH,
  138. ) -> None:
  139. """Check if the node has a non-empty docstring."""
  140. docstring = node.doc_node.value if node.doc_node else None
  141. if docstring is None:
  142. docstring = _infer_dunder_doc_attribute(node)
  143. if docstring is None:
  144. if not report_missing:
  145. return
  146. lines = utils.get_node_last_lineno(node) - node.lineno
  147. if node_type == "module" and not lines:
  148. # If the module does not have a body, there's no reason
  149. # to require a docstring.
  150. return
  151. max_lines = self.linter.config.docstring_min_length
  152. if node_type != "module" and max_lines > -1 and lines < max_lines:
  153. return
  154. if node_type == "class":
  155. self.linter.stats.undocumented["klass"] += 1
  156. else:
  157. self.linter.stats.undocumented[node_type] += 1
  158. match node.body:
  159. case [nodes.Expr(value=nodes.Call() as value), *_]:
  160. # Most likely a string with a format call. Let's see.
  161. match utils.safe_infer(value.func):
  162. case astroid.BoundMethod(
  163. bound=astroid.Instance(name="str" | "unicode" | "bytes")
  164. ):
  165. # Strings.
  166. return
  167. match node_type:
  168. case "module":
  169. message = "missing-module-docstring"
  170. case "class":
  171. message = "missing-class-docstring"
  172. case _:
  173. message = "missing-function-docstring"
  174. self.add_message(message, node=node, confidence=confidence)
  175. elif not docstring.strip():
  176. if node_type == "class":
  177. self.linter.stats.undocumented["klass"] += 1
  178. else:
  179. self.linter.stats.undocumented[node_type] += 1
  180. self.add_message(
  181. "empty-docstring", node=node, args=(node_type,), confidence=confidence
  182. )