Dear Dougal Matthews, > Are you able to share the full code? It would be best to demonstrate it as an > extension either way.
Of course, here it is, but I can also make a kind of pull request if this is more convenient for you. Viele Grüße Bruno Daniel Dr. Bruno Daniel Research and Development Blue Yonder GmbH Ohiostraße 8 D-76149 Karlsruhe Tel +49 (0)721 383 117 80 Fax +49 (0)721 383 117 69 bruno.dan...@blue-yonder.com www.blue-yonder.com Registergericht Mannheim, HRA 701753 USt-IdNr. DE 259 967 448 Geschäftsführer: Jochen Bossert, Uwe Weiss (CEO) ________________________________ From: Dougal Matthews [dou...@dougalmatthews.com] Sent: Thursday, July 24, 2014 3:15 PM To: Daniel, Bruno Cc: code-quality@python.org Subject: Re: [code-quality] Contribution: Checking function, method, and constructor parameter documentation This is great, and something I am also writing. Are you able to share the full code? It would be best to demonstrate it as an extension either way. On Thu, Jul 24, 2014 at 10:55 AM, Daniel, Bruno <bruno.dan...@blue-yonder.com<mailto:bruno.dan...@blue-yonder.com>> wrote: Dear pylint developers, We use pylint extensively in our company -- thanks this great tool! -- and wrote some plugins ourselves to aid us in our daily work. One of these plugins is a Sphinx parameter documentation checker that helps us to identify missing parameter or parameter type documentation in functions, methods and class constructors (__init__ methods). The checker class also identifies naming inconsistencies between each function or method signature and the corresponding docstring. By convention, we use Sphinx syntax for the parameter documentation (see below for examples) and we document class constructor parameters in the class docstring. We want to offer this code to you as a optional standard pylint checker. Would such an addition to pylint be welcome? To illustrate, here are our unit tests which include several examples: -snip----- test_pylint_plugin.py ---------------------------------------------- """Unittest for our pylint plugin.""" from __future__ import division, print_function import unittest from astroid import test_utils from pylint.testutils import CheckerTestCase, Message, set_config from nbpy_devel.pylint_plugin import SphinxDocChecker, NbpyChecker class SpinxDocCheckerTest(CheckerTestCase): """Tests for pylint_plugin.SphinxDocChecker""" CHECKER_CLASS = SphinxDocChecker def test_missing_func_params_in_docstring(self): """Example of a function with missing parameter documentation in the docstring """ node = test_utils.extract_node(""" def function_foo(x, y): '''docstring ... missing parameter documentation''' pass """) with self.assertAddsMessages( Message( msg_id='W9003', node=node, args=('x, y',)), Message( msg_id='W9004', node=node, args=('x, y',))): self.checker.visit_function(node) def test_missing_method_params_in_docstring(self): """Example of a class method with missing parameter documentation in the docstring """ node = test_utils.extract_node(""" class Foo(object): def method_foo(self, x, y): '''docstring ... missing parameter documentation''' pass """) method_node = node.body[0] with self.assertAddsMessages( Message( msg_id='W9003', node=method_node, args=('x, y',)), Message( msg_id='W9004', node=method_node, args=('x, y',))): self.checker.visit(node) def test_existing_func_params_in_docstring(self): """Example of a function with correctly documented parameters and return values """ node = test_utils.extract_node(""" def function_foo(xarg, yarg): '''function foo ... :param xarg: bla xarg :type xarg: int :param yarg: bla yarg :type yarg: float :return: sum :rtype: float ''' return xarg + yarg """) with self.assertNoMessages(): self.checker.visit_function(node) def test_wrong_name_of_func_params_in_docstring(self): """Example of functions with inconsistent parameter names in the signature and in the documentation """ node = test_utils.extract_node(""" def function_foo(xarg, yarg): '''function foo ... :param xarg1: bla xarg :type xarg: int :param yarg: bla yarg :type yarg1: float ''' return xarg + yarg """) with self.assertAddsMessages( Message( msg_id='W9003', node=node, args=('xarg, xarg1',)), Message( msg_id='W9004', node=node, args=('yarg, yarg1',))): self.checker.visit_function(node) node = test_utils.extract_node(""" def function_foo(xarg, yarg): '''function foo ... :param yarg1: bla yarg :type yarg1: float For the other parameters, see bla. ''' return xarg + yarg """) with self.assertAddsMessages( Message( msg_id='W9003', node=node, args=('yarg1',)), Message( msg_id='W9004', node=node, args=('yarg1',))): self.checker.visit_function(node) def test_see_sentence_for_func_params_in_docstring(self): """Example for the usage of "For the other parameters, see" to avoid too many repetitions, e.g. in functions or methods adhering to a given interface """ node = test_utils.extract_node(""" def function_foo(xarg, yarg): '''function foo ... :param yarg: bla yarg :type yarg: float For the other parameters, see :func:`bla` ''' return xarg + yarg """) with self.assertNoMessages(): self.checker.visit_function(node) def test_constr_params_in_class(self): """Example of a class with missing constructor parameter documentation Everything is completely analogous to functions. """ node = test_utils.extract_node(""" class ClassFoo(object): '''docstring foo missing constructor parameter documentation''' def __init__(self, x, y): pass """) with self.assertAddsMessages( Message( msg_id='W9003', node=node, args=('x, y',)), Message( msg_id='W9004', node=node, args=('x, y',))): self.checker.visit_class(node) if __name__ == '__main__': unittest.main() ----------------------------------------------------------------------------- Kind regards Bruno Daniel ---- Dr. Bruno Daniel Research and Development bruno.dan...@blue-yonder.com www.blue-yonder.com _______________________________________________ code-quality mailing list code-quality@python.org https://mail.python.org/mailman/listinfo/code-quality
"""Unittest for our pylint plugin.""" from __future__ import division, print_function import unittest from astroid import test_utils from pylint.testutils import CheckerTestCase, Message, set_config from pylint_plugin import SphinxDocChecker class SpinxDocCheckerTest(CheckerTestCase): """Tests for pylint_plugin.SphinxDocChecker""" CHECKER_CLASS = SphinxDocChecker def test_missing_func_params_in_docstring(self): """Example of a function with missing parameter documentation in the docstring """ node = test_utils.extract_node(""" def function_foo(x, y): '''docstring ... missing parameter documentation''' pass """) with self.assertAddsMessages( Message( msg_id='W9003', node=node, args=('x, y',)), Message( msg_id='W9004', node=node, args=('x, y',))): self.checker.visit_function(node) def test_missing_method_params_in_docstring(self): """Example of a class method with missing parameter documentation in the docstring """ node = test_utils.extract_node(""" class Foo(object): def method_foo(self, x, y): '''docstring ... missing parameter documentation''' pass """) method_node = node.body[0] with self.assertAddsMessages( Message( msg_id='W9003', node=method_node, args=('x, y',)), Message( msg_id='W9004', node=method_node, args=('x, y',))): self.checker.visit(node) def test_existing_func_params_in_docstring(self): """Example of a function with correctly documented parameters and return values """ node = test_utils.extract_node(""" def function_foo(xarg, yarg): '''function foo ... :param xarg: bla xarg :type xarg: int :param yarg: bla yarg :type yarg: float :return: sum :rtype: float ''' return xarg + yarg """) with self.assertNoMessages(): self.checker.visit_function(node) def test_wrong_name_of_func_params_in_docstring(self): """Example of functions with inconsistent parameter names in the signature and in the documentation """ node = test_utils.extract_node(""" def function_foo(xarg, yarg): '''function foo ... :param xarg1: bla xarg :type xarg: int :param yarg: bla yarg :type yarg1: float ''' return xarg + yarg """) with self.assertAddsMessages( Message( msg_id='W9003', node=node, args=('xarg, xarg1',)), Message( msg_id='W9004', node=node, args=('yarg, yarg1',))): self.checker.visit_function(node) node = test_utils.extract_node(""" def function_foo(xarg, yarg): '''function foo ... :param yarg1: bla yarg :type yarg1: float For the other parameters, see bla. ''' return xarg + yarg """) with self.assertAddsMessages( Message( msg_id='W9003', node=node, args=('yarg1',)), Message( msg_id='W9004', node=node, args=('yarg1',))): self.checker.visit_function(node) def test_see_sentence_for_func_params_in_docstring(self): """Example for the usage of "For the other parameters, see" to avoid too many repetitions, e.g. in functions or methods adhering to a given interface """ node = test_utils.extract_node(""" def function_foo(xarg, yarg): '''function foo ... :param yarg: bla yarg :type yarg: float For the other parameters, see :func:`bla` ''' return xarg + yarg """) with self.assertNoMessages(): self.checker.visit_function(node) def test_constr_params_in_class(self): """Example of a class with missing constructor parameter documentation Everything is completely analogous to functions. """ node = test_utils.extract_node(""" class ClassFoo(object): '''docstring foo missing constructor parameter documentation''' def __init__(self, x, y): pass """) with self.assertAddsMessages( Message( msg_id='W9003', node=node, args=('x, y',)), Message( msg_id='W9004', node=node, args=('x, y',))): self.checker.visit_class(node) if __name__ == '__main__': unittest.main()
"""Pylint plugin for Sphinx parameter documentation checking """ from __future__ import print_function, division import re # import pprint from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker from pylint.checkers.typecheck import TypeChecker from pylint.checkers.utils import check_messages import astroid.scoped_nodes class SphinxDocChecker(BaseChecker): """Checker for Sphinx documentation parameters Checks * that all function and method parameters are mentioned in the Sphinx params and types part of the docstring. """ __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_mentioned_in_docstring( node, node.doc, node.name, 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_sphinx_param_in_docstring = re.compile(r""" :param # Sphinx keyword \s+ # whitespace (\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_mentioned_in_docstring( self, node, doc, name, 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. :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 name: Name of the function, method or class. :type name: 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: 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) # Compare the function arguments with the ones found in the Sphinx # docstring. for message_id, pattern, not_needed in [ ('W9003', self.re_sphinx_param_in_docstring, self.not_needed_param_in_docstring), ('W9004', self.re_sphinx_type_in_docstring, not_needed_type_in_docstring), ]: found_argument_names = re.findall(pattern, doc) if not tolerate_missing_params: missing_or_differing_argument_names = ( (set(expected_argument_names) ^ set(found_argument_names)) - not_needed) else: missing_or_differing_argument_names = ( (set(found_argument_names) - set(expected_argument_names)) - not_needed) if missing_or_differing_argument_names: self.add_message( message_id, args=(', '.join( sorted(missing_or_differing_argument_names)),), node=node) 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_mentioned_in_docstring( node, node.doc, node.name, 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))
_______________________________________________ code-quality mailing list code-quality@python.org https://mail.python.org/mailman/listinfo/code-quality