mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-12-02 03:34:22 +01:00
541 lines
20 KiB
Python
541 lines
20 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# Copyright (c) 2014-2015 Bruno Daniel <bruno.daniel@blue-yonder.com>
|
||
|
# Copyright (c) 2015-2020 Claudiu Popa <pcmanticore@gmail.com>
|
||
|
# Copyright (c) 2016-2019 Ashley Whetter <ashley@awhetter.co.uk>
|
||
|
# Copyright (c) 2016 Glenn Matthews <glenn@e-dad.net>
|
||
|
# Copyright (c) 2016 Glenn Matthews <glmatthe@cisco.com>
|
||
|
# Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
|
||
|
# Copyright (c) 2017 Ville Skyttä <ville.skytta@iki.fi>
|
||
|
# Copyright (c) 2017 John Paraskevopoulos <io.paraskev@gmail.com>
|
||
|
# Copyright (c) 2018, 2020 Anthony Sottile <asottile@umich.edu>
|
||
|
# Copyright (c) 2018 Jim Robertson <jrobertson98atx@gmail.com>
|
||
|
# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
|
||
|
# Copyright (c) 2018 Adam Dangoor <adamdangoor@gmail.com>
|
||
|
# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
|
||
|
# Copyright (c) 2019 Pierre Sassoulas <pierre.sassoulas@gmail.com>
|
||
|
# Copyright (c) 2020 Damien Baty <damien.baty@polyconseil.fr>
|
||
|
|
||
|
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||
|
# For details: https://github.com/PyCQA/pylint/blob/master/COPYING
|
||
|
|
||
|
"""Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings
|
||
|
"""
|
||
|
import astroid
|
||
|
|
||
|
from pylint.checkers import BaseChecker
|
||
|
from pylint.checkers import utils as checker_utils
|
||
|
from pylint.extensions import _check_docs_utils as utils
|
||
|
from pylint.interfaces import IAstroidChecker
|
||
|
|
||
|
|
||
|
class DocstringParameterChecker(BaseChecker):
|
||
|
"""Checker for Sphinx, Google, or Numpy style docstrings
|
||
|
|
||
|
* Check that all function, method and constructor parameters are mentioned
|
||
|
in the params and types part of the docstring. Constructor parameters
|
||
|
can be documented in either the class docstring or ``__init__`` docstring,
|
||
|
but not both.
|
||
|
* 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.
|
||
|
* Check that all explicitly raised exceptions in a function are documented
|
||
|
in the function docstring. Caught exceptions are ignored.
|
||
|
|
||
|
Activate this checker by adding the line::
|
||
|
|
||
|
load-plugins=pylint.extensions.docparams
|
||
|
|
||
|
to the ``MASTER`` section of your ``.pylintrc``.
|
||
|
|
||
|
:param linter: linter object
|
||
|
:type linter: :class:`pylint.lint.PyLinter`
|
||
|
"""
|
||
|
|
||
|
__implements__ = IAstroidChecker
|
||
|
|
||
|
name = "parameter_documentation"
|
||
|
msgs = {
|
||
|
"W9005": (
|
||
|
'"%s" has constructor parameters documented in class and __init__',
|
||
|
"multiple-constructor-doc",
|
||
|
"Please remove parameter declarations in the class or constructor.",
|
||
|
),
|
||
|
"W9006": (
|
||
|
'"%s" not documented as being raised',
|
||
|
"missing-raises-doc",
|
||
|
"Please document exceptions for all raised exception types.",
|
||
|
),
|
||
|
"W9008": (
|
||
|
"Redundant returns documentation",
|
||
|
"redundant-returns-doc",
|
||
|
"Please remove the return/rtype documentation from this method.",
|
||
|
),
|
||
|
"W9010": (
|
||
|
"Redundant yields documentation",
|
||
|
"redundant-yields-doc",
|
||
|
"Please remove the yields documentation from this method.",
|
||
|
),
|
||
|
"W9011": (
|
||
|
"Missing return documentation",
|
||
|
"missing-return-doc",
|
||
|
"Please add documentation about what this method returns.",
|
||
|
{"old_names": [("W9007", "old-missing-returns-doc")]},
|
||
|
),
|
||
|
"W9012": (
|
||
|
"Missing return type documentation",
|
||
|
"missing-return-type-doc",
|
||
|
"Please document the type returned by this method.",
|
||
|
# we can't use the same old_name for two different warnings
|
||
|
# {'old_names': [('W9007', 'missing-returns-doc')]},
|
||
|
),
|
||
|
"W9013": (
|
||
|
"Missing yield documentation",
|
||
|
"missing-yield-doc",
|
||
|
"Please add documentation about what this generator yields.",
|
||
|
{"old_names": [("W9009", "old-missing-yields-doc")]},
|
||
|
),
|
||
|
"W9014": (
|
||
|
"Missing yield type documentation",
|
||
|
"missing-yield-type-doc",
|
||
|
"Please document the type yielded by this method.",
|
||
|
# we can't use the same old_name for two different warnings
|
||
|
# {'old_names': [('W9009', 'missing-yields-doc')]},
|
||
|
),
|
||
|
"W9015": (
|
||
|
'"%s" missing in parameter documentation',
|
||
|
"missing-param-doc",
|
||
|
"Please add parameter declarations for all parameters.",
|
||
|
{"old_names": [("W9003", "old-missing-param-doc")]},
|
||
|
),
|
||
|
"W9016": (
|
||
|
'"%s" missing in parameter type documentation',
|
||
|
"missing-type-doc",
|
||
|
"Please add parameter type declarations for all parameters.",
|
||
|
{"old_names": [("W9004", "old-missing-type-doc")]},
|
||
|
),
|
||
|
"W9017": (
|
||
|
'"%s" differing in parameter documentation',
|
||
|
"differing-param-doc",
|
||
|
"Please check parameter names in declarations.",
|
||
|
),
|
||
|
"W9018": (
|
||
|
'"%s" differing in parameter type documentation',
|
||
|
"differing-type-doc",
|
||
|
"Please check parameter names in type declarations.",
|
||
|
),
|
||
|
}
|
||
|
|
||
|
options = (
|
||
|
(
|
||
|
"accept-no-param-doc",
|
||
|
{
|
||
|
"default": True,
|
||
|
"type": "yn",
|
||
|
"metavar": "<y or n>",
|
||
|
"help": "Whether to accept totally missing parameter "
|
||
|
"documentation in the docstring of a function that has "
|
||
|
"parameters.",
|
||
|
},
|
||
|
),
|
||
|
(
|
||
|
"accept-no-raise-doc",
|
||
|
{
|
||
|
"default": True,
|
||
|
"type": "yn",
|
||
|
"metavar": "<y or n>",
|
||
|
"help": "Whether to accept totally missing raises "
|
||
|
"documentation in the docstring of a function that "
|
||
|
"raises an exception.",
|
||
|
},
|
||
|
),
|
||
|
(
|
||
|
"accept-no-return-doc",
|
||
|
{
|
||
|
"default": True,
|
||
|
"type": "yn",
|
||
|
"metavar": "<y or n>",
|
||
|
"help": "Whether to accept totally missing return "
|
||
|
"documentation in the docstring of a function that "
|
||
|
"returns a statement.",
|
||
|
},
|
||
|
),
|
||
|
(
|
||
|
"accept-no-yields-doc",
|
||
|
{
|
||
|
"default": True,
|
||
|
"type": "yn",
|
||
|
"metavar": "<y or n>",
|
||
|
"help": "Whether to accept totally missing yields "
|
||
|
"documentation in the docstring of a generator.",
|
||
|
},
|
||
|
),
|
||
|
(
|
||
|
"default-docstring-type",
|
||
|
{
|
||
|
"type": "choice",
|
||
|
"default": "default",
|
||
|
"choices": list(utils.DOCSTRING_TYPES),
|
||
|
"help": "If the docstring type cannot be guessed "
|
||
|
"the specified docstring type will be used.",
|
||
|
},
|
||
|
),
|
||
|
)
|
||
|
|
||
|
priority = -2
|
||
|
|
||
|
constructor_names = {"__init__", "__new__"}
|
||
|
not_needed_param_in_docstring = {"self", "cls"}
|
||
|
|
||
|
def visit_functiondef(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`
|
||
|
"""
|
||
|
node_doc = utils.docstringify(node.doc, self.config.default_docstring_type)
|
||
|
self.check_functiondef_params(node, node_doc)
|
||
|
self.check_functiondef_returns(node, node_doc)
|
||
|
self.check_functiondef_yields(node, node_doc)
|
||
|
|
||
|
def check_functiondef_params(self, node, node_doc):
|
||
|
node_allow_no_param = None
|
||
|
if node.name in self.constructor_names:
|
||
|
class_node = checker_utils.node_frame_class(node)
|
||
|
if class_node is not None:
|
||
|
class_doc = utils.docstringify(
|
||
|
class_node.doc, self.config.default_docstring_type
|
||
|
)
|
||
|
self.check_single_constructor_params(class_doc, node_doc, class_node)
|
||
|
|
||
|
# __init__ or class docstrings can have no parameters documented
|
||
|
# as long as the other documents them.
|
||
|
node_allow_no_param = (
|
||
|
class_doc.has_params()
|
||
|
or class_doc.params_documented_elsewhere()
|
||
|
or None
|
||
|
)
|
||
|
class_allow_no_param = (
|
||
|
node_doc.has_params()
|
||
|
or node_doc.params_documented_elsewhere()
|
||
|
or None
|
||
|
)
|
||
|
|
||
|
self.check_arguments_in_docstring(
|
||
|
class_doc, node.args, class_node, class_allow_no_param
|
||
|
)
|
||
|
|
||
|
self.check_arguments_in_docstring(
|
||
|
node_doc, node.args, node, node_allow_no_param
|
||
|
)
|
||
|
|
||
|
def check_functiondef_returns(self, node, node_doc):
|
||
|
if (not node_doc.supports_yields and node.is_generator()) or node.is_abstract():
|
||
|
return
|
||
|
|
||
|
return_nodes = node.nodes_of_class(astroid.Return)
|
||
|
if (node_doc.has_returns() or node_doc.has_rtype()) and not any(
|
||
|
utils.returns_something(ret_node) for ret_node in return_nodes
|
||
|
):
|
||
|
self.add_message("redundant-returns-doc", node=node)
|
||
|
|
||
|
def check_functiondef_yields(self, node, node_doc):
|
||
|
if not node_doc.supports_yields or node.is_abstract():
|
||
|
return
|
||
|
|
||
|
if (
|
||
|
node_doc.has_yields() or node_doc.has_yields_type()
|
||
|
) and not node.is_generator():
|
||
|
self.add_message("redundant-yields-doc", node=node)
|
||
|
|
||
|
def visit_raise(self, node):
|
||
|
func_node = node.frame()
|
||
|
if not isinstance(func_node, astroid.FunctionDef):
|
||
|
return
|
||
|
|
||
|
expected_excs = utils.possible_exc_types(node)
|
||
|
|
||
|
if not expected_excs:
|
||
|
return
|
||
|
|
||
|
if not func_node.doc:
|
||
|
# If this is a property setter,
|
||
|
# the property should have the docstring instead.
|
||
|
property_ = utils.get_setters_property(func_node)
|
||
|
if property_:
|
||
|
func_node = property_
|
||
|
|
||
|
doc = utils.docstringify(func_node.doc, self.config.default_docstring_type)
|
||
|
if not doc.is_valid():
|
||
|
if doc.doc:
|
||
|
self._handle_no_raise_doc(expected_excs, func_node)
|
||
|
return
|
||
|
|
||
|
found_excs_full_names = doc.exceptions()
|
||
|
|
||
|
# Extract just the class name, e.g. "error" from "re.error"
|
||
|
found_excs_class_names = {exc.split(".")[-1] for exc in found_excs_full_names}
|
||
|
missing_excs = expected_excs - found_excs_class_names
|
||
|
self._add_raise_message(missing_excs, func_node)
|
||
|
|
||
|
def visit_return(self, node):
|
||
|
if not utils.returns_something(node):
|
||
|
return
|
||
|
|
||
|
func_node = node.frame()
|
||
|
if not isinstance(func_node, astroid.FunctionDef):
|
||
|
return
|
||
|
|
||
|
doc = utils.docstringify(func_node.doc, self.config.default_docstring_type)
|
||
|
if not doc.is_valid() and self.config.accept_no_return_doc:
|
||
|
return
|
||
|
|
||
|
is_property = checker_utils.decorated_with_property(func_node)
|
||
|
|
||
|
if not (doc.has_returns() or (doc.has_property_returns() and is_property)):
|
||
|
self.add_message("missing-return-doc", node=func_node)
|
||
|
|
||
|
if func_node.returns:
|
||
|
return
|
||
|
|
||
|
if not (doc.has_rtype() or (doc.has_property_type() and is_property)):
|
||
|
self.add_message("missing-return-type-doc", node=func_node)
|
||
|
|
||
|
def visit_yield(self, node):
|
||
|
func_node = node.frame()
|
||
|
if not isinstance(func_node, astroid.FunctionDef):
|
||
|
return
|
||
|
|
||
|
doc = utils.docstringify(func_node.doc, self.config.default_docstring_type)
|
||
|
if not doc.is_valid() and self.config.accept_no_yields_doc:
|
||
|
return
|
||
|
|
||
|
if doc.supports_yields:
|
||
|
doc_has_yields = doc.has_yields()
|
||
|
doc_has_yields_type = doc.has_yields_type()
|
||
|
else:
|
||
|
doc_has_yields = doc.has_returns()
|
||
|
doc_has_yields_type = doc.has_rtype()
|
||
|
|
||
|
if not doc_has_yields:
|
||
|
self.add_message("missing-yield-doc", node=func_node)
|
||
|
|
||
|
if not (doc_has_yields_type or func_node.returns):
|
||
|
self.add_message("missing-yield-type-doc", node=func_node)
|
||
|
|
||
|
def visit_yieldfrom(self, node):
|
||
|
self.visit_yield(node)
|
||
|
|
||
|
def _compare_missing_args(
|
||
|
self,
|
||
|
found_argument_names,
|
||
|
message_id,
|
||
|
not_needed_names,
|
||
|
expected_argument_names,
|
||
|
warning_node,
|
||
|
):
|
||
|
"""Compare the found argument names with the expected ones and
|
||
|
generate a message if there are arguments missing.
|
||
|
|
||
|
:param set 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
|
||
|
|
||
|
:param set expected_argument_names: Expected argument names
|
||
|
:param NodeNG warning_node: The node to be analyzed
|
||
|
"""
|
||
|
missing_argument_names = (
|
||
|
expected_argument_names - found_argument_names
|
||
|
) - not_needed_names
|
||
|
if missing_argument_names:
|
||
|
self.add_message(
|
||
|
message_id,
|
||
|
args=(", ".join(sorted(missing_argument_names)),),
|
||
|
node=warning_node,
|
||
|
)
|
||
|
|
||
|
def _compare_different_args(
|
||
|
self,
|
||
|
found_argument_names,
|
||
|
message_id,
|
||
|
not_needed_names,
|
||
|
expected_argument_names,
|
||
|
warning_node,
|
||
|
):
|
||
|
"""Compare the found argument names with the expected ones and
|
||
|
generate a message if there are extra arguments found.
|
||
|
|
||
|
:param set 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
|
||
|
|
||
|
:param set expected_argument_names: Expected argument names
|
||
|
:param NodeNG warning_node: The node to be analyzed
|
||
|
"""
|
||
|
differing_argument_names = (
|
||
|
(expected_argument_names ^ found_argument_names)
|
||
|
- not_needed_names
|
||
|
- expected_argument_names
|
||
|
)
|
||
|
|
||
|
if differing_argument_names:
|
||
|
self.add_message(
|
||
|
message_id,
|
||
|
args=(", ".join(sorted(differing_argument_names)),),
|
||
|
node=warning_node,
|
||
|
)
|
||
|
|
||
|
def check_arguments_in_docstring(
|
||
|
self, doc, arguments_node, warning_node, accept_no_param_doc=None
|
||
|
):
|
||
|
"""Check that all parameters in a function, method or class constructor
|
||
|
on the one hand and the parameters mentioned in the parameter
|
||
|
documentation (e.g. 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 parameter documentation that don't or no
|
||
|
longer exist in the function parameter list are noticed.
|
||
|
* If the text "For the parameters, see" or "For the other parameters,
|
||
|
see" (ignoring additional whitespace) is mentioned in the docstring,
|
||
|
missing parameter documentation is tolerated.
|
||
|
* If there's no Sphinx style, Google style or NumPy style parameter
|
||
|
documentation at all, i.e. ``:param`` is never mentioned etc., the
|
||
|
checker assumes that the parameters are documented in another format
|
||
|
and the absence is tolerated.
|
||
|
|
||
|
:param doc: Docstring for the function, method or class.
|
||
|
:type doc: :class:`Docstring`
|
||
|
|
||
|
:param arguments_node: Arguments node for the function, method or
|
||
|
class constructor.
|
||
|
:type arguments_node: :class:`astroid.scoped_nodes.Arguments`
|
||
|
|
||
|
:param warning_node: The node to assign the warnings to
|
||
|
:type warning_node: :class:`astroid.scoped_nodes.Node`
|
||
|
|
||
|
:param accept_no_param_doc: Whether or not to allow no parameters
|
||
|
to be documented.
|
||
|
If None then this value is read from the configuration.
|
||
|
:type accept_no_param_doc: bool or None
|
||
|
"""
|
||
|
# Tolerate missing param or type declarations if there is a link to
|
||
|
# another method carrying the same name.
|
||
|
if not doc.doc:
|
||
|
return
|
||
|
|
||
|
if accept_no_param_doc is None:
|
||
|
accept_no_param_doc = self.config.accept_no_param_doc
|
||
|
tolerate_missing_params = doc.params_documented_elsewhere()
|
||
|
|
||
|
# Collect the function arguments.
|
||
|
expected_argument_names = {arg.name for arg in arguments_node.args}
|
||
|
expected_argument_names.update(arg.name for arg in arguments_node.kwonlyargs)
|
||
|
not_needed_type_in_docstring = self.not_needed_param_in_docstring.copy()
|
||
|
|
||
|
if arguments_node.vararg is not None:
|
||
|
expected_argument_names.add(arguments_node.vararg)
|
||
|
not_needed_type_in_docstring.add(arguments_node.vararg)
|
||
|
if arguments_node.kwarg is not None:
|
||
|
expected_argument_names.add(arguments_node.kwarg)
|
||
|
not_needed_type_in_docstring.add(arguments_node.kwarg)
|
||
|
params_with_doc, params_with_type = doc.match_param_docs()
|
||
|
|
||
|
# Tolerate no parameter documentation at all.
|
||
|
if not params_with_doc and not params_with_type and accept_no_param_doc:
|
||
|
tolerate_missing_params = True
|
||
|
|
||
|
if not tolerate_missing_params:
|
||
|
self._compare_missing_args(
|
||
|
params_with_doc,
|
||
|
"missing-param-doc",
|
||
|
self.not_needed_param_in_docstring,
|
||
|
expected_argument_names,
|
||
|
warning_node,
|
||
|
)
|
||
|
|
||
|
for index, arg_name in enumerate(arguments_node.args):
|
||
|
if arguments_node.annotations[index]:
|
||
|
params_with_type.add(arg_name.name)
|
||
|
for index, arg_name in enumerate(arguments_node.kwonlyargs):
|
||
|
if arguments_node.kwonlyargs_annotations[index]:
|
||
|
params_with_type.add(arg_name.name)
|
||
|
|
||
|
if not tolerate_missing_params:
|
||
|
self._compare_missing_args(
|
||
|
params_with_type,
|
||
|
"missing-type-doc",
|
||
|
not_needed_type_in_docstring,
|
||
|
expected_argument_names,
|
||
|
warning_node,
|
||
|
)
|
||
|
|
||
|
self._compare_different_args(
|
||
|
params_with_doc,
|
||
|
"differing-param-doc",
|
||
|
self.not_needed_param_in_docstring,
|
||
|
expected_argument_names,
|
||
|
warning_node,
|
||
|
)
|
||
|
self._compare_different_args(
|
||
|
params_with_type,
|
||
|
"differing-type-doc",
|
||
|
not_needed_type_in_docstring,
|
||
|
expected_argument_names,
|
||
|
warning_node,
|
||
|
)
|
||
|
|
||
|
def check_single_constructor_params(self, class_doc, init_doc, class_node):
|
||
|
if class_doc.has_params() and init_doc.has_params():
|
||
|
self.add_message(
|
||
|
"multiple-constructor-doc", args=(class_node.name,), node=class_node
|
||
|
)
|
||
|
|
||
|
def _handle_no_raise_doc(self, excs, node):
|
||
|
if self.config.accept_no_raise_doc:
|
||
|
return
|
||
|
|
||
|
self._add_raise_message(excs, node)
|
||
|
|
||
|
def _add_raise_message(self, missing_excs, node):
|
||
|
"""
|
||
|
Adds a message on :param:`node` for the missing exception type.
|
||
|
|
||
|
:param missing_excs: A list of missing exception types.
|
||
|
:type missing_excs: set(str)
|
||
|
|
||
|
:param node: The node show the message on.
|
||
|
:type node: astroid.node_classes.NodeNG
|
||
|
"""
|
||
|
if node.is_abstract():
|
||
|
try:
|
||
|
missing_excs.remove("NotImplementedError")
|
||
|
except KeyError:
|
||
|
pass
|
||
|
|
||
|
if not missing_excs:
|
||
|
return
|
||
|
|
||
|
self.add_message(
|
||
|
"missing-raises-doc", args=(", ".join(sorted(missing_excs)),), node=node
|
||
|
)
|
||
|
|
||
|
|
||
|
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(DocstringParameterChecker(linter))
|