summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Drozd <nicholasdrozd@gmail.com>2023-04-02 05:41:26 -0400
committerGitHub <noreply@github.com>2023-04-02 11:41:26 +0200
commit3b42318eddaf0fded622179b19c7bcfcead26893 (patch)
tree502297ad801e6f05d56daff86500c74ba0ee8036
parentf7bd67604f395cd50734b9559acc37ae5d34ce4b (diff)
downloadpylint-git-3b42318eddaf0fded622179b19c7bcfcead26893.tar.gz
Add Pyreverse option to exclude standalone nodes (#8520)
* Add Pyreverse option to exclude standalone nodes * Add test * Add package test * Fix test names * Clean up test files
-rw-r--r--doc/whatsnew/fragments/8476.feature3
-rw-r--r--pylint/pyreverse/main.py8
-rw-r--r--pylint/pyreverse/writer.py13
-rw-r--r--pylint/testutils/pyreverse.py2
-rw-r--r--tests/pyreverse/conftest.py8
-rw-r--r--tests/pyreverse/data/classes_no_standalone.dot12
-rw-r--r--tests/pyreverse/data/packages_no_standalone.dot7
-rw-r--r--tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.mmd6
-rw-r--r--tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.py5
-rw-r--r--tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.rc2
-rw-r--r--tests/pyreverse/test_writer.py18
11 files changed, 84 insertions, 0 deletions
diff --git a/doc/whatsnew/fragments/8476.feature b/doc/whatsnew/fragments/8476.feature
new file mode 100644
index 000000000..56c0be201
--- /dev/null
+++ b/doc/whatsnew/fragments/8476.feature
@@ -0,0 +1,3 @@
+Add Pyreverse option to exclude standalone nodes from diagrams with `--no-standalone`.
+
+Closes #8476
diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py
index 975e432e4..58128bb57 100644
--- a/pylint/pyreverse/main.py
+++ b/pylint/pyreverse/main.py
@@ -158,6 +158,14 @@ OPTIONS: Options = (
},
),
(
+ "no-standalone",
+ {
+ "action": "store_true",
+ "default": False,
+ "help": "only show nodes with connections",
+ },
+ ),
+ (
"output",
{
"short": "o",
diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py
index cb711dd10..b5cab0a66 100644
--- a/pylint/pyreverse/writer.py
+++ b/pylint/pyreverse/writer.py
@@ -57,6 +57,12 @@ class DiagramWriter:
# 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,
@@ -75,6 +81,13 @@ class DiagramWriter:
# 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,
diff --git a/pylint/testutils/pyreverse.py b/pylint/testutils/pyreverse.py
index ba79ebf8b..24fddad77 100644
--- a/pylint/testutils/pyreverse.py
+++ b/pylint/testutils/pyreverse.py
@@ -37,6 +37,7 @@ class PyreverseConfig(
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,
@@ -59,6 +60,7 @@ class PyreverseConfig(
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
diff --git a/tests/pyreverse/conftest.py b/tests/pyreverse/conftest.py
index b281c5bee..9e1741f0a 100644
--- a/tests/pyreverse/conftest.py
+++ b/tests/pyreverse/conftest.py
@@ -29,6 +29,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(
output_format="puml",
diff --git a/tests/pyreverse/data/classes_no_standalone.dot b/tests/pyreverse/data/classes_no_standalone.dot
new file mode 100644
index 000000000..7cffc0a38
--- /dev/null
+++ b/tests/pyreverse/data/classes_no_standalone.dot
@@ -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"];
+}
diff --git a/tests/pyreverse/data/packages_no_standalone.dot b/tests/pyreverse/data/packages_no_standalone.dot
new file mode 100644
index 000000000..008c9c5d0
--- /dev/null
+++ b/tests/pyreverse/data/packages_no_standalone.dot
@@ -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"];
+}
diff --git a/tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.mmd b/tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.mmd
new file mode 100644
index 000000000..646d8220d
--- /dev/null
+++ b/tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.mmd
@@ -0,0 +1,6 @@
+classDiagram
+ class A {
+ }
+ class B {
+ }
+ B --|> A
diff --git a/tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.py b/tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.py
new file mode 100644
index 000000000..3d881d4c0
--- /dev/null
+++ b/tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.py
@@ -0,0 +1,5 @@
+class A: pass
+
+class B(A): pass
+
+class C: pass
diff --git a/tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.rc b/tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.rc
new file mode 100644
index 000000000..c17e41cfb
--- /dev/null
+++ b/tests/pyreverse/functional/class_diagrams/inheritance/no_standalone.rc
@@ -0,0 +1,2 @@
+[testoptions]
+command_line_args=--no-standalone
diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py
index 2897ca054..37a4b4f19 100644
--- a/tests/pyreverse/test_writer.py
+++ b/tests/pyreverse/test_writer.py
@@ -35,6 +35,7 @@ _DEFAULTS = {
"show_stdlib": False,
"only_classnames": False,
"output_directory": "",
+ "no_standalone": False,
}
TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data")
@@ -45,6 +46,7 @@ PUML_FILES = ["packages_No_Name.puml", "classes_No_Name.puml"]
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:
@@ -88,6 +90,15 @@ def setup_colorized_dot(
@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
) -> Iterator[None]:
@@ -138,6 +149,7 @@ def _setup(
for fname in (
DOT_FILES
+ COLORIZED_DOT_FILES
+ + NO_STANDALONE_FILES
+ PUML_FILES
+ COLORIZED_PUML_FILES
+ MMD_FILES
@@ -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: