8th day of python challenges 111-117
This commit is contained in:
@@ -0,0 +1,794 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2016-2018 Ashley Whetter <ashley@awhetter.co.uk>
|
||||
# Copyright (c) 2016-2017 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2016 Yuri Bochkarev <baltazar.bz@gmail.com>
|
||||
# Copyright (c) 2016 Glenn Matthews <glenn@e-dad.net>
|
||||
# Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
|
||||
# Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com>
|
||||
# Copyright (c) 2017 Mitar <mitar.github@tnode.com>
|
||||
# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
|
||||
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
|
||||
# Copyright (c) 2018 Mitchell T.H. Young <mitchelly@gmail.com>
|
||||
# Copyright (c) 2018 Adrian Chirieac <chirieacam@gmail.com>
|
||||
|
||||
# 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
|
||||
|
||||
"""Utility methods for docstring checking."""
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import re
|
||||
|
||||
import astroid
|
||||
|
||||
from pylint.checkers import utils
|
||||
|
||||
|
||||
def space_indentation(s):
|
||||
"""The number of leading spaces in a string
|
||||
|
||||
:param str s: input string
|
||||
|
||||
:rtype: int
|
||||
:return: number of leading spaces
|
||||
"""
|
||||
return len(s) - len(s.lstrip(" "))
|
||||
|
||||
|
||||
def get_setters_property_name(node):
|
||||
"""Get the name of the property that the given node is a setter for.
|
||||
|
||||
:param node: The node to get the property name for.
|
||||
:type node: str
|
||||
|
||||
:rtype: str or None
|
||||
:returns: The name of the property that the node is a setter for,
|
||||
or None if one could not be found.
|
||||
"""
|
||||
decorators = node.decorators.nodes if node.decorators else []
|
||||
for decorator in decorators:
|
||||
if (
|
||||
isinstance(decorator, astroid.Attribute)
|
||||
and decorator.attrname == "setter"
|
||||
and isinstance(decorator.expr, astroid.Name)
|
||||
):
|
||||
return decorator.expr.name
|
||||
return None
|
||||
|
||||
|
||||
def get_setters_property(node):
|
||||
"""Get the property node for the given setter node.
|
||||
|
||||
:param node: The node to get the property for.
|
||||
:type node: astroid.FunctionDef
|
||||
|
||||
:rtype: astroid.FunctionDef or None
|
||||
:returns: The node relating to the property of the given setter node,
|
||||
or None if one could not be found.
|
||||
"""
|
||||
property_ = None
|
||||
|
||||
property_name = get_setters_property_name(node)
|
||||
class_node = utils.node_frame_class(node)
|
||||
if property_name and class_node:
|
||||
class_attrs = class_node.getattr(node.name)
|
||||
for attr in class_attrs:
|
||||
if utils.decorated_with_property(attr):
|
||||
property_ = attr
|
||||
break
|
||||
|
||||
return property_
|
||||
|
||||
|
||||
def returns_something(return_node):
|
||||
"""Check if a return node returns a value other than None.
|
||||
|
||||
:param return_node: The return node to check.
|
||||
:type return_node: astroid.Return
|
||||
|
||||
:rtype: bool
|
||||
:return: True if the return node returns a value other than None,
|
||||
False otherwise.
|
||||
"""
|
||||
returns = return_node.value
|
||||
|
||||
if returns is None:
|
||||
return False
|
||||
|
||||
return not (isinstance(returns, astroid.Const) and returns.value is None)
|
||||
|
||||
|
||||
def _get_raise_target(node):
|
||||
if isinstance(node.exc, astroid.Call):
|
||||
func = node.exc.func
|
||||
if isinstance(func, (astroid.Name, astroid.Attribute)):
|
||||
return utils.safe_infer(func)
|
||||
return None
|
||||
|
||||
|
||||
def possible_exc_types(node):
|
||||
"""
|
||||
Gets all of the possible raised exception types for the given raise node.
|
||||
|
||||
.. note::
|
||||
|
||||
Caught exception types are ignored.
|
||||
|
||||
|
||||
:param node: The raise node to find exception types for.
|
||||
:type node: astroid.node_classes.NodeNG
|
||||
|
||||
:returns: A list of exception types possibly raised by :param:`node`.
|
||||
:rtype: set(str)
|
||||
"""
|
||||
excs = []
|
||||
if isinstance(node.exc, astroid.Name):
|
||||
inferred = utils.safe_infer(node.exc)
|
||||
if inferred:
|
||||
excs = [inferred.name]
|
||||
elif node.exc is None:
|
||||
handler = node.parent
|
||||
while handler and not isinstance(handler, astroid.ExceptHandler):
|
||||
handler = handler.parent
|
||||
|
||||
if handler and handler.type:
|
||||
inferred_excs = astroid.unpack_infer(handler.type)
|
||||
excs = (exc.name for exc in inferred_excs if exc is not astroid.Uninferable)
|
||||
else:
|
||||
target = _get_raise_target(node)
|
||||
if isinstance(target, astroid.ClassDef):
|
||||
excs = [target.name]
|
||||
elif isinstance(target, astroid.FunctionDef):
|
||||
for ret in target.nodes_of_class(astroid.Return):
|
||||
if ret.frame() != target:
|
||||
# return from inner function - ignore it
|
||||
continue
|
||||
|
||||
val = utils.safe_infer(ret.value)
|
||||
if (
|
||||
val
|
||||
and isinstance(val, (astroid.Instance, astroid.ClassDef))
|
||||
and utils.inherit_from_std_ex(val)
|
||||
):
|
||||
excs.append(val.name)
|
||||
|
||||
try:
|
||||
return {exc for exc in excs if not utils.node_ignores_exception(node, exc)}
|
||||
except astroid.InferenceError:
|
||||
return set()
|
||||
|
||||
|
||||
def docstringify(docstring, default_type="default"):
|
||||
for docstring_type in [
|
||||
SphinxDocstring,
|
||||
EpytextDocstring,
|
||||
GoogleDocstring,
|
||||
NumpyDocstring,
|
||||
]:
|
||||
instance = docstring_type(docstring)
|
||||
if instance.is_valid():
|
||||
return instance
|
||||
|
||||
docstring_type = DOCSTRING_TYPES.get(default_type, Docstring)
|
||||
return docstring_type(docstring)
|
||||
|
||||
|
||||
class Docstring:
|
||||
re_for_parameters_see = re.compile(
|
||||
r"""
|
||||
For\s+the\s+(other)?\s*parameters\s*,\s+see
|
||||
""",
|
||||
re.X | re.S,
|
||||
)
|
||||
|
||||
supports_yields = None
|
||||
"""True if the docstring supports a "yield" section.
|
||||
|
||||
False if the docstring uses the returns section to document generators.
|
||||
"""
|
||||
|
||||
# These methods are designed to be overridden
|
||||
# pylint: disable=no-self-use
|
||||
def __init__(self, doc):
|
||||
doc = doc or ""
|
||||
self.doc = doc.expandtabs()
|
||||
|
||||
def is_valid(self):
|
||||
return False
|
||||
|
||||
def exceptions(self):
|
||||
return set()
|
||||
|
||||
def has_params(self):
|
||||
return False
|
||||
|
||||
def has_returns(self):
|
||||
return False
|
||||
|
||||
def has_rtype(self):
|
||||
return False
|
||||
|
||||
def has_property_returns(self):
|
||||
return False
|
||||
|
||||
def has_property_type(self):
|
||||
return False
|
||||
|
||||
def has_yields(self):
|
||||
return False
|
||||
|
||||
def has_yields_type(self):
|
||||
return False
|
||||
|
||||
def match_param_docs(self):
|
||||
return set(), set()
|
||||
|
||||
def params_documented_elsewhere(self):
|
||||
return self.re_for_parameters_see.search(self.doc) is not None
|
||||
|
||||
|
||||
class SphinxDocstring(Docstring):
|
||||
re_type = r"""
|
||||
[~!]? # Optional link style prefix
|
||||
\w(?:\w|\.[^\.])* # Valid python name
|
||||
"""
|
||||
|
||||
re_simple_container_type = r"""
|
||||
{type} # a container type
|
||||
[\(\[] [^\n\s]+ [\)\]] # with the contents of the container
|
||||
""".format(
|
||||
type=re_type
|
||||
)
|
||||
|
||||
re_xref = r"""
|
||||
(?::\w+:)? # optional tag
|
||||
`{}` # what to reference
|
||||
""".format(
|
||||
re_type
|
||||
)
|
||||
|
||||
re_param_raw = r"""
|
||||
: # initial colon
|
||||
(?: # Sphinx keywords
|
||||
param|parameter|
|
||||
arg|argument|
|
||||
key|keyword
|
||||
)
|
||||
\s+ # whitespace
|
||||
|
||||
(?: # optional type declaration
|
||||
({type}|{container_type})
|
||||
\s+
|
||||
)?
|
||||
|
||||
(\w+) # Parameter name
|
||||
\s* # whitespace
|
||||
: # final colon
|
||||
""".format(
|
||||
type=re_type, container_type=re_simple_container_type
|
||||
)
|
||||
re_param_in_docstring = re.compile(re_param_raw, re.X | re.S)
|
||||
|
||||
re_type_raw = r"""
|
||||
:type # Sphinx keyword
|
||||
\s+ # whitespace
|
||||
({type}) # Parameter name
|
||||
\s* # whitespace
|
||||
: # final colon
|
||||
""".format(
|
||||
type=re_type
|
||||
)
|
||||
re_type_in_docstring = re.compile(re_type_raw, re.X | re.S)
|
||||
|
||||
re_property_type_raw = r"""
|
||||
:type: # Sphinx keyword
|
||||
\s+ # whitespace
|
||||
{type} # type declaration
|
||||
""".format(
|
||||
type=re_type
|
||||
)
|
||||
re_property_type_in_docstring = re.compile(re_property_type_raw, re.X | re.S)
|
||||
|
||||
re_raise_raw = r"""
|
||||
: # initial colon
|
||||
(?: # Sphinx keyword
|
||||
raises?|
|
||||
except|exception
|
||||
)
|
||||
\s+ # whitespace
|
||||
({type}) # exception type
|
||||
\s* # whitespace
|
||||
: # final colon
|
||||
""".format(
|
||||
type=re_type
|
||||
)
|
||||
re_raise_in_docstring = re.compile(re_raise_raw, re.X | re.S)
|
||||
|
||||
re_rtype_in_docstring = re.compile(r":rtype:")
|
||||
|
||||
re_returns_in_docstring = re.compile(r":returns?:")
|
||||
|
||||
supports_yields = False
|
||||
|
||||
def is_valid(self):
|
||||
return bool(
|
||||
self.re_param_in_docstring.search(self.doc)
|
||||
or self.re_raise_in_docstring.search(self.doc)
|
||||
or self.re_rtype_in_docstring.search(self.doc)
|
||||
or self.re_returns_in_docstring.search(self.doc)
|
||||
or self.re_property_type_in_docstring.search(self.doc)
|
||||
)
|
||||
|
||||
def exceptions(self):
|
||||
types = set()
|
||||
|
||||
for match in re.finditer(self.re_raise_in_docstring, self.doc):
|
||||
raise_type = match.group(1)
|
||||
types.add(raise_type)
|
||||
|
||||
return types
|
||||
|
||||
def has_params(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
return self.re_param_in_docstring.search(self.doc) is not None
|
||||
|
||||
def has_returns(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
return bool(self.re_returns_in_docstring.search(self.doc))
|
||||
|
||||
def has_rtype(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
return bool(self.re_rtype_in_docstring.search(self.doc))
|
||||
|
||||
def has_property_returns(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
# The summary line is the return doc,
|
||||
# so the first line must not be a known directive.
|
||||
return not self.doc.lstrip().startswith(":")
|
||||
|
||||
def has_property_type(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
return bool(self.re_property_type_in_docstring.search(self.doc))
|
||||
|
||||
def match_param_docs(self):
|
||||
params_with_doc = set()
|
||||
params_with_type = set()
|
||||
|
||||
for match in re.finditer(self.re_param_in_docstring, self.doc):
|
||||
name = match.group(2)
|
||||
params_with_doc.add(name)
|
||||
param_type = match.group(1)
|
||||
if param_type is not None:
|
||||
params_with_type.add(name)
|
||||
|
||||
params_with_type.update(re.findall(self.re_type_in_docstring, self.doc))
|
||||
return params_with_doc, params_with_type
|
||||
|
||||
|
||||
class EpytextDocstring(SphinxDocstring):
|
||||
"""
|
||||
Epytext is similar to Sphinx. See the docs:
|
||||
http://epydoc.sourceforge.net/epytext.html
|
||||
http://epydoc.sourceforge.net/fields.html#fields
|
||||
|
||||
It's used in PyCharm:
|
||||
https://www.jetbrains.com/help/pycharm/2016.1/creating-documentation-comments.html#d848203e314
|
||||
https://www.jetbrains.com/help/pycharm/2016.1/using-docstrings-to-specify-types.html
|
||||
"""
|
||||
|
||||
re_param_in_docstring = re.compile(
|
||||
SphinxDocstring.re_param_raw.replace(":", "@", 1), re.X | re.S
|
||||
)
|
||||
|
||||
re_type_in_docstring = re.compile(
|
||||
SphinxDocstring.re_type_raw.replace(":", "@", 1), re.X | re.S
|
||||
)
|
||||
|
||||
re_property_type_in_docstring = re.compile(
|
||||
SphinxDocstring.re_property_type_raw.replace(":", "@", 1), re.X | re.S
|
||||
)
|
||||
|
||||
re_raise_in_docstring = re.compile(
|
||||
SphinxDocstring.re_raise_raw.replace(":", "@", 1), re.X | re.S
|
||||
)
|
||||
|
||||
re_rtype_in_docstring = re.compile(
|
||||
r"""
|
||||
@ # initial "at" symbol
|
||||
(?: # Epytext keyword
|
||||
rtype|returntype
|
||||
)
|
||||
: # final colon
|
||||
""",
|
||||
re.X | re.S,
|
||||
)
|
||||
|
||||
re_returns_in_docstring = re.compile(r"@returns?:")
|
||||
|
||||
def has_property_returns(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
# If this is a property docstring, the summary is the return doc.
|
||||
if self.has_property_type():
|
||||
# The summary line is the return doc,
|
||||
# so the first line must not be a known directive.
|
||||
return not self.doc.lstrip().startswith("@")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class GoogleDocstring(Docstring):
|
||||
re_type = SphinxDocstring.re_type
|
||||
|
||||
re_xref = SphinxDocstring.re_xref
|
||||
|
||||
re_container_type = r"""
|
||||
(?:{type}|{xref}) # a container type
|
||||
[\(\[] [^\n]+ [\)\]] # with the contents of the container
|
||||
""".format(
|
||||
type=re_type, xref=re_xref
|
||||
)
|
||||
|
||||
re_multiple_type = r"""
|
||||
(?:{container_type}|{type}|{xref})
|
||||
(?:\s+or\s+(?:{container_type}|{type}|{xref}))*
|
||||
""".format(
|
||||
type=re_type, xref=re_xref, container_type=re_container_type
|
||||
)
|
||||
|
||||
_re_section_template = r"""
|
||||
^([ ]*) {0} \s*: \s*$ # Google parameter header
|
||||
( .* ) # section
|
||||
"""
|
||||
|
||||
re_param_section = re.compile(
|
||||
_re_section_template.format(r"(?:Args|Arguments|Parameters)"),
|
||||
re.X | re.S | re.M,
|
||||
)
|
||||
|
||||
re_keyword_param_section = re.compile(
|
||||
_re_section_template.format(r"Keyword\s(?:Args|Arguments|Parameters)"),
|
||||
re.X | re.S | re.M,
|
||||
)
|
||||
|
||||
re_param_line = re.compile(
|
||||
r"""
|
||||
\s* \*{{0,2}}(\w+) # identifier potentially with asterisks
|
||||
\s* ( [(]
|
||||
{type}
|
||||
(?:,\s+optional)?
|
||||
[)] )? \s* : # optional type declaration
|
||||
\s* (.*) # beginning of optional description
|
||||
""".format(
|
||||
type=re_multiple_type
|
||||
),
|
||||
re.X | re.S | re.M,
|
||||
)
|
||||
|
||||
re_raise_section = re.compile(
|
||||
_re_section_template.format(r"Raises"), re.X | re.S | re.M
|
||||
)
|
||||
|
||||
re_raise_line = re.compile(
|
||||
r"""
|
||||
\s* ({type}) \s* : # identifier
|
||||
\s* (.*) # beginning of optional description
|
||||
""".format(
|
||||
type=re_type
|
||||
),
|
||||
re.X | re.S | re.M,
|
||||
)
|
||||
|
||||
re_returns_section = re.compile(
|
||||
_re_section_template.format(r"Returns?"), re.X | re.S | re.M
|
||||
)
|
||||
|
||||
re_returns_line = re.compile(
|
||||
r"""
|
||||
\s* ({type}:)? # identifier
|
||||
\s* (.*) # beginning of description
|
||||
""".format(
|
||||
type=re_multiple_type
|
||||
),
|
||||
re.X | re.S | re.M,
|
||||
)
|
||||
|
||||
re_property_returns_line = re.compile(
|
||||
r"""
|
||||
^{type}: # indentifier
|
||||
\s* (.*) # Summary line / description
|
||||
""".format(
|
||||
type=re_multiple_type
|
||||
),
|
||||
re.X | re.S | re.M,
|
||||
)
|
||||
|
||||
re_yields_section = re.compile(
|
||||
_re_section_template.format(r"Yields?"), re.X | re.S | re.M
|
||||
)
|
||||
|
||||
re_yields_line = re_returns_line
|
||||
|
||||
supports_yields = True
|
||||
|
||||
def is_valid(self):
|
||||
return bool(
|
||||
self.re_param_section.search(self.doc)
|
||||
or self.re_raise_section.search(self.doc)
|
||||
or self.re_returns_section.search(self.doc)
|
||||
or self.re_yields_section.search(self.doc)
|
||||
or self.re_property_returns_line.search(self._first_line())
|
||||
)
|
||||
|
||||
def has_params(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
return self.re_param_section.search(self.doc) is not None
|
||||
|
||||
def has_returns(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
entries = self._parse_section(self.re_returns_section)
|
||||
for entry in entries:
|
||||
match = self.re_returns_line.match(entry)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
return_desc = match.group(2)
|
||||
if return_desc:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def has_rtype(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
entries = self._parse_section(self.re_returns_section)
|
||||
for entry in entries:
|
||||
match = self.re_returns_line.match(entry)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
return_type = match.group(1)
|
||||
if return_type:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def has_property_returns(self):
|
||||
# The summary line is the return doc,
|
||||
# so the first line must not be a known directive.
|
||||
first_line = self._first_line()
|
||||
return not bool(
|
||||
self.re_param_section.search(first_line)
|
||||
or self.re_raise_section.search(first_line)
|
||||
or self.re_returns_section.search(first_line)
|
||||
or self.re_yields_section.search(first_line)
|
||||
)
|
||||
|
||||
def has_property_type(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
return bool(self.re_property_returns_line.match(self._first_line()))
|
||||
|
||||
def has_yields(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
entries = self._parse_section(self.re_yields_section)
|
||||
for entry in entries:
|
||||
match = self.re_yields_line.match(entry)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
yield_desc = match.group(2)
|
||||
if yield_desc:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def has_yields_type(self):
|
||||
if not self.doc:
|
||||
return False
|
||||
|
||||
entries = self._parse_section(self.re_yields_section)
|
||||
for entry in entries:
|
||||
match = self.re_yields_line.match(entry)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
yield_type = match.group(1)
|
||||
if yield_type:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def exceptions(self):
|
||||
types = set()
|
||||
|
||||
entries = self._parse_section(self.re_raise_section)
|
||||
for entry in entries:
|
||||
match = self.re_raise_line.match(entry)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
exc_type = match.group(1)
|
||||
exc_desc = match.group(2)
|
||||
if exc_desc:
|
||||
types.add(exc_type)
|
||||
|
||||
return types
|
||||
|
||||
def match_param_docs(self):
|
||||
params_with_doc = set()
|
||||
params_with_type = set()
|
||||
|
||||
entries = self._parse_section(self.re_param_section)
|
||||
entries.extend(self._parse_section(self.re_keyword_param_section))
|
||||
for entry in entries:
|
||||
match = self.re_param_line.match(entry)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
param_name = match.group(1)
|
||||
param_type = match.group(2)
|
||||
param_desc = match.group(3)
|
||||
if param_type:
|
||||
params_with_type.add(param_name)
|
||||
|
||||
if param_desc:
|
||||
params_with_doc.add(param_name)
|
||||
|
||||
return params_with_doc, params_with_type
|
||||
|
||||
def _first_line(self):
|
||||
return self.doc.lstrip().split("\n", 1)[0]
|
||||
|
||||
@staticmethod
|
||||
def min_section_indent(section_match):
|
||||
return len(section_match.group(1)) + 1
|
||||
|
||||
@staticmethod
|
||||
def _is_section_header(_):
|
||||
# Google parsing does not need to detect section headers,
|
||||
# because it works off of indentation level only
|
||||
return False
|
||||
|
||||
def _parse_section(self, section_re):
|
||||
section_match = section_re.search(self.doc)
|
||||
if section_match is None:
|
||||
return []
|
||||
|
||||
min_indentation = self.min_section_indent(section_match)
|
||||
|
||||
entries = []
|
||||
entry = []
|
||||
is_first = True
|
||||
for line in section_match.group(2).splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
indentation = space_indentation(line)
|
||||
if indentation < min_indentation:
|
||||
break
|
||||
|
||||
# The first line after the header defines the minimum
|
||||
# indentation.
|
||||
if is_first:
|
||||
min_indentation = indentation
|
||||
is_first = False
|
||||
|
||||
if indentation == min_indentation:
|
||||
if self._is_section_header(line):
|
||||
break
|
||||
# Lines with minimum indentation must contain the beginning
|
||||
# of a new parameter documentation.
|
||||
if entry:
|
||||
entries.append("\n".join(entry))
|
||||
entry = []
|
||||
|
||||
entry.append(line)
|
||||
|
||||
if entry:
|
||||
entries.append("\n".join(entry))
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
class NumpyDocstring(GoogleDocstring):
|
||||
_re_section_template = r"""
|
||||
^([ ]*) {0} \s*?$ # Numpy parameters header
|
||||
\s* [-=]+ \s*?$ # underline
|
||||
( .* ) # section
|
||||
"""
|
||||
|
||||
re_param_section = re.compile(
|
||||
_re_section_template.format(r"(?:Args|Arguments|Parameters)"),
|
||||
re.X | re.S | re.M,
|
||||
)
|
||||
|
||||
re_param_line = re.compile(
|
||||
r"""
|
||||
\s* (\w+) # identifier
|
||||
\s* :
|
||||
\s* (?:({type})(?:,\s+optional)?)? # optional type declaration
|
||||
\n # description starts on a new line
|
||||
\s* (.*) # description
|
||||
""".format(
|
||||
type=GoogleDocstring.re_multiple_type
|
||||
),
|
||||
re.X | re.S,
|
||||
)
|
||||
|
||||
re_raise_section = re.compile(
|
||||
_re_section_template.format(r"Raises"), re.X | re.S | re.M
|
||||
)
|
||||
|
||||
re_raise_line = re.compile(
|
||||
r"""
|
||||
\s* ({type})$ # type declaration
|
||||
\s* (.*) # optional description
|
||||
""".format(
|
||||
type=GoogleDocstring.re_type
|
||||
),
|
||||
re.X | re.S | re.M,
|
||||
)
|
||||
|
||||
re_returns_section = re.compile(
|
||||
_re_section_template.format(r"Returns?"), re.X | re.S | re.M
|
||||
)
|
||||
|
||||
re_returns_line = re.compile(
|
||||
r"""
|
||||
\s* (?:\w+\s+:\s+)? # optional name
|
||||
({type})$ # type declaration
|
||||
\s* (.*) # optional description
|
||||
""".format(
|
||||
type=GoogleDocstring.re_multiple_type
|
||||
),
|
||||
re.X | re.S | re.M,
|
||||
)
|
||||
|
||||
re_yields_section = re.compile(
|
||||
_re_section_template.format(r"Yields?"), re.X | re.S | re.M
|
||||
)
|
||||
|
||||
re_yields_line = re_returns_line
|
||||
|
||||
supports_yields = True
|
||||
|
||||
@staticmethod
|
||||
def min_section_indent(section_match):
|
||||
return len(section_match.group(1))
|
||||
|
||||
@staticmethod
|
||||
def _is_section_header(line):
|
||||
return bool(re.match(r"\s*-+$", line))
|
||||
|
||||
|
||||
DOCSTRING_TYPES = {
|
||||
"sphinx": SphinxDocstring,
|
||||
"epytext": EpytextDocstring,
|
||||
"google": GoogleDocstring,
|
||||
"numpy": NumpyDocstring,
|
||||
"default": Docstring,
|
||||
}
|
||||
"""A map of the name of the docstring type to its class.
|
||||
|
||||
:type: dict(str, type)
|
||||
"""
|
@@ -0,0 +1,73 @@
|
||||
# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com>
|
||||
|
||||
# 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
|
||||
|
||||
"""Checker for deprecated builtins."""
|
||||
import sys
|
||||
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.checkers.utils import check_messages
|
||||
from pylint.interfaces import IAstroidChecker
|
||||
|
||||
|
||||
BAD_FUNCTIONS = ["map", "filter"]
|
||||
if sys.version_info < (3, 0):
|
||||
BAD_FUNCTIONS.append("input")
|
||||
# Some hints regarding the use of bad builtins.
|
||||
BUILTIN_HINTS = {"map": "Using a list comprehension can be clearer."}
|
||||
BUILTIN_HINTS["filter"] = BUILTIN_HINTS["map"]
|
||||
|
||||
|
||||
class BadBuiltinChecker(BaseChecker):
|
||||
|
||||
__implements__ = (IAstroidChecker,)
|
||||
name = "deprecated_builtins"
|
||||
msgs = {
|
||||
"W0141": (
|
||||
"Used builtin function %s",
|
||||
"bad-builtin",
|
||||
"Used when a black listed builtin function is used (see the "
|
||||
"bad-function option). Usual black listed functions are the ones "
|
||||
"like map, or filter , where Python offers now some cleaner "
|
||||
"alternative like list comprehension.",
|
||||
)
|
||||
}
|
||||
|
||||
options = (
|
||||
(
|
||||
"bad-functions",
|
||||
{
|
||||
"default": BAD_FUNCTIONS,
|
||||
"type": "csv",
|
||||
"metavar": "<builtin function names>",
|
||||
"help": "List of builtins function names that should not be "
|
||||
"used, separated by a comma",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@check_messages("bad-builtin")
|
||||
def visit_call(self, node):
|
||||
if isinstance(node.func, astroid.Name):
|
||||
name = node.func.name
|
||||
# ignore the name if it's not a builtin (i.e. not defined in the
|
||||
# locals nor globals scope)
|
||||
if not (name in node.frame() or name in node.root()):
|
||||
if name in self.config.bad_functions:
|
||||
hint = BUILTIN_HINTS.get(name)
|
||||
if hint:
|
||||
args = "%r. %s" % (name, hint)
|
||||
else:
|
||||
args = repr(name)
|
||||
self.add_message("bad-builtin", node=node, args=args)
|
||||
|
||||
|
||||
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(BadBuiltinChecker(linter))
|
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2014-2015 Bruno Daniel <bruno.daniel@blue-yonder.com>
|
||||
# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2016 Ashley Whetter <ashley@awhetter.co.uk>
|
||||
|
||||
# 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
|
||||
|
||||
import warnings
|
||||
|
||||
from pylint.extensions import docparams
|
||||
|
||||
|
||||
def register(linter):
|
||||
"""Required method to auto register this checker.
|
||||
|
||||
:param linter: Main interface object for Pylint plugins
|
||||
:type linter: Pylint object
|
||||
"""
|
||||
warnings.warn(
|
||||
"This plugin is deprecated, use pylint.extensions.docparams instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
linter.register_checker(docparams.DocstringParameterChecker(linter))
|
@@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
|
||||
# Copyright (c) 2016-2017 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2016 Glenn Matthews <glmatthe@cisco.com>
|
||||
# Copyright (c) 2018 Ville Skyttä <ville.skytta@upcloud.com>
|
||||
|
||||
# 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
|
||||
|
||||
import astroid
|
||||
from pylint.checkers import BaseTokenChecker
|
||||
from pylint.checkers.utils import check_messages
|
||||
from pylint.interfaces import ITokenChecker, IAstroidChecker
|
||||
|
||||
|
||||
class ElseifUsedChecker(BaseTokenChecker):
|
||||
"""Checks for use of "else if" when an "elif" could be used
|
||||
"""
|
||||
|
||||
__implements__ = (ITokenChecker, IAstroidChecker)
|
||||
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 "
|
||||
"an if statement and does not contain statements that "
|
||||
"would be unrelated to it.",
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self, linter=None):
|
||||
BaseTokenChecker.__init__(self, linter)
|
||||
self._init()
|
||||
|
||||
def _init(self):
|
||||
self._elifs = []
|
||||
self._if_counter = 0
|
||||
|
||||
def process_tokens(self, tokens):
|
||||
# Process tokens and look for 'if' or 'elif'
|
||||
for _, token, _, _, _ in tokens:
|
||||
if token == "elif":
|
||||
self._elifs.append(True)
|
||||
elif token == "if":
|
||||
self._elifs.append(False)
|
||||
|
||||
def leave_module(self, _):
|
||||
self._init()
|
||||
|
||||
def visit_ifexp(self, _):
|
||||
self._if_counter += 1
|
||||
|
||||
def visit_comprehension(self, node):
|
||||
self._if_counter += len(node.ifs)
|
||||
|
||||
@check_messages("else-if-used")
|
||||
def visit_if(self, node):
|
||||
if isinstance(node.parent, astroid.If):
|
||||
orelse = node.parent.orelse
|
||||
# current if node must directly follow an "else"
|
||||
if orelse and orelse == [node]:
|
||||
if not self._elifs[self._if_counter]:
|
||||
self.add_message("else-if-used", node=node)
|
||||
self._if_counter += 1
|
||||
|
||||
|
||||
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(ElseifUsedChecker(linter))
|
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2016 Alexander Todorov <atodorov@otb.bg>
|
||||
# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com>
|
||||
|
||||
# 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
|
||||
|
||||
"""Looks for comparisons to empty string."""
|
||||
|
||||
import itertools
|
||||
|
||||
import astroid
|
||||
|
||||
from pylint import interfaces
|
||||
from pylint import checkers
|
||||
from pylint.checkers import utils
|
||||
|
||||
|
||||
def _is_constant_zero(node):
|
||||
return isinstance(node, astroid.Const) and node.value == 0
|
||||
|
||||
|
||||
class CompareToZeroChecker(checkers.BaseChecker):
|
||||
"""Checks for comparisons to zero.
|
||||
Most of the times you should use the fact that integers with a value of 0 are false.
|
||||
An exception to this rule is when 0 is allowed in the program and has a
|
||||
different meaning than None!
|
||||
"""
|
||||
|
||||
__implements__ = (interfaces.IAstroidChecker,)
|
||||
|
||||
# configuration section name
|
||||
name = "compare-to-zero"
|
||||
msgs = {
|
||||
"C2001": (
|
||||
"Avoid comparisons to zero",
|
||||
"compare-to-zero",
|
||||
"Used when Pylint detects comparison to a 0 constant.",
|
||||
)
|
||||
}
|
||||
|
||||
priority = -2
|
||||
options = ()
|
||||
|
||||
@utils.check_messages("compare-to-zero")
|
||||
def visit_compare(self, node):
|
||||
_operators = ["!=", "==", "is not", "is"]
|
||||
# note: astroid.Compare has the left most operand in node.left
|
||||
# while the rest are a list of tuples in node.ops
|
||||
# the format of the tuple is ('compare operator sign', node)
|
||||
# here we squash everything into `ops` to make it easier for processing later
|
||||
ops = [("", node.left)]
|
||||
ops.extend(node.ops)
|
||||
ops = list(itertools.chain(*ops))
|
||||
|
||||
for ops_idx in range(len(ops) - 2):
|
||||
op_1 = ops[ops_idx]
|
||||
op_2 = ops[ops_idx + 1]
|
||||
op_3 = ops[ops_idx + 2]
|
||||
error_detected = False
|
||||
|
||||
# 0 ?? X
|
||||
if _is_constant_zero(op_1) and op_2 in _operators:
|
||||
error_detected = True
|
||||
# X ?? 0
|
||||
elif op_2 in _operators and _is_constant_zero(op_3):
|
||||
error_detected = True
|
||||
|
||||
if error_detected:
|
||||
self.add_message("compare-to-zero", node=node)
|
||||
|
||||
|
||||
def register(linter):
|
||||
"""Required method to auto register this checker."""
|
||||
linter.register_checker(CompareToZeroChecker(linter))
|
498
venv/lib/python3.6/site-packages/pylint/extensions/docparams.py
Normal file
498
venv/lib/python3.6/site-packages/pylint/extensions/docparams.py
Normal file
@@ -0,0 +1,498 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2014-2015 Bruno Daniel <bruno.daniel@blue-yonder.com>
|
||||
# Copyright (c) 2015-2017 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2016-2018 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 Anthony Sottile <asottile@umich.edu>
|
||||
# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
|
||||
# Copyright (c) 2018 Adam Dangoor <adamdangoor@gmail.com>
|
||||
|
||||
# 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
|
||||
"""
|
||||
from __future__ import print_function, division, absolute_import
|
||||
|
||||
import astroid
|
||||
|
||||
from pylint.interfaces import IAstroidChecker
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.checkers import utils as checker_utils
|
||||
import pylint.extensions._check_docs_utils as utils
|
||||
|
||||
|
||||
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", "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", "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", "missing-param-doc")]},
|
||||
),
|
||||
"W9016": (
|
||||
'"%s" missing in parameter type documentation',
|
||||
"missing-type-doc",
|
||||
"Please add parameter type declarations for all parameters.",
|
||||
{"old_names": [("W9004", "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:
|
||||
self.add_message("missing-yield-type-doc", node=func_node)
|
||||
|
||||
def visit_yieldfrom(self, node):
|
||||
self.visit_yield(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: str
|
||||
|
||||
: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
|
||||
|
||||
def _compare_missing_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 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
|
||||
"""
|
||||
if not tolerate_missing_params:
|
||||
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(found_argument_names, message_id, not_needed_names):
|
||||
"""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
|
||||
"""
|
||||
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,
|
||||
)
|
||||
|
||||
_compare_missing_args(
|
||||
params_with_doc, "missing-param-doc", self.not_needed_param_in_docstring
|
||||
)
|
||||
|
||||
for index, arg_name in enumerate(arguments_node.args):
|
||||
if arguments_node.annotations[index]:
|
||||
params_with_type.add(arg_name.name)
|
||||
|
||||
_compare_missing_args(
|
||||
params_with_type, "missing-type-doc", not_needed_type_in_docstring
|
||||
)
|
||||
|
||||
_compare_different_args(
|
||||
params_with_doc, "differing-param-doc", self.not_needed_param_in_docstring
|
||||
)
|
||||
_compare_different_args(
|
||||
params_with_type, "differing-type-doc", not_needed_type_in_docstring
|
||||
)
|
||||
|
||||
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))
|
@@ -0,0 +1,89 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2016-2017 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2016 Łukasz Rogalski <rogalski.91@gmail.com>
|
||||
# Copyright (c) 2016 Luis Escobar <lescobar@vauxoo.com>
|
||||
|
||||
# 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
|
||||
|
||||
import linecache
|
||||
|
||||
from pylint import checkers
|
||||
from pylint.interfaces import IAstroidChecker, HIGH
|
||||
from pylint.checkers.utils import check_messages
|
||||
|
||||
|
||||
class DocStringStyleChecker(checkers.BaseChecker):
|
||||
"""Checks format of docstrings based on PEP 0257"""
|
||||
|
||||
__implements__ = IAstroidChecker
|
||||
name = "docstyle"
|
||||
|
||||
msgs = {
|
||||
"C0198": (
|
||||
'Bad docstring quotes in %s, expected """, given %s',
|
||||
"bad-docstring-quotes",
|
||||
"Used when a docstring does not have triple double quotes.",
|
||||
),
|
||||
"C0199": (
|
||||
"First line empty in %s docstring",
|
||||
"docstring-first-line-empty",
|
||||
"Used when a blank line is found at the beginning of a docstring.",
|
||||
),
|
||||
}
|
||||
|
||||
@check_messages("docstring-first-line-empty", "bad-docstring-quotes")
|
||||
def visit_module(self, node):
|
||||
self._check_docstring("module", node)
|
||||
|
||||
def visit_classdef(self, node):
|
||||
self._check_docstring("class", node)
|
||||
|
||||
def visit_functiondef(self, node):
|
||||
ftype = "method" if node.is_method() else "function"
|
||||
self._check_docstring(ftype, node)
|
||||
|
||||
visit_asyncfunctiondef = visit_functiondef
|
||||
|
||||
def _check_docstring(self, node_type, node):
|
||||
docstring = node.doc
|
||||
if docstring and docstring[0] == "\n":
|
||||
self.add_message(
|
||||
"docstring-first-line-empty",
|
||||
node=node,
|
||||
args=(node_type,),
|
||||
confidence=HIGH,
|
||||
)
|
||||
|
||||
# Use "linecache", instead of node.as_string(), because the latter
|
||||
# looses the original form of the docstrings.
|
||||
|
||||
if docstring:
|
||||
lineno = node.fromlineno + 1
|
||||
line = linecache.getline(node.root().file, lineno).lstrip()
|
||||
if line and line.find('"""') == 0:
|
||||
return
|
||||
if line and "'''" in line:
|
||||
quotes = "'''"
|
||||
elif line and line[0] == '"':
|
||||
quotes = '"'
|
||||
elif line and line[0] == "'":
|
||||
quotes = "'"
|
||||
else:
|
||||
quotes = False
|
||||
if quotes:
|
||||
self.add_message(
|
||||
"bad-docstring-quotes",
|
||||
node=node,
|
||||
args=(node_type, quotes),
|
||||
confidence=HIGH,
|
||||
)
|
||||
|
||||
|
||||
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(DocStringStyleChecker(linter))
|
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2016 Alexander Todorov <atodorov@otb.bg>
|
||||
# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com>
|
||||
|
||||
# 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
|
||||
|
||||
"""Looks for comparisons to empty string."""
|
||||
|
||||
import itertools
|
||||
|
||||
import astroid
|
||||
|
||||
from pylint import interfaces
|
||||
from pylint import checkers
|
||||
from pylint.checkers import utils
|
||||
|
||||
|
||||
def _is_constant_empty_str(node):
|
||||
return isinstance(node, astroid.Const) and node.value == ""
|
||||
|
||||
|
||||
class CompareToEmptyStringChecker(checkers.BaseChecker):
|
||||
"""Checks for comparisons to empty string.
|
||||
Most of the times you should use the fact that empty strings are false.
|
||||
An exception to this rule is when an empty string value is allowed in the program
|
||||
and has a different meaning than None!
|
||||
"""
|
||||
|
||||
__implements__ = (interfaces.IAstroidChecker,)
|
||||
|
||||
# configuration section name
|
||||
name = "compare-to-empty-string"
|
||||
msgs = {
|
||||
"C1901": (
|
||||
"Avoid comparisons to empty string",
|
||||
"compare-to-empty-string",
|
||||
"Used when Pylint detects comparison to an empty string constant.",
|
||||
)
|
||||
}
|
||||
|
||||
priority = -2
|
||||
options = ()
|
||||
|
||||
@utils.check_messages("compare-to-empty-string")
|
||||
def visit_compare(self, node):
|
||||
_operators = ["!=", "==", "is not", "is"]
|
||||
# note: astroid.Compare has the left most operand in node.left
|
||||
# while the rest are a list of tuples in node.ops
|
||||
# the format of the tuple is ('compare operator sign', node)
|
||||
# here we squash everything into `ops` to make it easier for processing later
|
||||
ops = [("", node.left)]
|
||||
ops.extend(node.ops)
|
||||
ops = list(itertools.chain(*ops))
|
||||
|
||||
for ops_idx in range(len(ops) - 2):
|
||||
op_1 = ops[ops_idx]
|
||||
op_2 = ops[ops_idx + 1]
|
||||
op_3 = ops[ops_idx + 2]
|
||||
error_detected = False
|
||||
|
||||
# x ?? ""
|
||||
if _is_constant_empty_str(op_1) and op_2 in _operators:
|
||||
error_detected = True
|
||||
# '' ?? X
|
||||
elif op_2 in _operators and _is_constant_empty_str(op_3):
|
||||
error_detected = True
|
||||
|
||||
if error_detected:
|
||||
self.add_message("compare-to-empty-string", node=node)
|
||||
|
||||
|
||||
def register(linter):
|
||||
"""Required method to auto register this checker."""
|
||||
linter.register_checker(CompareToEmptyStringChecker(linter))
|
201
venv/lib/python3.6/site-packages/pylint/extensions/mccabe.py
Normal file
201
venv/lib/python3.6/site-packages/pylint/extensions/mccabe.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# Copyright (c) 2016-2017 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
|
||||
# Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com>
|
||||
|
||||
# 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
|
||||
|
||||
"""Module to add McCabe checker class for pylint. """
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from mccabe import (
|
||||
PathGraph as Mccabe_PathGraph,
|
||||
PathGraphingAstVisitor as Mccabe_PathGraphingAstVisitor,
|
||||
)
|
||||
from pylint import checkers
|
||||
from pylint.checkers.utils import check_messages
|
||||
from pylint.interfaces import HIGH, IAstroidChecker
|
||||
|
||||
|
||||
class PathGraph(Mccabe_PathGraph):
|
||||
def __init__(self, node):
|
||||
super(PathGraph, self).__init__(name="", entity="", lineno=1)
|
||||
self.root = node
|
||||
|
||||
|
||||
class PathGraphingAstVisitor(Mccabe_PathGraphingAstVisitor):
|
||||
def __init__(self):
|
||||
super(PathGraphingAstVisitor, self).__init__()
|
||||
self._bottom_counter = 0
|
||||
|
||||
def default(self, node, *args):
|
||||
for child in node.get_children():
|
||||
self.dispatch(child, *args)
|
||||
|
||||
def dispatch(self, node, *args):
|
||||
self.node = node
|
||||
klass = node.__class__
|
||||
meth = self._cache.get(klass)
|
||||
if meth is None:
|
||||
className = klass.__name__
|
||||
meth = getattr(self.visitor, "visit" + className, self.default)
|
||||
self._cache[klass] = meth
|
||||
return meth(node, *args)
|
||||
|
||||
def visitFunctionDef(self, node):
|
||||
if self.graph is not None:
|
||||
# closure
|
||||
pathnode = self._append_node(node)
|
||||
self.tail = pathnode
|
||||
self.dispatch_list(node.body)
|
||||
bottom = "%s" % self._bottom_counter
|
||||
self._bottom_counter += 1
|
||||
self.graph.connect(self.tail, bottom)
|
||||
self.graph.connect(node, bottom)
|
||||
self.tail = bottom
|
||||
else:
|
||||
self.graph = PathGraph(node)
|
||||
self.tail = node
|
||||
self.dispatch_list(node.body)
|
||||
self.graphs["%s%s" % (self.classname, node.name)] = self.graph
|
||||
self.reset()
|
||||
|
||||
visitAsyncFunctionDef = visitFunctionDef
|
||||
|
||||
def visitSimpleStatement(self, node):
|
||||
self._append_node(node)
|
||||
|
||||
visitAssert = (
|
||||
visitAssign
|
||||
) = (
|
||||
visitAugAssign
|
||||
) = (
|
||||
visitDelete
|
||||
) = (
|
||||
visitPrint
|
||||
) = (
|
||||
visitRaise
|
||||
) = (
|
||||
visitYield
|
||||
) = (
|
||||
visitImport
|
||||
) = (
|
||||
visitCall
|
||||
) = (
|
||||
visitSubscript
|
||||
) = (
|
||||
visitPass
|
||||
) = (
|
||||
visitContinue
|
||||
) = (
|
||||
visitBreak
|
||||
) = visitGlobal = visitReturn = visitExpr = visitAwait = visitSimpleStatement
|
||||
|
||||
def visitWith(self, node):
|
||||
self._append_node(node)
|
||||
self.dispatch_list(node.body)
|
||||
|
||||
visitAsyncWith = visitWith
|
||||
|
||||
def _append_node(self, node):
|
||||
if not self.tail:
|
||||
return None
|
||||
self.graph.connect(self.tail, node)
|
||||
self.tail = node
|
||||
return node
|
||||
|
||||
def _subgraph(self, node, name, extra_blocks=()):
|
||||
"""create the subgraphs representing any `if` and `for` statements"""
|
||||
if self.graph is None:
|
||||
# global loop
|
||||
self.graph = PathGraph(node)
|
||||
self._subgraph_parse(node, node, extra_blocks)
|
||||
self.graphs["%s%s" % (self.classname, name)] = self.graph
|
||||
self.reset()
|
||||
else:
|
||||
self._append_node(node)
|
||||
self._subgraph_parse(node, node, extra_blocks)
|
||||
|
||||
def _subgraph_parse(
|
||||
self, node, pathnode, extra_blocks
|
||||
): # pylint: disable=unused-argument
|
||||
"""parse the body and any `else` block of `if` and `for` statements"""
|
||||
loose_ends = []
|
||||
self.tail = node
|
||||
self.dispatch_list(node.body)
|
||||
loose_ends.append(self.tail)
|
||||
for extra in extra_blocks:
|
||||
self.tail = node
|
||||
self.dispatch_list(extra.body)
|
||||
loose_ends.append(self.tail)
|
||||
if node.orelse:
|
||||
self.tail = node
|
||||
self.dispatch_list(node.orelse)
|
||||
loose_ends.append(self.tail)
|
||||
else:
|
||||
loose_ends.append(node)
|
||||
if node:
|
||||
bottom = "%s" % self._bottom_counter
|
||||
self._bottom_counter += 1
|
||||
for le in loose_ends:
|
||||
self.graph.connect(le, bottom)
|
||||
self.tail = bottom
|
||||
|
||||
|
||||
class McCabeMethodChecker(checkers.BaseChecker):
|
||||
"""Checks McCabe complexity cyclomatic threshold in methods and functions
|
||||
to validate a too complex code.
|
||||
"""
|
||||
|
||||
__implements__ = IAstroidChecker
|
||||
name = "design"
|
||||
|
||||
msgs = {
|
||||
"R1260": (
|
||||
"%s is too complex. The McCabe rating is %d",
|
||||
"too-complex",
|
||||
"Used when a method or function is too complex based on "
|
||||
"McCabe Complexity Cyclomatic",
|
||||
)
|
||||
}
|
||||
options = (
|
||||
(
|
||||
"max-complexity",
|
||||
{
|
||||
"default": 10,
|
||||
"type": "int",
|
||||
"metavar": "<int>",
|
||||
"help": "McCabe complexity cyclomatic threshold",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@check_messages("too-complex")
|
||||
def visit_module(self, node):
|
||||
"""visit an astroid.Module node to check too complex rating and
|
||||
add message if is greather than max_complexity stored from options"""
|
||||
visitor = PathGraphingAstVisitor()
|
||||
for child in node.body:
|
||||
visitor.preorder(child, visitor)
|
||||
for graph in visitor.graphs.values():
|
||||
complexity = graph.complexity()
|
||||
node = graph.root
|
||||
if hasattr(node, "name"):
|
||||
node_name = "'%s'" % node.name
|
||||
else:
|
||||
node_name = "This '%s'" % node.__class__.__name__.lower()
|
||||
if complexity <= self.config.max_complexity:
|
||||
continue
|
||||
self.add_message(
|
||||
"too-complex", node=node, confidence=HIGH, args=(node_name, complexity)
|
||||
)
|
||||
|
||||
|
||||
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(McCabeMethodChecker(linter))
|
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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
|
||||
|
||||
"""Looks for overlapping exceptions."""
|
||||
|
||||
import astroid
|
||||
|
||||
from pylint import interfaces
|
||||
from pylint import checkers
|
||||
from pylint.checkers import utils
|
||||
|
||||
from pylint.checkers.exceptions import _annotated_unpack_infer
|
||||
|
||||
|
||||
class OverlappingExceptionsChecker(checkers.BaseChecker):
|
||||
"""Checks for two or more exceptions in the same exception handler
|
||||
clause that are identical or parts of the same inheritance hierarchy
|
||||
(i.e. overlapping)."""
|
||||
|
||||
__implements__ = interfaces.IAstroidChecker
|
||||
|
||||
name = "overlap-except"
|
||||
msgs = {
|
||||
"W0714": (
|
||||
"Overlapping exceptions (%s)",
|
||||
"overlapping-except",
|
||||
"Used when exceptions in handler overlap or are identical",
|
||||
)
|
||||
}
|
||||
priority = -2
|
||||
options = ()
|
||||
|
||||
@utils.check_messages("overlapping-except")
|
||||
def visit_tryexcept(self, node):
|
||||
"""check for empty except"""
|
||||
for handler in node.handlers:
|
||||
if handler.type is None:
|
||||
continue
|
||||
if isinstance(handler.type, astroid.BoolOp):
|
||||
continue
|
||||
try:
|
||||
excs = list(_annotated_unpack_infer(handler.type))
|
||||
except astroid.InferenceError:
|
||||
continue
|
||||
|
||||
handled_in_clause = []
|
||||
for part, exc in excs:
|
||||
if exc is astroid.Uninferable:
|
||||
continue
|
||||
if isinstance(exc, astroid.Instance) and utils.inherit_from_std_ex(exc):
|
||||
# pylint: disable=protected-access
|
||||
exc = exc._proxied
|
||||
|
||||
if not isinstance(exc, astroid.ClassDef):
|
||||
continue
|
||||
|
||||
exc_ancestors = [
|
||||
anc for anc in exc.ancestors() if isinstance(anc, astroid.ClassDef)
|
||||
]
|
||||
|
||||
for prev_part, prev_exc in handled_in_clause:
|
||||
prev_exc_ancestors = [
|
||||
anc
|
||||
for anc in prev_exc.ancestors()
|
||||
if isinstance(anc, astroid.ClassDef)
|
||||
]
|
||||
if exc == prev_exc:
|
||||
self.add_message(
|
||||
"overlapping-except",
|
||||
node=handler.type,
|
||||
args="%s and %s are the same"
|
||||
% (prev_part.as_string(), part.as_string()),
|
||||
)
|
||||
elif prev_exc in exc_ancestors or exc in prev_exc_ancestors:
|
||||
ancestor = part if exc in prev_exc_ancestors else prev_part
|
||||
descendant = part if prev_exc in exc_ancestors else prev_part
|
||||
self.add_message(
|
||||
"overlapping-except",
|
||||
node=handler.type,
|
||||
args="%s is an ancestor class of %s"
|
||||
% (ancestor.as_string(), descendant.as_string()),
|
||||
)
|
||||
handled_in_clause += [(part, exc)]
|
||||
|
||||
|
||||
def register(linter):
|
||||
"""Required method to auto register this checker."""
|
||||
linter.register_checker(OverlappingExceptionsChecker(linter))
|
@@ -0,0 +1,116 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2016-2017 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2016 Glenn Matthews <glmatthe@cisco.com>
|
||||
# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
|
||||
# Copyright (c) 2018 Ville Skyttä <ville.skytta@upcloud.com>
|
||||
|
||||
# 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
|
||||
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.checkers.utils import check_messages, is_none, node_type
|
||||
from pylint.interfaces import IAstroidChecker
|
||||
|
||||
|
||||
BUILTINS = "builtins"
|
||||
|
||||
|
||||
class MultipleTypesChecker(BaseChecker):
|
||||
"""Checks for variable type redefinitions (NoneType excepted)
|
||||
|
||||
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,
|
||||
ifexpr, etc. Also it would be great to have support for inference on
|
||||
str.split()
|
||||
"""
|
||||
|
||||
__implements__ = IAstroidChecker
|
||||
|
||||
name = "multiple_types"
|
||||
msgs = {
|
||||
"R0204": (
|
||||
"Redefinition of %s type from %s to %s",
|
||||
"redefined-variable-type",
|
||||
"Used when the type of a variable changes inside a "
|
||||
"method or a function.",
|
||||
)
|
||||
}
|
||||
|
||||
def visit_classdef(self, _):
|
||||
self._assigns.append({})
|
||||
|
||||
@check_messages("redefined-variable-type")
|
||||
def leave_classdef(self, _):
|
||||
self._check_and_add_messages()
|
||||
|
||||
visit_functiondef = visit_classdef
|
||||
leave_functiondef = leave_module = leave_classdef
|
||||
|
||||
def visit_module(self, _):
|
||||
self._assigns = [{}]
|
||||
|
||||
def _check_and_add_messages(self):
|
||||
assigns = self._assigns.pop()
|
||||
for name, args in assigns.items():
|
||||
if len(args) <= 1:
|
||||
continue
|
||||
orig_node, orig_type = args[0]
|
||||
# Check if there is a type in the following nodes that would be
|
||||
# different from orig_type.
|
||||
for redef_node, redef_type in args[1:]:
|
||||
if redef_type == orig_type:
|
||||
continue
|
||||
# if a variable is defined to several types in an if node,
|
||||
# this is not actually redefining.
|
||||
orig_parent = orig_node.parent
|
||||
redef_parent = redef_node.parent
|
||||
if isinstance(orig_parent, astroid.If):
|
||||
if orig_parent == redef_parent:
|
||||
if (
|
||||
redef_node in orig_parent.orelse
|
||||
and orig_node not in orig_parent.orelse
|
||||
):
|
||||
orig_node, orig_type = redef_node, redef_type
|
||||
continue
|
||||
elif isinstance(
|
||||
redef_parent, astroid.If
|
||||
) and redef_parent in orig_parent.nodes_of_class(astroid.If):
|
||||
orig_node, orig_type = redef_node, redef_type
|
||||
continue
|
||||
orig_type = orig_type.replace(BUILTINS + ".", "")
|
||||
redef_type = redef_type.replace(BUILTINS + ".", "")
|
||||
self.add_message(
|
||||
"redefined-variable-type",
|
||||
node=redef_node,
|
||||
args=(name, orig_type, redef_type),
|
||||
)
|
||||
break
|
||||
|
||||
def visit_assign(self, node):
|
||||
# we don't handle multiple assignment nor slice assignment
|
||||
target = node.targets[0]
|
||||
if isinstance(target, (astroid.Tuple, astroid.Subscript)):
|
||||
return
|
||||
# ignore NoneType
|
||||
if is_none(node):
|
||||
return
|
||||
_type = node_type(node.value)
|
||||
if _type:
|
||||
self._assigns[-1].setdefault(target.as_string(), []).append(
|
||||
(node, _type.pytype())
|
||||
)
|
||||
|
||||
|
||||
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(MultipleTypesChecker(linter))
|
Reference in New Issue
Block a user