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 Pyreverse option to exclude standalone nodes #8520

Merged
merged 5 commits into from
Apr 2, 2023
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
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/8476.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add Pyreverse option to exclude standalone nodes from diagrams with `--no-standalone`.

Closes #8476
8 changes: 8 additions & 0 deletions pylint/pyreverse/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@
"help": "don't show attributes and methods in the class boxes; this disables -f values",
},
),
(
"no-standalone",
{
"action": "store_true",
"default": False,
"help": "only show nodes with connections",
},
),
(
"output",
{
Expand Down
13 changes: 13 additions & 0 deletions pylint/pyreverse/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ def write_packages(self, diagram: PackageDiagram) -> None:
# sorted to get predictable (hence testable) results
for module in sorted(diagram.modules(), key=lambda x: x.title):
module.fig_id = module.node.qname()
if self.config.no_standalone and not any(
module in (rel.from_object, rel.to_object)
for rel in diagram.get_relationships("depends")
):
continue

self.printer.emit_node(
module.fig_id,
type_=NodeType.PACKAGE,
Expand All @@ -75,6 +81,13 @@ def write_classes(self, diagram: ClassDiagram) -> None:
# sorted to get predictable (hence testable) results
for obj in sorted(diagram.objects, key=lambda x: x.title): # type: ignore[no-any-return]
obj.fig_id = obj.node.qname()
if self.config.no_standalone and not any(
obj in (rel.from_object, rel.to_object)
for rel_type in ("specialization", "association", "aggregation")
for rel in diagram.get_relationships(rel_type)
):
continue

self.printer.emit_node(
obj.fig_id,
type_=NodeType.CLASS,
Expand Down
2 changes: 2 additions & 0 deletions pylint/testutils/pyreverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(
all_ancestors: bool | None = None,
show_associated: int | None = None,
all_associated: bool | None = None,
no_standalone: bool = False,
show_builtin: bool = False,
show_stdlib: bool = False,
module_names: bool | None = None,
Expand All @@ -59,6 +60,7 @@ def __init__(
self.all_ancestors = all_ancestors
self.show_associated = show_associated
self.all_associated = all_associated
self.no_standalone = no_standalone
self.show_builtin = show_builtin
self.show_stdlib = show_stdlib
self.module_names = module_names
Expand Down
8 changes: 8 additions & 0 deletions tests/pyreverse/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ def colorized_dot_config() -> PyreverseConfig:
)


@pytest.fixture()
def no_standalone_dot_config() -> PyreverseConfig:
return PyreverseConfig(
output_format="dot",
no_standalone=True,
)


@pytest.fixture()
def puml_config() -> PyreverseConfig:
return PyreverseConfig(
Expand Down
12 changes: 12 additions & 0 deletions tests/pyreverse/data/classes_no_standalone.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
digraph "classes_no_standalone" {
rankdir=BT
charset="utf-8"
"data.clientmodule_test.Ancestor" [color="black", fontcolor="black", label=<{Ancestor|attr : str<br ALIGN="LEFT"/>cls_member<br ALIGN="LEFT"/>|get_value()<br ALIGN="LEFT"/>set_value(value)<br ALIGN="LEFT"/>}>, shape="record", style="solid"];
"data.suppliermodule_test.DoNothing" [color="black", fontcolor="black", label=<{DoNothing|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
"data.suppliermodule_test.DoNothing2" [color="black", fontcolor="black", label=<{DoNothing2|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
"data.clientmodule_test.Specialization" [color="black", fontcolor="black", label=<{Specialization|TYPE : str<br ALIGN="LEFT"/>relation<br ALIGN="LEFT"/>relation2<br ALIGN="LEFT"/>top : str<br ALIGN="LEFT"/>|from_value(value: int)<br ALIGN="LEFT"/>increment_value(): None<br ALIGN="LEFT"/>transform_value(value: int): int<br ALIGN="LEFT"/>}>, shape="record", style="solid"];
"data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"];
"data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Ancestor" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"];
"data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Specialization" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"];
"data.suppliermodule_test.DoNothing2" -> "data.clientmodule_test.Specialization" [arrowhead="odiamond", arrowtail="none", fontcolor="green", label="relation2", style="solid"];
}
7 changes: 7 additions & 0 deletions tests/pyreverse/data/packages_no_standalone.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
digraph "packages_no_standalone" {
rankdir=BT
charset="utf-8"
"data.clientmodule_test" [color="black", label=<data.clientmodule_test>, shape="box", style="solid"];
"data.suppliermodule_test" [color="black", label=<data.suppliermodule_test>, shape="box", style="solid"];
"data.clientmodule_test" -> "data.suppliermodule_test" [arrowhead="open", arrowtail="none"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
classDiagram
class A {
}
class B {
}
B --|> A
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class A: pass

class B(A): pass

class C: pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[testoptions]
command_line_args=--no-standalone
18 changes: 18 additions & 0 deletions tests/pyreverse/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"show_stdlib": False,
"only_classnames": False,
"output_directory": "",
"no_standalone": False,
}

TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data")
Expand All @@ -45,6 +46,7 @@
COLORIZED_PUML_FILES = ["packages_colorized.puml", "classes_colorized.puml"]
MMD_FILES = ["packages_No_Name.mmd", "classes_No_Name.mmd"]
HTML_FILES = ["packages_No_Name.html", "classes_No_Name.html"]
NO_STANDALONE_FILES = ["classes_no_standalone.dot", "packages_no_standalone.dot"]


class Config:
Expand Down Expand Up @@ -87,6 +89,15 @@ def setup_colorized_dot(
yield from _setup(project, colorized_dot_config, writer)


@pytest.fixture()
def setup_no_standalone_dot(
no_standalone_dot_config: PyreverseConfig, get_project: GetProjectCallable
) -> Iterator[None]:
writer = DiagramWriter(no_standalone_dot_config)
project = get_project(TEST_DATA_DIR, name="no_standalone")
yield from _setup(project, no_standalone_dot_config, writer)


@pytest.fixture()
def setup_puml(
puml_config: PyreverseConfig, get_project: GetProjectCallable
Expand Down Expand Up @@ -138,6 +149,7 @@ def _setup(
for fname in (
DOT_FILES
+ COLORIZED_DOT_FILES
+ NO_STANDALONE_FILES
+ PUML_FILES
+ COLORIZED_PUML_FILES
+ MMD_FILES
Expand All @@ -161,6 +173,12 @@ def test_colorized_dot_files(generated_file: str) -> None:
_assert_files_are_equal(generated_file)


@pytest.mark.usefixtures("setup_no_standalone_dot")
@pytest.mark.parametrize("generated_file", NO_STANDALONE_FILES)
def test_no_standalone_dot_files(generated_file: str) -> None:
_assert_files_are_equal(generated_file)


@pytest.mark.usefixtures("setup_puml")
@pytest.mark.parametrize("generated_file", PUML_FILES)
def test_puml_files(generated_file: str) -> None:
Expand Down