Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ignoring nested classes #73

Merged
merged 1 commit into from
May 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ Configure within your ``pyproject.toml`` (``interrogate`` will automatically det
ignore-private = false
ignore-property-decorators = false
ignore-module = false
ignore-nested-functions = false
ignore-nested-classes = true
fail-under = 95
exclude = ["setup.py", "docs", "build"]
ignore-regex = ["^get$", "^mock_.*", ".*BaseClass.*"]
Expand Down Expand Up @@ -379,6 +381,8 @@ Or configure within your ``setup.cfg`` (``interrogate`` will automatically detec
ignore-private = false
ignore-property-decorators = false
ignore-module = false
ignore-nested-functions = false
ignore-nested-classes = true
fail-under = 95
exclude = setup.py,docs,build
ignore-regex = ^get$,^mock_.*,.*BaseClass.*
Expand Down Expand Up @@ -436,6 +440,8 @@ To view all options available, run ``interrogate --help``:
-n, --ignore-nested-functions Ignore nested functions and methods.
[default: False]

-C, --ignore-nested-classes Ignore nested classes. [default: False]

-p, --ignore-private Ignore private classes, methods, and
functions starting with two underscores.
[default: False]
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Added
^^^^^

* Support for generating the status badge as a PNG file with a new ``--badge-format`` flag (`#70 <https://github.com/econchick/interrogate/issues/70>`_).
* Add new option ``-C`` / ``--ignore-nested-classess` to ignore – you guessed it – nested classes (`#65 <https://github.com/econchick/interrogate/issues/65>`_).

.. short-log

Expand Down
4 changes: 4 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ Command Line Options

Ignore nested functions and methods. [default: ``False``]

.. option:: -C, --ignore-nested-classes

Ignore nested classes. [default: ``False``]

.. option:: -p, --ignore-private

Ignore private classes, methods, and functions starting with two underscores. [default: ``False``]
Expand Down
2 changes: 1 addition & 1 deletion src/interrogate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2020 Lynn Root
"""Explain yourself! Interrogate a codebase for docstring coverage."""
__author__ = "Lynn Root"
__version__ = "1.4.0.dev1"
__version__ = "1.4.0.dev2"
__email__ = "[email protected]"
__description__ = "Interrogate a codebase for docstring coverage."
__uri__ = "https://interrogate.readthedocs.io"
10 changes: 10 additions & 0 deletions src/interrogate/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@
show_default=True,
help="Ignore nested functions and methods.",
)
@click.option(
"-C",
"--ignore-nested-classes",
is_flag=True,
default=False,
show_default=True,
help="Ignore nested classes.",
)
@click.option(
"-p",
"--ignore-private",
Expand Down Expand Up @@ -243,6 +251,7 @@ def main(ctx, paths, **kwargs):
.. versionadded:: 1.3.0 ``--ignore-property-decorators``
.. versionadded:: 1.3.0 config parsing support for setup.cfg
.. versionadded:: 1.4.0 ``--badge-format``
.. versionadded:: 1.4.0 ``--ignore-nested-classes``

.. versionchanged:: 1.3.1 only generate badge if results change from
an existing badge.
Expand Down Expand Up @@ -271,6 +280,7 @@ def main(ctx, paths, **kwargs):
ignore_magic=kwargs["ignore_magic"],
ignore_module=kwargs["ignore_module"],
ignore_nested_functions=kwargs["ignore_nested_functions"],
ignore_nested_classes=kwargs["ignore_nested_classes"],
ignore_property_decorators=kwargs["ignore_property_decorators"],
ignore_private=kwargs["ignore_private"],
ignore_regex=kwargs["ignore_regex"],
Expand Down
1 change: 1 addition & 0 deletions src/interrogate/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class InterrogateConfig:
ignore_semiprivate = attr.ib(default=False)
ignore_init_method = attr.ib(default=False)
ignore_init_module = attr.ib(default=False)
ignore_nested_classes = attr.ib(default=False)
ignore_nested_functions = attr.ib(default=False)
ignore_property_decorators = attr.ib(default=False)
include_regex = attr.ib(default=False)
Expand Down
11 changes: 11 additions & 0 deletions src/interrogate/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,15 @@ def _filter_nodes(self, nodes):
filtered.insert(0, module_node)
return filtered

def _filter_inner_nested(self, nodes):
"""Filter out children of ignored nested funcs/classes."""
nested_cls = [n for n in nodes if n.is_nested_cls]
inner_nested_nodes = [n for n in nodes if n.parent in nested_cls]

filtered_nodes = [n for n in nodes if n not in inner_nested_nodes]
filtered_nodes = [n for n in filtered_nodes if n not in nested_cls]
return filtered_nodes

def _get_file_coverage(self, filename):
"""Get coverage results for a particular file."""
with open(filename, "r", encoding="utf-8") as f:
Expand All @@ -207,6 +216,8 @@ def _get_file_coverage(self, filename):
filtered_nodes = [
n for n in filtered_nodes if not n.is_nested_func
]
if self.config.ignore_nested_classes:
filtered_nodes = self._filter_inner_nested(filtered_nodes)

results = InterrogateFileResult(
filename=filename,
Expand Down
21 changes: 18 additions & 3 deletions src/interrogate/visit.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class CovNode:
"function").
:param bool is_nested_func: if the node itself is a nested function
or method.
:param bool is_nested_cls: if the node itself is a nested class.
:param CovNode _parent: parent node of current CovNode, if any.
"""

Expand All @@ -36,7 +37,8 @@ class CovNode:
covered = attr.ib()
node_type = attr.ib()
is_nested_func = attr.ib()
_parent = attr.ib()
is_nested_cls = attr.ib()
parent = attr.ib()


class CoverageVisitor(ast.NodeVisitor):
Expand Down Expand Up @@ -96,8 +98,9 @@ def _visit_helper(self, node):
level=len(self.stack),
node_type=node_type,
lineno=lineno,
is_nested_func=self._is_nested_func(parent, node_type),
is_nested_cls=self._is_nested_cls(parent, node_type),
parent=parent,
is_nested_func=self._is_nested(parent, node_type),
)
self.stack.append(cov_node)
self.nodes.append(cov_node)
Expand All @@ -106,7 +109,7 @@ def _visit_helper(self, node):

self.stack.pop()

def _is_nested(self, parent, node_type):
def _is_nested_func(self, parent, node_type):
"""Is node a nested func/method of another func/method."""
if parent is None:
return False
Expand All @@ -115,6 +118,18 @@ def _is_nested(self, parent, node_type):
return True
return False

def _is_nested_cls(self, parent, node_type):
"""Is node a nested func/method of another func/method."""
if parent is None:
return False
# is it a nested class?
if (
parent.node_type in ("ClassDef", "FunctionDef")
and node_type == "ClassDef"
):
return True
return False

def _is_private(self, node):
"""Is node private (i.e. __MyClass, __my_func)."""
if node.name.endswith("__"):
Expand Down
6 changes: 6 additions & 0 deletions tests/functional/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def test_run_no_paths(runner, monkeypatch, tmpdir):
(["-m"], 46.2, 1),
# ignore init method docs
(["-i"], 45.3, 1),
# ignore nested funcs
(["-n"], 45.3, 1),
# ignore nested classes
(["-C"], 47.2, 1),
# ignore regex
(["-r", "^get$"], 46.2, 1),
# whitelist regex
Expand Down Expand Up @@ -88,6 +92,8 @@ def test_run_shortflags(flags, exp_result, exp_exit_code, runner):
(["--ignore-property-decorators"], 46.2, 1),
(["--ignore-magic"], 46.2, 1),
(["--ignore-init-method"], 45.3, 1),
(["--ignore-nested-functions"], 45.3, 1),
(["--ignore-nested-classes"], 47.2, 1),
(["--ignore-regex", "^get$"], 46.2, 1),
(["--whitelist-regex", "^get$"], 50.0, 1),
(["--exclude", os.path.join(SAMPLE_DIR, "partial.py")], 55.9, 1),
Expand Down