docstyle.py 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  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. from __future__ import annotations
  5. import linecache
  6. from typing import TYPE_CHECKING
  7. from astroid import nodes
  8. from pylint import checkers
  9. from pylint.checkers.utils import only_required_for_messages
  10. from pylint.interfaces import HIGH
  11. if TYPE_CHECKING:
  12. from pylint.lint import PyLinter
  13. class DocStringStyleChecker(checkers.BaseChecker):
  14. """Checks format of docstrings based on PEP 0257."""
  15. name = "docstyle"
  16. msgs = {
  17. "C0198": (
  18. 'Bad docstring quotes in %s, expected """, given %s',
  19. "bad-docstring-quotes",
  20. "Used when a docstring does not have triple double quotes.",
  21. ),
  22. "C0199": (
  23. "First line empty in %s docstring",
  24. "docstring-first-line-empty",
  25. "Used when a blank line is found at the beginning of a docstring.",
  26. ),
  27. }
  28. @only_required_for_messages("docstring-first-line-empty", "bad-docstring-quotes")
  29. def visit_module(self, node: nodes.Module) -> None:
  30. self._check_docstring("module", node)
  31. def visit_classdef(self, node: nodes.ClassDef) -> None:
  32. self._check_docstring("class", node)
  33. def visit_functiondef(self, node: nodes.FunctionDef) -> None:
  34. ftype = "method" if node.is_method() else "function"
  35. self._check_docstring(ftype, node)
  36. visit_asyncfunctiondef = visit_functiondef
  37. def _check_docstring(
  38. self, node_type: str, node: nodes.Module | nodes.ClassDef | nodes.FunctionDef
  39. ) -> None:
  40. docstring = node.doc_node.value if node.doc_node else None
  41. if docstring and docstring[0] == "\n":
  42. self.add_message(
  43. "docstring-first-line-empty",
  44. node=node,
  45. args=(node_type,),
  46. confidence=HIGH,
  47. )
  48. # Use "linecache", instead of node.as_string(), because the latter
  49. # looses the original form of the docstrings.
  50. if docstring:
  51. lineno = node.fromlineno + 1
  52. line = linecache.getline(node.root().file, lineno).lstrip()
  53. if line and line.find('"""') == 0:
  54. return
  55. if line and "'''" in line:
  56. quotes = "'''"
  57. elif line and line[0] == '"':
  58. quotes = '"'
  59. elif line and line[0] == "'":
  60. quotes = "'"
  61. else:
  62. quotes = ""
  63. if quotes:
  64. self.add_message(
  65. "bad-docstring-quotes",
  66. node=node,
  67. args=(node_type, quotes),
  68. confidence=HIGH,
  69. )
  70. def register(linter: PyLinter) -> None:
  71. linter.register_checker(DocStringStyleChecker(linter))