Skip to content

Commit

Permalink
[refactor] Create a file for the DocstringChecker in pylint.checker.base
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Sassoulas committed Mar 24, 2022
1 parent 84d22cf commit 6940715
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 189 deletions.
191 changes: 2 additions & 189 deletions pylint/checkers/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@
from pylint.checkers import utils
from pylint.checkers.base.basic_checker import _BasicChecker
from pylint.checkers.base.comparison_checker import ComparisonChecker
from pylint.checkers.base.docstring_checker import DocStringChecker
from pylint.checkers.base.pass_checker import PassChecker
from pylint.checkers.utils import (
infer_all,
is_overload_stub,
is_property_deleter,
is_property_setter,
)
from pylint.checkers.utils import infer_all, is_property_deleter, is_property_setter
from pylint.reporters.ureports import nodes as reporter_nodes
from pylint.utils import LinterStats
from pylint.utils.utils import get_global_option
Expand Down Expand Up @@ -132,8 +128,6 @@ class AnyStyle(NamingStyle):
)
}

# do not require a doc string on private/system methods
NO_REQUIRED_DOC_RGX = re.compile("^_")
REVERSED_PROTOCOL_METHOD = "__reversed__"
SEQUENCE_PROTOCOL_METHODS = ("__getitem__", "__len__")
REVERSED_METHODS = (SEQUENCE_PROTOCOL_METHODS, (REVERSED_PROTOCOL_METHOD,))
Expand Down Expand Up @@ -2146,187 +2140,6 @@ def _check_typevar_variance(self, name: str, node: nodes.AssignName) -> None:
)


class DocStringChecker(_BasicChecker):
msgs = {
"C0112": (
"Empty %s docstring",
"empty-docstring",
"Used when a module, function, class or method has an empty "
"docstring (it would be too easy ;).",
{"old_names": [("W0132", "old-empty-docstring")]},
),
"C0114": (
"Missing module docstring",
"missing-module-docstring",
"Used when a module has no docstring."
"Empty modules do not require a docstring.",
{"old_names": [("C0111", "missing-docstring")]},
),
"C0115": (
"Missing class docstring",
"missing-class-docstring",
"Used when a class has no docstring."
"Even an empty class must have a docstring.",
{"old_names": [("C0111", "missing-docstring")]},
),
"C0116": (
"Missing function or method docstring",
"missing-function-docstring",
"Used when a function or method has no docstring."
"Some special methods like __init__ do not require a "
"docstring.",
{"old_names": [("C0111", "missing-docstring")]},
),
}
options = (
(
"no-docstring-rgx",
{
"default": NO_REQUIRED_DOC_RGX,
"type": "regexp",
"metavar": "<regexp>",
"help": "Regular expression which should only match "
"function or class names that do not require a "
"docstring.",
},
),
(
"docstring-min-length",
{
"default": -1,
"type": "int",
"metavar": "<int>",
"help": (
"Minimum line length for functions/classes that"
" require docstrings, shorter ones are exempt."
),
},
),
)

def open(self):
self.linter.stats.reset_undocumented()

@utils.check_messages("missing-docstring", "empty-docstring")
def visit_module(self, node: nodes.Module) -> None:
self._check_docstring("module", node)

@utils.check_messages("missing-docstring", "empty-docstring")
def visit_classdef(self, node: nodes.ClassDef) -> None:
if self.config.no_docstring_rgx.match(node.name) is None:
self._check_docstring("class", node)

@utils.check_messages("missing-docstring", "empty-docstring")
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
if self.config.no_docstring_rgx.match(node.name) is None:
ftype = "method" if node.is_method() else "function"
if (
is_property_setter(node)
or is_property_deleter(node)
or is_overload_stub(node)
):
return

if isinstance(node.parent.frame(future=True), nodes.ClassDef):
overridden = False
confidence = (
interfaces.INFERENCE
if utils.has_known_bases(node.parent.frame(future=True))
else interfaces.INFERENCE_FAILURE
)
# check if node is from a method overridden by its ancestor
for ancestor in node.parent.frame(future=True).ancestors():
if ancestor.qname() == "builtins.object":
continue
if node.name in ancestor and isinstance(
ancestor[node.name], nodes.FunctionDef
):
overridden = True
break
self._check_docstring(
ftype, node, report_missing=not overridden, confidence=confidence # type: ignore[arg-type]
)
elif isinstance(node.parent.frame(future=True), nodes.Module):
self._check_docstring(ftype, node) # type: ignore[arg-type]
else:
return

visit_asyncfunctiondef = visit_functiondef

def _check_docstring(
self,
node_type: Literal["class", "function", "method", "module"],
node,
report_missing=True,
confidence=interfaces.HIGH,
):
"""Check if the node has a non-empty docstring."""
docstring = node.doc_node.value if node.doc_node else None
if docstring is None:
docstring = _infer_dunder_doc_attribute(node)

if docstring is None:
if not report_missing:
return
lines = utils.get_node_last_lineno(node) - node.lineno

if node_type == "module" and not lines:
# If the module does not have a body, there's no reason
# to require a docstring.
return
max_lines = self.config.docstring_min_length

if node_type != "module" and max_lines > -1 and lines < max_lines:
return
if node_type == "class":
self.linter.stats.undocumented["klass"] += 1
else:
self.linter.stats.undocumented[node_type] += 1
if (
node.body
and isinstance(node.body[0], nodes.Expr)
and isinstance(node.body[0].value, nodes.Call)
):
# Most likely a string with a format call. Let's see.
func = utils.safe_infer(node.body[0].value.func)
if isinstance(func, astroid.BoundMethod) and isinstance(
func.bound, astroid.Instance
):
# Strings.
if func.bound.name in {"str", "unicode", "bytes"}:
return
if node_type == "module":
message = "missing-module-docstring"
elif node_type == "class":
message = "missing-class-docstring"
else:
message = "missing-function-docstring"
self.add_message(message, node=node, confidence=confidence)
elif not docstring.strip():
if node_type == "class":
self.linter.stats.undocumented["klass"] += 1
else:
self.linter.stats.undocumented[node_type] += 1
self.add_message(
"empty-docstring", node=node, args=(node_type,), confidence=confidence
)


def _infer_dunder_doc_attribute(node):
# Try to see if we have a `__doc__` attribute.
try:
docstring = node["__doc__"]
except KeyError:
return None

docstring = utils.safe_infer(docstring)
if not docstring:
return None
if not isinstance(docstring, nodes.Const):
return None
return docstring.value


def register(linter: "PyLinter") -> None:
linter.register_checker(BasicErrorChecker(linter))
linter.register_checker(BasicChecker(linter))
Expand Down
Loading

0 comments on commit 6940715

Please sign in to comment.