mirror of
https://fuchsia.googlesource.com/third_party/github.com/pylint-dev/pylint
synced 2024-09-21 16:19:21 +00:00
216 lines
8.3 KiB
Python
216 lines
8.3 KiB
Python
"""Pylint plugin for Sphinx parameter documentation checking
|
|
"""
|
|
from __future__ import print_function, division, absolute_import
|
|
|
|
import re
|
|
|
|
from pylint.interfaces import IAstroidChecker
|
|
from pylint.checkers import BaseChecker
|
|
import astroid.scoped_nodes
|
|
|
|
|
|
class SphinxDocChecker(BaseChecker):
|
|
"""Checker for Sphinx documentation parameters
|
|
|
|
* Check that all function, method and constructor parameters are mentioned
|
|
in the Sphinx params and types part of the docstring. By convention,
|
|
constructor parameters are documented in the class docstring.
|
|
* Check that there are no naming inconsistencies between the signature and
|
|
the documentation, i.e. also report documented parameters that are missing
|
|
in the signature. This is important to find cases where parameters are
|
|
renamed only in the code, not in the documentation.
|
|
|
|
Activate this checker by adding the line::
|
|
|
|
load-plugins=pylint.extensions.check_docs
|
|
|
|
to the ``MASTER`` section of your ``.pylintrc``.
|
|
|
|
:param linter: linter object
|
|
:type linter: :class:`pylint.lint.PyLinter`
|
|
"""
|
|
__implements__ = IAstroidChecker
|
|
|
|
name = 'Sphinx doc checks'
|
|
msgs = {
|
|
'W9003': ('"%s" missing or differing in Sphinx params',
|
|
'missing-sphinx-param',
|
|
'Please add Sphinx param declarations for all arguments.'),
|
|
'W9004': ('"%s" missing or differing in Sphinx types',
|
|
'missing-sphinx-type',
|
|
'Please add Sphinx type declarations for all arguments.'),
|
|
}
|
|
|
|
options = ()
|
|
|
|
priority = -2
|
|
|
|
def __init__(self, linter=None):
|
|
BaseChecker.__init__(self, linter)
|
|
|
|
def visit_function(self, node):
|
|
"""Called for function and method definitions (def).
|
|
|
|
:param node: Node for a function or method definition in the AST
|
|
:type node: :class:`astroid.scoped_nodes.Function`
|
|
"""
|
|
self.check_arguments_in_docstring(node, node.doc, node.args)
|
|
|
|
re_for_parameters_see = re.compile(r"""
|
|
For\s+the\s+(other)?\s*parameters\s*,\s+see
|
|
""", re.X | re.S)
|
|
|
|
re_prefix_of_func_name = re.compile(r"""
|
|
.* # part before final dot
|
|
\. # final dot
|
|
""", re.X | re.S)
|
|
|
|
re_param = re.compile(r"""
|
|
:\s*param\b
|
|
""", re.X | re.S)
|
|
|
|
re_sphinx_param_in_docstring = re.compile(r"""
|
|
:param # Sphinx keyword
|
|
\s+ # whitespace
|
|
|
|
(?: # optional type declaration
|
|
(\w+)
|
|
\s+
|
|
)?
|
|
|
|
(\w+) # Parameter name
|
|
\s* # whitespace
|
|
: # final colon
|
|
""", re.X | re.S)
|
|
|
|
re_sphinx_type_in_docstring = re.compile(r"""
|
|
:type # Sphinx keyword
|
|
\s+ # whitespace
|
|
(\w+) # Parameter name
|
|
\s* # whitespace
|
|
: # final colon
|
|
""", re.X | re.S)
|
|
|
|
not_needed_param_in_docstring = set(['self', 'cls'])
|
|
|
|
def check_arguments_in_docstring(self, node, doc, arguments_node):
|
|
"""Check that all arguments in a function, method or class constructor
|
|
on the one hand and the arguments mentioned in the Sphinx tags 'param'
|
|
and 'type' on the other hand are consistent with each other.
|
|
|
|
* Undocumented parameters except 'self' are noticed.
|
|
* Undocumented parameter types except for 'self' and the ``*<args>``
|
|
and ``**<kwargs>`` parameters are noticed.
|
|
* Parameters mentioned in the Sphinx documentation that don't or no
|
|
longer exist in the function parameter list are noticed.
|
|
* If there is a Sphinx link like ``:meth:...`` or ``:func:...`` to a
|
|
function carrying the same name as the current function, missing
|
|
parameter documentations are tolerated, but the existing parameters
|
|
mentioned in the documentation are checked.
|
|
* If there's no Sphinx parameter documentation at all, i.e. ``:param``
|
|
is never mentioned, the checker assumes that the parameters are
|
|
documented in some format other than Sphinx and the absence is
|
|
tolerated.
|
|
|
|
:param node: Node for a function, method or class definition in the AST.
|
|
:type node: :class:`astroid.scoped_nodes.Function` or
|
|
:class:`astroid.scoped_nodes.Class`
|
|
|
|
:param doc: Docstring for the function, method or class.
|
|
:type doc: str
|
|
|
|
:param arguments_node: Arguments node for the function, method or
|
|
class constructor.
|
|
:type arguments_node: :class:`astroid.scoped_nodes.Arguments`
|
|
"""
|
|
# Tolerate missing param or type declarations if there is a link to
|
|
# another method carrying the same name.
|
|
if node.doc is None:
|
|
return
|
|
|
|
tolerate_missing_params = False
|
|
if (self.re_for_parameters_see.search(doc) is not None
|
|
or self.re_param.search(doc) is None):
|
|
tolerate_missing_params = True
|
|
|
|
# Collect the function arguments.
|
|
expected_argument_names = [arg.name for arg in arguments_node.args]
|
|
not_needed_type_in_docstring = (
|
|
self.not_needed_param_in_docstring.copy())
|
|
|
|
if arguments_node.vararg is not None:
|
|
expected_argument_names.append(arguments_node.vararg)
|
|
not_needed_type_in_docstring.add(arguments_node.vararg)
|
|
if arguments_node.kwarg is not None:
|
|
expected_argument_names.append(arguments_node.kwarg)
|
|
not_needed_type_in_docstring.add(arguments_node.kwarg)
|
|
|
|
def compare_args(found_argument_names, message_id, not_needed_names):
|
|
"""Compare the found argument names with the expected ones and
|
|
generate a message if there are inconsistencies.
|
|
|
|
:param list found_argument_names: argument names found in the
|
|
docstring
|
|
|
|
:param str message_id: pylint message id
|
|
|
|
:param not_needed_names: names that may be omitted
|
|
:type not_needed_names: set of str
|
|
"""
|
|
if not tolerate_missing_params:
|
|
missing_or_differing_argument_names = (
|
|
(set(expected_argument_names) ^ set(found_argument_names))
|
|
- not_needed_names)
|
|
else:
|
|
missing_or_differing_argument_names = (
|
|
(set(found_argument_names) - set(expected_argument_names))
|
|
- not_needed_names)
|
|
|
|
if missing_or_differing_argument_names:
|
|
self.add_message(
|
|
message_id,
|
|
args=(', '.join(
|
|
sorted(missing_or_differing_argument_names)),),
|
|
node=node)
|
|
|
|
# Sphinx param declarations
|
|
found_argument_names = []
|
|
for match in re.finditer(self.re_sphinx_param_in_docstring, doc):
|
|
name = match.group(2)
|
|
found_argument_names.append(name)
|
|
if match.group(1) is not None:
|
|
not_needed_type_in_docstring.add(name)
|
|
compare_args(found_argument_names, 'missing-sphinx-param',
|
|
self.not_needed_param_in_docstring)
|
|
|
|
# Sphinx type declarations
|
|
found_argument_names = re.findall(self.re_sphinx_type_in_docstring, doc)
|
|
compare_args(found_argument_names, 'missing-sphinx-type',
|
|
not_needed_type_in_docstring)
|
|
|
|
constructor_names = set(["__init__", "__new__"])
|
|
|
|
def visit_class(self, node):
|
|
"""Called for class definitions.
|
|
|
|
:param node: Node for a class definition in the AST
|
|
:type node: :class:`astroid.scoped_nodes.Class`
|
|
"""
|
|
for body_item in node.body:
|
|
if (isinstance(body_item, astroid.scoped_nodes.Function)
|
|
and hasattr(body_item, 'name')):
|
|
if body_item.name in self.constructor_names:
|
|
self.check_arguments_in_docstring(
|
|
node, node.doc, body_item.args)
|
|
else:
|
|
self.visit_function(body_item)
|
|
|
|
|
|
def register(linter):
|
|
"""Required method to auto register this checker.
|
|
|
|
:param linter: Main interface object for Pylint plugins
|
|
:type linter: Pylint object
|
|
"""
|
|
linter.register_checker(SphinxDocChecker(linter))
|