docstring_checker.py 7.7 KB

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