From 26c9042825a6549c9f889b2cd17b5e08ba784ab2 Mon Sep 17 00:00:00 2001 From: kasium <15907922+kasium@users.noreply.github.com> Date: Wed, 15 Dec 2021 00:19:44 +0100 Subject: Enable missing-raises-doc to understand class hierarchies (#5278) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, missing-raises-doc could not understand class hierarchies but just compared names. So, when a method documented a raise of a parent class, but a child exception was raised, the check failed. With this change, if the name compare doesn't help, the exception class hierarchy is checked. For that, possible_exc_types was changed to return classes instead of names Resolved #4955 Co-authored-by: Pierre Sassoulas Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> --- pylint/extensions/_check_docs_utils.py | 29 +++++++++++++++-------------- pylint/extensions/docparams.py | 15 +++++++++++++-- 2 files changed, 28 insertions(+), 16 deletions(-) (limited to 'pylint') diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index bfab0e1e4..2a26fe0e2 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -120,7 +120,7 @@ def _split_multiple_exc_types(target: str) -> List[str]: return re.split(delimiters, target) -def possible_exc_types(node): +def possible_exc_types(node: nodes.NodeNG) -> Set[nodes.ClassDef]: """ Gets all of the possible raised exception types for the given raise node. @@ -130,28 +130,30 @@ def possible_exc_types(node): :param node: The raise node to find exception types for. - :type node: nodes.NodeNG :returns: A list of exception types possibly raised by :param:`node`. - :rtype: set(str) """ excs = [] if isinstance(node.exc, nodes.Name): inferred = utils.safe_infer(node.exc) if inferred: - excs = [inferred.name] + excs = [inferred] elif node.exc is None: handler = node.parent while handler and not isinstance(handler, nodes.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) + try: + for exc in astroid.unpack_infer(handler.type): + if exc is not astroid.Uninferable: + excs.append(exc) + except astroid.InferenceError: + pass else: target = _get_raise_target(node) if isinstance(target, nodes.ClassDef): - excs = [target.name] + excs = [target] elif isinstance(target, nodes.FunctionDef): for ret in target.nodes_of_class(nodes.Return): if ret.frame() != target: @@ -159,15 +161,14 @@ def possible_exc_types(node): continue val = utils.safe_infer(ret.value) - if ( - val - and isinstance(val, (astroid.Instance, nodes.ClassDef)) - and utils.inherit_from_std_ex(val) - ): - excs.append(val.name) + if val and utils.inherit_from_std_ex(val): + if isinstance(val, nodes.ClassDef): + excs.append(val) + elif isinstance(val, astroid.Instance): + excs.append(val.getattr("__class__")[0]) try: - return {exc for exc in excs if not utils.node_ignores_exception(node, exc)} + return {exc for exc in excs if not utils.node_ignores_exception(node, exc.name)} except astroid.InferenceError: return set() diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index e7b293988..a0c66f16c 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -309,14 +309,25 @@ class DocstringParameterChecker(BaseChecker): doc = utils.docstringify(func_node.doc, self.config.default_docstring_type) if not doc.matching_sections(): if doc.doc: - self._handle_no_raise_doc(expected_excs, func_node) + missing = {exc.name for exc in expected_excs} + self._handle_no_raise_doc(missing, 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 + + missing_excs = set() + for expected in expected_excs: + for found_exc in found_excs_class_names: + if found_exc == expected.name: + break + if any(found_exc == ancestor.name for ancestor in expected.ancestors()): + break + else: + missing_excs.add(expected.name) + self._add_raise_message(missing_excs, func_node) def visit_return(self, node: nodes.Return) -> None: -- cgit v1.2.1