diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CONTRIBUTORS.txt | 3 | ||||
-rw-r--r-- | doc/Makefile | 20 | ||||
-rw-r--r-- | doc/conf.py | 2 | ||||
-rw-r--r-- | doc/extensions.rst | 215 | ||||
-rwxr-xr-x | doc/exts/pylint_extensions.py | 113 | ||||
-rwxr-xr-x[-rw-r--r--] | doc/exts/pylint_features.py | 29 | ||||
-rw-r--r-- | doc/generated_features.rst | 5 | ||||
-rw-r--r-- | doc/index.rst | 2 | ||||
-rw-r--r-- | doc/whatsnew/1.6.rst | 2 | ||||
-rw-r--r-- | pylint/extensions/bad_builtin.rst | 9 | ||||
-rw-r--r-- | pylint/extensions/check_elif.py | 2 | ||||
-rw-r--r-- | pylint/extensions/docparams.py | 14 | ||||
-rw-r--r-- | pylint/extensions/docparams.rst | 152 | ||||
-rw-r--r-- | pylint/extensions/mccabe.rst | 37 | ||||
-rw-r--r-- | pylint/extensions/redefined_variable_type.py | 1 | ||||
-rw-r--r-- | pylint/test/unittest_lint.py | 17 | ||||
-rw-r--r-- | pylint/utils.py | 155 |
18 files changed, 466 insertions, 313 deletions
diff --git a/.gitignore b/.gitignore index cb8281678..9d9ec161c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /pylint.egg-info/ .tox *.sw[a-z] +doc/extensions.rst doc/features.rst pyve build-stamp diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 89cebdd17..918140ab0 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -99,4 +99,5 @@ Order doesn't matter (not that much, at least ;) * Yannick Brehon: contributor. -* Glenn Matthews: bug reports, occasional bug fixes +* Glenn Matthews: bug reports, autogenerated documentation for optional + extensions, occasional bug fixes diff --git a/doc/Makefile b/doc/Makefile index 2cd7f7e5e..c33d15447 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -37,8 +37,9 @@ help: clean: -rm -rf $(BUILDDIR)/* -rm -f features.rst + -rm -f extensions.rst -html: features +html: features.rst extensions.rst $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @@ -131,15 +132,18 @@ doctest: @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." +extensions.rst: exts/pylint_extensions.py +extensions.rst: ../pylint/utils.py +extensions.rst: $(shell find ../pylint/extensions -type f -regex '.*\.py') +extensions.rst: $(shell find ../pylint/extensions -type f -regex '.*\.rst') + rm -f extensions.rst + PYTHONPATH=$(PYTHONPATH) ./exts/pylint_extensions.py -features: +features.rst: exts/pylint_features.py +features.rst: ../pylint/utils.py +features.rst: $(shell find ../pylint/checkers -type f -regex '.*\.py') rm -f features.rst - echo "Pylint features" > features.rst - echo "===============" >> features.rst - echo "" >> features.rst - echo ".. generated by pylint --full-documentation" >> features.rst - echo "" >> features.rst - PYTHONPATH=$(PYTHONPATH) pylint --full-documentation >> features.rst + PYTHONPATH=$(PYTHONPATH) ./exts/pylint_features.py gen-examples: chmod u+w ../examples/pylintrc diff --git a/doc/conf.py b/doc/conf.py index 38bb95ce2..c0b374a8d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -25,7 +25,7 @@ sys.path.append(os.path.abspath('exts')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['pylint_features'] +extensions = ['pylint_features', 'pylint_extensions'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/extensions.rst b/doc/extensions.rst deleted file mode 100644 index 8fe4241e9..000000000 --- a/doc/extensions.rst +++ /dev/null @@ -1,215 +0,0 @@ - -Optional Pylint checkers in the extensions module -================================================= - -Parameter documentation checker -------------------------------- - -If you document the parameters of your functions, methods and constructors and -their types systematically in your code this optional component might -be useful for you. Sphinx style, Google style, and Numpy style are supported. -(For some examples, see https://pypi.python.org/pypi/sphinxcontrib-napoleon .) - -You can activate this checker by adding the line:: - - load-plugins=pylint.extensions.docparams - -to the ``MASTER`` section of your ``.pylintrc``. - -This checker verifies that all function, method, and constructor parameters are -mentioned in the - -* Sphinx ``param`` and ``type`` parts of the docstring:: - - def function_foo(x, y, z): - '''function foo ... - - :param x: bla x - :type x: int - - :param y: bla y - :type y: float - - :param int z: bla z - - :return: sum - :rtype: float - ''' - return x + y + z - -* or the Google style ``Args:`` part of the docstring:: - - def function_foo(x, y, z): - '''function foo ... - - Args: - x (int): bla x - y (float): bla y - - z (int): bla z - - Returns: - float: sum - ''' - return x + y + z - -* or the Numpy style ``Parameters`` part of the docstring:: - - def function_foo(x, y, z): - '''function foo ... - - Parameters - ---------- - x: int - bla x - y: float - bla y - - z: int - bla z - - Returns - ------- - float - sum - ''' - return x + y + z - - -You'll be notified of **missing parameter documentation** but also of -**naming inconsistencies** between the signature and the documentation which -often arise when parameters are renamed automatically in the code, but not in -the documentation. - -By convention, constructor parameters are documented in the class docstring. -(``__init__`` and ``__new__`` methods are considered constructors.):: - - class ClassFoo(object): - '''Sphinx style docstring foo - - :param float x: bla x - - :param y: bla y - :type y: int - ''' - def __init__(self, x, y): - pass - - class ClassFoo(object): - '''Google style docstring foo - - Args: - x (float): bla x - y (int): bla y - ''' - def __init__(self, x, y): - pass - -In some cases, having to document all parameters is a nuisance, for instance if -many of your functions or methods just follow a **common interface**. To remove -this burden, the checker accepts missing parameter documentation if one of the -following phrases is found in the docstring: - -* For the other parameters, see -* For the parameters, see - -(with arbitrary whitespace between the words). Please add a link to the -docstring defining the interface, e.g. a superclass method, after "see":: - - def callback(x, y, z): - '''Sphinx style docstring for callback ... - - :param x: bla x - :type x: int - - For the other parameters, see - :class:`MyFrameworkUsingAndDefiningCallback` - ''' - return x + y + z - - def callback(x, y, z): - '''Google style docstring for callback ... - - Args: - x (int): bla x - - For the other parameters, see - :class:`MyFrameworkUsingAndDefiningCallback` - ''' - return x + y + z - -Naming inconsistencies in existing parameter and their type documentations are -still detected. - -By default, omitting the parameter documentation of a function altogether is -tolerated without any warnings. If you want to switch off this behavior -(forcing functions to document their parameters), set the option -``accept-no-param-doc`` to ``no`` in your ``.pylintrc``. - -By default, omitting the exception raising documentation of a function -altogether is tolerated without any warnings. If you want to switch off this -behavior (forcing functions that raise exceptions to document them), set the -option ``accept-no-raise-doc`` to ``no`` in your ``.pylintrc``. - -By default, omitting the return documentation of a function altogether is -tolerated without any warnings. If you want to switch off this behavior -(forcing functions to document their returns), set the option -``accept-no-return-doc`` to ``no`` in your ``.pylintrc``. - - -Prohibit builtin checker ------------------------- - -This used to be the ``bad-builtin`` core checker, but it was moved to -an extension instead. It can be used for finding prohibited used builtins, -such as ``map`` or ``filter``, for which other alternatives exists. - -If you want to control for what builtins the checker should warn about, -you can use the ``bad-functions`` option:: - - $ pylint a.py --load-plugins=pylint.extensions.bad_builtin --bad-functions=apply,reduce - ... - - -.. _mccabe_extension: - -Complexity checker ------------------- - -You can now use this plugin for finding complexity issues in your code base. - -Activate it through ``pylint --load-plugins=pylint.extensions.mccabe``. It introduces -a new warning, ``too-complex``, which is emitted when a code block has a complexity -higher than a preestablished value, which can be controlled through the -``max-complexity`` option, such as in this example:: - - $ cat a.py - def f10(): - """McCabe rating: 11""" - myint = 2 - if myint == 5: - return myint - elif myint == 6: - return myint - elif myint == 7: - return myint - elif myint == 8: - return myint - elif myint == 9: - return myint - elif myint == 10: - if myint == 8: - while True: - return True - elif myint == 8: - with myint: - return 8 - else: - if myint == 2: - return myint - return myint - return myint - $ pylint a.py --load-plugins=pylint.extensions.mccabe - R:1: 'f10' is too complex. The McCabe rating is 11 (too-complex) - $ pylint a.py --load-plugins=pylint.extensions.mccabe --max-complexity=50 - $ diff --git a/doc/exts/pylint_extensions.py b/doc/exts/pylint_extensions.py new file mode 100755 index 000000000..e1d4f67ff --- /dev/null +++ b/doc/exts/pylint_extensions.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# 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 + +"""Script used to generate the extensions file before building the actual documentation.""" + +import os +import re +import sys + +import pkg_resources +import six +import sphinx + +from pylint.lint import PyLinter + + +# Some modules have been renamed and deprecated under their old names. +# Skip documenting these modules since: +# 1) They are deprecated, why document them moving forward? +# 2) We can't load the deprecated module and the newly renamed module at the +# same time without getting naming conflicts +DEPRECATED_MODULES = [ + 'check_docs', # ==> docparams +] + +def builder_inited(app): + """Output full documentation in ReST format for all extension modules""" + # PACKAGE/docs/exts/pylint_extensions.py --> PACKAGE/ + base_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + # PACKAGE/ --> PACKAGE/pylint/extensions + ext_path = os.path.join(base_path, 'pylint', 'extensions') + modules = [] + doc_files = {} + for filename in os.listdir(ext_path): + name, ext = os.path.splitext(filename) + if name[0] == '_' or name in DEPRECATED_MODULES: + continue + if ext == '.py': + modules.append('pylint.extensions.%s' % name) + elif ext == '.rst': + doc_files['pylint.extensions.' + name] = os.path.join(ext_path, + filename) + if not modules: + sys.exit("No Pylint extensions found?") + + linter = PyLinter() + linter.load_plugin_modules(modules) + + extensions_doc = os.path.join(base_path, 'doc', 'extensions.rst') + with open(extensions_doc, 'w') as stream: + stream.write("Optional Pylint checkers in the extensions module\n") + stream.write("=================================================\n\n") + stream.write("Pylint provides the following optional plugins:\n\n") + for module in modules: + stream.write("- :ref:`{0}`\n".format(module)) + stream.write("\n") + stream.write("You can activate any or all of these extensions " + "by adding a ``load-plugins`` line to the ``MASTER`` " + "section of your ``.pylintrc``, for example::\n") + stream.write("\n load-plugins=pylint.extensions.docparams," + "pylint.extensions.docstyle\n\n") + by_module = get_plugins_info(linter, doc_files) + for module, info in sorted(six.iteritems(by_module)): + linter._print_checker_doc(info['name'], info, stream=stream) + + +def get_plugins_info(linter, doc_files): + by_module = {} + + for checker in linter.get_checkers(): + if checker.name == 'master': + continue + module = checker.__module__ + # Plugins only - skip over core checkers + if re.match("pylint.checkers", module): + continue + + # Find any .rst documentation associated with this plugin + doc = "" + doc_file = doc_files.get(module) + if doc_file: + with open(doc_file, 'r') as f: + doc = f.read() + + try: + by_module[module]['options'] += checker.options_and_values() + by_module[module]['msgs'].update(checker.msgs) + by_module[module]['reports'] += checker.reports + by_module[module]['doc'] += doc + by_module[module]['name'] += checker.name + by_module[module]['module'] += module + except KeyError: + by_module[module] = { + 'options': list(checker.options_and_values()), + 'msgs': dict(checker.msgs), + 'reports': list(checker.reports), + 'doc': doc, + 'name': checker.name, + 'module': module, + } + + return by_module + + +def setup(app): + app.connect('builder-inited', builder_inited) + return {'version': sphinx.__display_version__} + + +if __name__ == "__main__": + builder_inited(None) diff --git a/doc/exts/pylint_features.py b/doc/exts/pylint_features.py index 30207f5bb..b81d1e4b6 100644..100755 --- a/doc/exts/pylint_features.py +++ b/doc/exts/pylint_features.py @@ -1,31 +1,34 @@ +#!/usr/bin/env python # 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 """Script used to generate the features file before building the actual documentation.""" import os -import subprocess import sys import sphinx +from pylint.lint import PyLinter def builder_inited(app): - popen = subprocess.Popen([sys.executable, "-m", "pylint", "--full-documentation"], - stdout=subprocess.PIPE) - output, _ = popen.communicate() + # PACKAGE/docs/exts/pylint_extensions.py --> PACKAGE/ + base_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + linter = PyLinter() + linter.load_default_plugins() - if not output: - print("Pylint might not be available.") - return - - features = os.path.join(os.path.dirname('.'), 'features.rst') - with open(features, 'wb') as stream: - stream.write(b"Pylint features\n") - stream.write(b"===============\n\n") - stream.write(output) + features = os.path.join(base_path, 'doc', 'features.rst') + with open(features, 'w') as stream: + stream.write("Pylint features\n") + stream.write("===============\n\n") + stream.write(".. generated by pylint --full-documentation\n\n") + linter.print_full_documentation(stream) def setup(app): app.connect('builder-inited', builder_inited) return {'version': sphinx.__display_version__} + +if __name__ == "__main__": + builder_inited(None) diff --git a/doc/generated_features.rst b/doc/generated_features.rst deleted file mode 100644 index 8ab6b9c88..000000000 --- a/doc/generated_features.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. toctree:: - :titlesonly: - :maxdepth: 1 - - features
\ No newline at end of file diff --git a/doc/index.rst b/doc/index.rst index 0d677c47b..6cbc3d1c2 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,7 +15,7 @@ Pylint User Manual run output message-control - generated_features + features extensions options ide-integration diff --git a/doc/whatsnew/1.6.rst b/doc/whatsnew/1.6.rst index fff378b8d..3c543d1ea 100644 --- a/doc/whatsnew/1.6.rst +++ b/doc/whatsnew/1.6.rst @@ -57,7 +57,7 @@ New checkers $ pylint module_or_project --load-plugins=pylint.extensions.mccabe - See more at :ref:`mccabe_extension` + See more at :ref:`pylint.extensions.mccabe` New features diff --git a/pylint/extensions/bad_builtin.rst b/pylint/extensions/bad_builtin.rst new file mode 100644 index 000000000..e87e6843c --- /dev/null +++ b/pylint/extensions/bad_builtin.rst @@ -0,0 +1,9 @@ +This used to be the ``bad-builtin`` core checker, but it was moved to +an extension instead. It can be used for finding prohibited used builtins, +such as ``map`` or ``filter``, for which other alternatives exists. + +If you want to control for what builtins the checker should warn about, +you can use the ``bad-functions`` option:: + + $ pylint a.py --load-plugins=pylint.extensions.bad_builtin --bad-functions=apply,reduce + ... diff --git a/pylint/extensions/check_elif.py b/pylint/extensions/check_elif.py index 341710adc..79083aae7 100644 --- a/pylint/extensions/check_elif.py +++ b/pylint/extensions/check_elif.py @@ -12,7 +12,7 @@ class ElseifUsedChecker(BaseTokenChecker): """ __implements__ = (ITokenChecker, IAstroidChecker) - name = 'elseifused' + name = 'else_if_used' msgs = {'R5501': ('Consider using "elif" instead of "else if"', 'else-if-used', 'Used when an else statement is immediately followed by ' diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 0c545bd44..f3da7fe1e 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -29,7 +29,7 @@ class DocstringParameterChecker(BaseChecker): Activate this checker by adding the line:: - load-plugins=pylint.extensions.check_docs + load-plugins=pylint.extensions.docparams to the ``MASTER`` section of your ``.pylintrc``. @@ -38,7 +38,7 @@ class DocstringParameterChecker(BaseChecker): """ __implements__ = IAstroidChecker - name = 'docstring_params' + name = 'parameter_documentation' msgs = { 'W9003': ('"%s" missing or differing in parameter documentation', 'missing-param-doc', @@ -74,19 +74,19 @@ class DocstringParameterChecker(BaseChecker): }), ('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' + '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' + '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' + 'help': 'Whether to accept totally missing yields ' 'documentation in the docstring of a generator.' }), ) diff --git a/pylint/extensions/docparams.rst b/pylint/extensions/docparams.rst new file mode 100644 index 000000000..e46c4dcd7 --- /dev/null +++ b/pylint/extensions/docparams.rst @@ -0,0 +1,152 @@ +If you document the parameters of your functions, methods and constructors and +their types systematically in your code this optional component might +be useful for you. Sphinx style, Google style, and Numpy style are supported. +(For some examples, see https://pypi.python.org/pypi/sphinxcontrib-napoleon .) + +You can activate this checker by adding the line:: + + load-plugins=pylint.extensions.docparams + +to the ``MASTER`` section of your ``.pylintrc``. + +This checker verifies that all function, method, and constructor docstrings +include documentation of the + +* parameters and their types +* return value and its type +* exceptions raised + +and can handle docstrings in + +* Sphinx style (``param``, ``type``, ``return``, ``rtype``, + ``raise`` / ``except``):: + + def function_foo(x, y, z): + '''function foo ... + + :param x: bla x + :type x: int + + :param y: bla y + :type y: float + + :param int z: bla z + + :return: sum + :rtype: float + + :raises OSError: bla + ''' + return x + y + z + +* or the Google style (``Args:``, ``Returns:``, ``Raises:``):: + + def function_foo(x, y, z): + '''function foo ... + + Args: + x (int): bla x + y (float): bla y + + z (int): bla z + + Returns: + float: sum + + Raises: + OSError: bla + ''' + return x + y + z + +* or the Numpy style (``Parameters``, ``Returns``, ``Raises``):: + + def function_foo(x, y, z): + '''function foo ... + + Parameters + ---------- + x: int + bla x + y: float + bla y + + z: int + bla z + + Returns + ------- + float + sum + + Raises + ------ + OSError + bla + ''' + return x + y + z + + +You'll be notified of **missing parameter documentation** but also of +**naming inconsistencies** between the signature and the documentation which +often arise when parameters are renamed automatically in the code, but not in +the documentation. + +Constructor parameters can be documented in either the class docstring or +the ``__init__`` docstring, but not both:: + + class ClassFoo(object): + '''Sphinx style docstring foo + + :param float x: bla x + + :param y: bla y + :type y: int + ''' + def __init__(self, x, y): + pass + + class ClassBar(object): + def __init__(self, x, y): + '''Google style docstring bar + + Args: + x (float): bla x + y (int): bla y + ''' + pass + +In some cases, having to document all parameters is a nuisance, for instance if +many of your functions or methods just follow a **common interface**. To remove +this burden, the checker accepts missing parameter documentation if one of the +following phrases is found in the docstring: + +* For the other parameters, see +* For the parameters, see + +(with arbitrary whitespace between the words). Please add a link to the +docstring defining the interface, e.g. a superclass method, after "see":: + + def callback(x, y, z): + '''Sphinx style docstring for callback ... + + :param x: bla x + :type x: int + + For the other parameters, see + :class:`MyFrameworkUsingAndDefiningCallback` + ''' + return x + y + z + + def callback(x, y, z): + '''Google style docstring for callback ... + + Args: + x (int): bla x + + For the other parameters, see + :class:`MyFrameworkUsingAndDefiningCallback` + ''' + return x + y + z + +Naming inconsistencies in existing parameter and their type documentations are +still detected. diff --git a/pylint/extensions/mccabe.rst b/pylint/extensions/mccabe.rst new file mode 100644 index 000000000..e63d5b20f --- /dev/null +++ b/pylint/extensions/mccabe.rst @@ -0,0 +1,37 @@ +You can now use this plugin for finding complexity issues in your code base. + +Activate it through ``pylint --load-plugins=pylint.extensions.mccabe``. It introduces +a new warning, ``too-complex``, which is emitted when a code block has a complexity +higher than a preestablished value, which can be controlled through the +``max-complexity`` option, such as in this example:: + + $ cat a.py + def f10(): + """McCabe rating: 11""" + myint = 2 + if myint == 5: + return myint + elif myint == 6: + return myint + elif myint == 7: + return myint + elif myint == 8: + return myint + elif myint == 9: + return myint + elif myint == 10: + if myint == 8: + while True: + return True + elif myint == 8: + with myint: + return 8 + else: + if myint == 2: + return myint + return myint + return myint + $ pylint a.py --load-plugins=pylint.extensions.mccabe + R:1: 'f10' is too complex. The McCabe rating is 11 (too-complex) + $ pylint a.py --load-plugins=pylint.extensions.mccabe --max-complexity=50 + $ diff --git a/pylint/extensions/redefined_variable_type.py b/pylint/extensions/redefined_variable_type.py index 6775b1d4e..4904fd456 100644 --- a/pylint/extensions/redefined_variable_type.py +++ b/pylint/extensions/redefined_variable_type.py @@ -19,6 +19,7 @@ class MultipleTypesChecker(BaseChecker): At a function, method, class or module scope This rule could be improved: + - Currently, if an attribute is set to different types in 2 methods of a same class, it won't be detected (see functional test) - One could improve the support for inference on assignment with tuples, diff --git a/pylint/test/unittest_lint.py b/pylint/test/unittest_lint.py index 562f27279..72051e118 100644 --- a/pylint/test/unittest_lint.py +++ b/pylint/test/unittest_lint.py @@ -4,6 +4,7 @@ from contextlib import contextmanager import sys import os +import re import tempfile from shutil import rmtree from os import getcwd, chdir @@ -463,6 +464,22 @@ class PyLinterTC(unittest.TestCase): checker_names = [c.name for c in self.linter.prepare_checkers()] self.assertIn('python3', checker_names) + def test_full_documentation(self): + out = six.StringIO() + self.linter.print_full_documentation(out) + output = out.getvalue() + # A few spot checks only + for re_str in [ + # autogenerated text + "^Pylint global options and switches$", + "Verbatim name of the checker is ``python3``", + # messages + "^:old-octal-literal \(E1608\):", + # options + "^:dummy-variables-rgx:", + ]: + regexp = re.compile(re_str, re.MULTILINE) + self.assertRegexpMatches(output, regexp) class ConfigTC(unittest.TestCase): diff --git a/pylint/utils.py b/pylint/utils.py index 68ea2c064..53d31f388 100644 --- a/pylint/utils.py +++ b/pylint/utils.py @@ -7,6 +7,7 @@ main pylint class from __future__ import print_function import collections +from inspect import cleandoc import os from os.path import dirname, basename, splitext, exists, isdir, join, normpath import re @@ -416,13 +417,16 @@ class MessagesHandlerMixIn(object): Message(msgid, symbol, (abspath, path, module, obj, line or 1, col_offset or 0), msg, confidence)) - def print_full_documentation(self): + def print_full_documentation(self, stream=None): """output a full documentation in ReST format""" - print("Pylint global options and switches") - print("----------------------------------") - print("") - print("Pylint provides global options and switches.") - print("") + if not stream: + stream = sys.stdout + + print("Pylint global options and switches", file=stream) + print("----------------------------------", file=stream) + print("", file=stream) + print("Pylint provides global options and switches.", file=stream) + print("", file=stream) by_checker = {} for checker in self.get_checkers(): @@ -433,63 +437,94 @@ class MessagesHandlerMixIn(object): title = 'General options' else: title = '%s options' % section.capitalize() - print(title) - print('~' * len(title)) - _rest_format_section(sys.stdout, None, options) - print("") + print(title, file=stream) + print('~' * len(title), file=stream) + _rest_format_section(stream, None, options) + print("", file=stream) else: + name = checker.name try: - by_checker[checker.name][0] += checker.options_and_values() - by_checker[checker.name][1].update(checker.msgs) - by_checker[checker.name][2] += checker.reports + by_checker[name]['options'] += checker.options_and_values() + by_checker[name]['msgs'].update(checker.msgs) + by_checker[name]['reports'] += checker.reports except KeyError: - by_checker[checker.name] = [list(checker.options_and_values()), - dict(checker.msgs), - list(checker.reports)] - - print("Pylint checkers' options and switches") - print("-------------------------------------") - print("") - print("Pylint checkers can provide three set of features:") - print("") - print("* options that control their execution,") - print("* messages that they can raise,") - print("* reports that they can generate.") - print("") - print("Below is a list of all checkers and their features.") - print("") - - for checker, (options, msgs, reports) in six.iteritems(by_checker): - title = '%s checker' % (checker.replace("_", " ").title()) - print(title) - print('~' * len(title)) - print("") - print("Verbatim name of the checker is ``%s``." % checker) - print("") - if options: - title = 'Options' - print(title) - print('^' * len(title)) - _rest_format_section(sys.stdout, None, options) - print("") - if msgs: - title = 'Messages' - print(title) - print('~' * len(title)) - for msgid, msg in sorted(six.iteritems(msgs), - key=lambda kv: (_MSG_ORDER.index(kv[0][0]), kv[1])): - msg = build_message_def(checker, msgid, msg) - print(msg.format_help(checkerref=False)) - print("") - if reports: - title = 'Reports' - print(title) - print('~' * len(title)) - for report in reports: - print(':%s: %s' % report[:2]) - print("") - print("") - + by_checker[name] = { + 'options': list(checker.options_and_values()), + 'msgs': dict(checker.msgs), + 'reports': list(checker.reports), + } + + print("Pylint checkers' options and switches", file=stream) + print("-------------------------------------", file=stream) + print("", file=stream) + print("Pylint checkers can provide three set of features:", file=stream) + print("", file=stream) + print("* options that control their execution,", file=stream) + print("* messages that they can raise,", file=stream) + print("* reports that they can generate.", file=stream) + print("", file=stream) + print("Below is a list of all checkers and their features.", file=stream) + print("", file=stream) + + for checker, info in six.iteritems(by_checker): + self._print_checker_doc(checker, info, stream=stream) + + @staticmethod + def _print_checker_doc(checker_name, info, stream=None): + """Helper method for print_full_documentation. + + Also used by doc/exts/pylint_extensions.py. + """ + if not stream: + stream = sys.stdout + + doc = info.get('doc') + module = info.get('module') + msgs = info.get('msgs') + options = info.get('options') + reports = info.get('reports') + + title = '%s checker' % (checker_name.replace("_", " ").title()) + + if module: + # Provide anchor to link against + print(".. _%s:\n" % module, file=stream) + print(title, file=stream) + print('~' * len(title), file=stream) + print("", file=stream) + if module: + print("This checker is provided by ``%s``." % module, file=stream) + print("Verbatim name of the checker is ``%s``." % checker_name, file=stream) + print("", file=stream) + if doc: + title = 'Documentation' + print(title, file=stream) + print('^' * len(title), file=stream) + print(cleandoc(doc), file=stream) + print("", file=stream) + if options: + title = 'Options' + print(title, file=stream) + print('^' * len(title), file=stream) + _rest_format_section(stream, None, options) + print("", file=stream) + if msgs: + title = 'Messages' + print(title, file=stream) + print('^' * len(title), file=stream) + for msgid, msg in sorted(six.iteritems(msgs), + key=lambda kv: (_MSG_ORDER.index(kv[0][0]), kv[1])): + msg = build_message_def(checker_name, msgid, msg) + print(msg.format_help(checkerref=False), file=stream) + print("", file=stream) + if reports: + title = 'Reports' + print(title, file=stream) + print('^' * len(title), file=stream) + for report in reports: + print(':%s: %s' % report[:2], file=stream) + print("", file=stream) + print("", file=stream) class FileState(object): """Hold internal state specific to the currently analyzed file""" |