diff options
Diffstat (limited to 'pylint/pyreverse')
-rw-r--r-- | pylint/pyreverse/__init__.py | 4 | ||||
-rw-r--r-- | pylint/pyreverse/diadefslib.py | 17 | ||||
-rw-r--r-- | pylint/pyreverse/diagrams.py | 27 | ||||
-rw-r--r-- | pylint/pyreverse/dot_printer.py | 6 | ||||
-rw-r--r-- | pylint/pyreverse/inspector.py | 53 | ||||
-rw-r--r-- | pylint/pyreverse/main.py | 49 | ||||
-rw-r--r-- | pylint/pyreverse/mermaidjs_printer.py | 9 | ||||
-rw-r--r-- | pylint/pyreverse/plantuml_printer.py | 11 | ||||
-rw-r--r-- | pylint/pyreverse/printer.py | 6 | ||||
-rw-r--r-- | pylint/pyreverse/printer_factory.py | 6 | ||||
-rw-r--r-- | pylint/pyreverse/utils.py | 9 | ||||
-rw-r--r-- | pylint/pyreverse/vcg_printer.py | 303 | ||||
-rw-r--r-- | pylint/pyreverse/writer.py | 31 |
13 files changed, 91 insertions, 440 deletions
diff --git a/pylint/pyreverse/__init__.py b/pylint/pyreverse/__init__.py index 458c0f35d..175e9cb67 100644 --- a/pylint/pyreverse/__init__.py +++ b/pylint/pyreverse/__init__.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Pyreverse.extensions.""" diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index 85b23052e..3b7694823 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Handle diagram generation options for class diagram or default diagrams.""" @@ -12,6 +12,7 @@ from typing import Any import astroid from astroid import nodes +from astroid.modutils import is_stdlib_module from pylint.pyreverse.diagrams import ClassDiagram, PackageDiagram from pylint.pyreverse.inspector import Linker, Project @@ -67,10 +68,14 @@ class DiaDefGenerator: return self.anc_level, self.association_level def show_node(self, node: nodes.ClassDef) -> bool: - """True if builtins and not show_builtins.""" - if self.config.show_builtin: - return True - return node.root().name != "builtins" # type: ignore[no-any-return] + """Determine if node should be shown based on config.""" + if node.root().name == "builtins": + return self.config.show_builtin # type: ignore[no-any-return] + + if is_stdlib_module(node.root().name): + return self.config.show_stdlib # type: ignore[no-any-return] + + return True def add_class(self, node: nodes.ClassDef) -> None: """Visit one class and add it to diagram.""" diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index 4437d3c4e..01bce7dc3 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Diagram objects.""" @@ -13,7 +13,7 @@ import astroid from astroid import nodes, util from pylint.checkers.utils import decorated_with_property -from pylint.pyreverse.utils import FilterMixIn, is_interface +from pylint.pyreverse.utils import FilterMixIn class Figure: @@ -50,7 +50,13 @@ class DiagramEntity(Figure): ) -> None: super().__init__() self.title = title - self.node: nodes.NodeNG = node if node else nodes.NodeNG() + self.node: nodes.NodeNG = node or nodes.NodeNG( + lineno=None, + col_offset=None, + end_lineno=None, + end_col_offset=None, + parent=None, + ) self.shape = self.default_shape @@ -195,11 +201,7 @@ class ClassDiagram(Figure, FilterMixIn): node = obj.node obj.attrs = self.get_attrs(node) obj.methods = self.get_methods(node) - # shape - if is_interface(node): - obj.shape = "interface" - else: - obj.shape = "class" + obj.shape = "class" # inheritance link for par_node in node.ancestors(recurs=False): try: @@ -207,13 +209,6 @@ class ClassDiagram(Figure, FilterMixIn): self.add_relationship(obj, par_obj, "specialization") except KeyError: continue - # implements link - for impl_node in node.implements: - try: - impl_obj = self.object_from_node(impl_node) - self.add_relationship(obj, impl_obj, "implements") - except KeyError: - continue # associations & aggregations links for name, values in list(node.aggregations_type.items()): diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index 99cb17e97..edaea2384 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Class to generate files in dot format and image formats supported by Graphviz.""" @@ -25,13 +25,11 @@ class HTMLLabels(Enum): ALLOWED_CHARSETS: frozenset[str] = frozenset(("utf-8", "iso-8859-1", "latin1")) SHAPES: dict[NodeType, str] = { NodeType.PACKAGE: "box", - NodeType.INTERFACE: "record", NodeType.CLASS: "record", } # pylint: disable-next=consider-using-namedtuple-or-dataclass ARROWS: dict[EdgeType, dict[str, str]] = { EdgeType.INHERITS: {"arrowtail": "none", "arrowhead": "empty"}, - EdgeType.IMPLEMENTS: {"arrowtail": "node", "arrowhead": "empty", "style": "dashed"}, EdgeType.ASSOCIATION: { "fontcolor": "green", "arrowtail": "none", diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 523ff8171..0cabe9473 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Visitor doing some post-processing on the astroid tree. @@ -12,13 +12,11 @@ from __future__ import annotations import collections import os import traceback -import warnings from abc import ABC, abstractmethod -from collections.abc import Generator -from typing import Any, Callable, Optional +from typing import Callable, Optional import astroid -from astroid import nodes, util +from astroid import nodes from pylint import constants from pylint.pyreverse import utils @@ -39,27 +37,6 @@ def _astroid_wrapper( return None -def interfaces(node: nodes.ClassDef) -> Generator[Any, None, None]: - """Return an iterator on interfaces implemented by the given class node.""" - try: - implements = astroid.bases.Instance(node).getattr("__implements__")[0] - except astroid.exceptions.NotFoundError: - return - if implements.frame(future=True) is not node: - return - found = set() - missing = False - for iface in nodes.unpack_infer(implements): - if isinstance(iface, util.UninferableBase): - missing = True - continue - if iface not in found: - found.add(iface) - yield iface - if missing: - raise astroid.exceptions.InferenceError() - - class IdGeneratorMixIn: """Mixin adding the ability to generate integer uid.""" @@ -129,9 +106,6 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor): * aggregations_type as instance_attrs_type but for aggregations relationships - - * implements, - list of implemented interface _objects_ (only on astroid.Class nodes) """ def __init__(self, project: Project, tag: bool = False) -> None: @@ -172,7 +146,6 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor): """Visit an astroid.Class node. * set the locals_type and instance_attrs_type mappings - * set the implements list and build it * optionally tag the node with a unique id """ if hasattr(node, "locals_type"): @@ -194,24 +167,6 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor): if not isinstance(assignattr, nodes.Unknown): self.associations_handler.handle(assignattr, node) self.handle_assignattr_type(assignattr, node) - # resolve implemented interface - try: - ifaces = interfaces(node) - if ifaces is not None: - node.implements = list(ifaces) - if node.implements: - # TODO: 3.0: Remove support for __implements__ - warnings.warn( - "pyreverse will drop support for resolving and displaying " - "implemented interfaces in pylint 3.0. The implementation " - "relies on the '__implements__' attribute proposed in PEP 245" - ", which was rejected in 2006.", - DeprecationWarning, - ) - else: - node.implements = [] - except astroid.InferenceError: - node.implements = [] def visit_functiondef(self, node: nodes.FunctionDef) -> None: """Visit an astroid.Function node. diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index a2dc94734..58128bb57 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Create UML diagrams for classes and modules in <packages>.""" @@ -27,7 +27,6 @@ from pylint.typing import Options DIRECTLY_SUPPORTED_FORMATS = ( "dot", - "vcg", "puml", "plantuml", "mmd", @@ -35,23 +34,16 @@ DIRECTLY_SUPPORTED_FORMATS = ( ) DEFAULT_COLOR_PALETTE = ( - "aliceblue", - "antiquewhite", - "aquamarine", - "burlywood", - "cadetblue", - "chartreuse", - "chocolate", - "coral", - "cornflowerblue", - "cyan", - "darkgoldenrod", - "darkseagreen", - "dodgerblue", - "forestgreen", - "gold", - "hotpink", - "mediumspringgreen", + # colorblind scheme taken from https://personal.sron.nl/~pault/ + "#77AADD", # light blue + "#99DDFF", # light cyan + "#44BB99", # mint + "#BBCC33", # pear + "#AAAA00", # olive + "#EEDD88", # light yellow + "#EE8866", # orange + "#FFAABB", # pink + "#DDDDDD", # pale grey ) OPTIONS: Options = ( @@ -138,6 +130,15 @@ OPTIONS: Options = ( }, ), ( + "show-stdlib", + { + "short": "L", + "action": "store_true", + "default": False, + "help": "include standard library objects in representation of classes", + }, + ), + ( "module-names", { "short": "m", @@ -157,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/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index a8f3c576b..61d0d7948 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Class to generate files in mermaidjs format.""" @@ -17,12 +17,10 @@ class MermaidJSPrinter(Printer): NODES: dict[NodeType, str] = { NodeType.CLASS: "class", - NodeType.INTERFACE: "class", NodeType.PACKAGE: "class", } ARROWS: dict[EdgeType, str] = { EdgeType.INHERITS: "--|>", - EdgeType.IMPLEMENTS: "..|>", EdgeType.ASSOCIATION: "--*", EdgeType.AGGREGATION: "--o", EdgeType.USES: "-->", @@ -46,7 +44,6 @@ class MermaidJSPrinter(Printer): # pylint: disable=duplicate-code if properties is None: properties = NodeProperties(label=name) - stereotype = "~~Interface~~" if type_ is NodeType.INTERFACE else "" nodetype = self.NODES[type_] body = [] if properties.attrs: @@ -60,7 +57,7 @@ class MermaidJSPrinter(Printer): line += f" {get_annotation_label(func.returns)}" body.append(line) name = name.split(".")[-1] - self.emit(f"{nodetype} {name}{stereotype} {{") + self.emit(f"{nodetype} {name} {{") self._inc_indent() for line in body: self.emit(line) diff --git a/pylint/pyreverse/plantuml_printer.py b/pylint/pyreverse/plantuml_printer.py index de3f983b7..5f703b62e 100644 --- a/pylint/pyreverse/plantuml_printer.py +++ b/pylint/pyreverse/plantuml_printer.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Class to generate files in dot format and image formats supported by Graphviz.""" @@ -17,12 +17,10 @@ class PlantUmlPrinter(Printer): NODES: dict[NodeType, str] = { NodeType.CLASS: "class", - NodeType.INTERFACE: "class", NodeType.PACKAGE: "package", } ARROWS: dict[EdgeType, str] = { EdgeType.INHERITS: "--|>", - EdgeType.IMPLEMENTS: "..|>", EdgeType.ASSOCIATION: "--*", EdgeType.AGGREGATION: "--o", EdgeType.USES: "-->", @@ -56,10 +54,9 @@ class PlantUmlPrinter(Printer): """ if properties is None: properties = NodeProperties(label=name) - stereotype = " << interface >>" if type_ is NodeType.INTERFACE else "" nodetype = self.NODES[type_] if properties.color and properties.color != self.DEFAULT_COLOR: - color = f" #{properties.color}" + color = f" #{properties.color.lstrip('#')}" else: color = "" body = [] @@ -76,7 +73,7 @@ class PlantUmlPrinter(Printer): label = properties.label if properties.label is not None else name if properties.fontcolor and properties.fontcolor != self.DEFAULT_COLOR: label = f"<color:{properties.fontcolor}>{label}</color>" - self.emit(f'{nodetype} "{label}" as {name}{stereotype}{color} {{') + self.emit(f'{nodetype} "{label}" as {name}{color} {{') self._inc_indent() for line in body: self.emit(line) diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py index cdbf7e3c8..f08c74602 100644 --- a/pylint/pyreverse/printer.py +++ b/pylint/pyreverse/printer.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Base class defining the interface for a printer.""" @@ -17,13 +17,11 @@ from pylint.pyreverse.utils import get_annotation_label class NodeType(Enum): CLASS = "class" - INTERFACE = "interface" PACKAGE = "package" class EdgeType(Enum): INHERITS = "inherits" - IMPLEMENTS = "implements" ASSOCIATION = "association" AGGREGATION = "aggregation" USES = "uses" diff --git a/pylint/pyreverse/printer_factory.py b/pylint/pyreverse/printer_factory.py index 41e8b46c8..fdbe480ed 100644 --- a/pylint/pyreverse/printer_factory.py +++ b/pylint/pyreverse/printer_factory.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt from __future__ import annotations @@ -8,10 +8,8 @@ from pylint.pyreverse.dot_printer import DotPrinter from pylint.pyreverse.mermaidjs_printer import HTMLMermaidJSPrinter, MermaidJSPrinter from pylint.pyreverse.plantuml_printer import PlantUmlPrinter from pylint.pyreverse.printer import Printer -from pylint.pyreverse.vcg_printer import VCGPrinter filetype_to_printer: dict[str, type[Printer]] = { - "vcg": VCGPrinter, "plantuml": PlantUmlPrinter, "puml": PlantUmlPrinter, "mmd": MermaidJSPrinter, diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index 078bc1b7e..6294773b2 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt """Generic classes/functions for pyreverse core/extensions.""" @@ -72,11 +72,6 @@ def get_visibility(name: str) -> str: return visibility -def is_interface(node: nodes.ClassDef) -> bool: - # bw compatibility - return node.type == "interface" # type: ignore[no-any-return] - - def is_exception(node: nodes.ClassDef) -> bool: # bw compatibility return node.type == "exception" # type: ignore[no-any-return] diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py deleted file mode 100644 index b9e2e94f3..000000000 --- a/pylint/pyreverse/vcg_printer.py +++ /dev/null @@ -1,303 +0,0 @@ -# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt - -"""Functions to generate files readable with George Sander's vcg -(Visualization of Compiler Graphs). - -You can download vcg at https://rw4.cs.uni-sb.de/~sander/html/gshome.html -Note that vcg exists as a debian package. -See vcg's documentation for explanation about the different values that -maybe used for the functions parameters. -""" - -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any - -from pylint.pyreverse.printer import EdgeType, Layout, NodeProperties, NodeType, Printer - -ATTRS_VAL = { - "algos": ( - "dfs", - "tree", - "minbackward", - "left_to_right", - "right_to_left", - "top_to_bottom", - "bottom_to_top", - "maxdepth", - "maxdepthslow", - "mindepth", - "mindepthslow", - "mindegree", - "minindegree", - "minoutdegree", - "maxdegree", - "maxindegree", - "maxoutdegree", - ), - "booleans": ("yes", "no"), - "colors": ( - "black", - "white", - "blue", - "red", - "green", - "yellow", - "magenta", - "lightgrey", - "cyan", - "darkgrey", - "darkblue", - "darkred", - "darkgreen", - "darkyellow", - "darkmagenta", - "darkcyan", - "gold", - "lightblue", - "lightred", - "lightgreen", - "lightyellow", - "lightmagenta", - "lightcyan", - "lilac", - "turquoise", - "aquamarine", - "khaki", - "purple", - "yellowgreen", - "pink", - "orange", - "orchid", - ), - "shapes": ("box", "ellipse", "rhomb", "triangle"), - "textmodes": ("center", "left_justify", "right_justify"), - "arrowstyles": ("solid", "line", "none"), - "linestyles": ("continuous", "dashed", "dotted", "invisible"), -} - -# meaning of possible values: -# O -> string -# 1 -> int -# list -> value in list -GRAPH_ATTRS = { - "title": 0, - "label": 0, - "color": ATTRS_VAL["colors"], - "textcolor": ATTRS_VAL["colors"], - "bordercolor": ATTRS_VAL["colors"], - "width": 1, - "height": 1, - "borderwidth": 1, - "textmode": ATTRS_VAL["textmodes"], - "shape": ATTRS_VAL["shapes"], - "shrink": 1, - "stretch": 1, - "orientation": ATTRS_VAL["algos"], - "vertical_order": 1, - "horizontal_order": 1, - "xspace": 1, - "yspace": 1, - "layoutalgorithm": ATTRS_VAL["algos"], - "late_edge_labels": ATTRS_VAL["booleans"], - "display_edge_labels": ATTRS_VAL["booleans"], - "dirty_edge_labels": ATTRS_VAL["booleans"], - "finetuning": ATTRS_VAL["booleans"], - "manhattan_edges": ATTRS_VAL["booleans"], - "smanhattan_edges": ATTRS_VAL["booleans"], - "port_sharing": ATTRS_VAL["booleans"], - "edges": ATTRS_VAL["booleans"], - "nodes": ATTRS_VAL["booleans"], - "splines": ATTRS_VAL["booleans"], -} -NODE_ATTRS = { - "title": 0, - "label": 0, - "color": ATTRS_VAL["colors"], - "textcolor": ATTRS_VAL["colors"], - "bordercolor": ATTRS_VAL["colors"], - "width": 1, - "height": 1, - "borderwidth": 1, - "textmode": ATTRS_VAL["textmodes"], - "shape": ATTRS_VAL["shapes"], - "shrink": 1, - "stretch": 1, - "vertical_order": 1, - "horizontal_order": 1, -} -EDGE_ATTRS = { - "sourcename": 0, - "targetname": 0, - "label": 0, - "linestyle": ATTRS_VAL["linestyles"], - "class": 1, - "thickness": 0, - "color": ATTRS_VAL["colors"], - "textcolor": ATTRS_VAL["colors"], - "arrowcolor": ATTRS_VAL["colors"], - "backarrowcolor": ATTRS_VAL["colors"], - "arrowsize": 1, - "backarrowsize": 1, - "arrowstyle": ATTRS_VAL["arrowstyles"], - "backarrowstyle": ATTRS_VAL["arrowstyles"], - "textmode": ATTRS_VAL["textmodes"], - "priority": 1, - "anchor": 1, - "horizontal_order": 1, -} -SHAPES: dict[NodeType, str] = { - NodeType.PACKAGE: "box", - NodeType.CLASS: "box", - NodeType.INTERFACE: "ellipse", -} -# pylint: disable-next=consider-using-namedtuple-or-dataclass -ARROWS: dict[EdgeType, dict[str, str | int]] = { - EdgeType.USES: { - "arrowstyle": "solid", - "backarrowstyle": "none", - "backarrowsize": 0, - }, - EdgeType.INHERITS: { - "arrowstyle": "solid", - "backarrowstyle": "none", - "backarrowsize": 10, - }, - EdgeType.IMPLEMENTS: { - "arrowstyle": "solid", - "backarrowstyle": "none", - "linestyle": "dotted", - "backarrowsize": 10, - }, - EdgeType.ASSOCIATION: { - "arrowstyle": "solid", - "backarrowstyle": "none", - "textcolor": "green", - }, - EdgeType.AGGREGATION: { - "arrowstyle": "solid", - "backarrowstyle": "none", - "textcolor": "green", - }, -} -ORIENTATION: dict[Layout, str] = { - Layout.LEFT_TO_RIGHT: "left_to_right", - Layout.RIGHT_TO_LEFT: "right_to_left", - Layout.TOP_TO_BOTTOM: "top_to_bottom", - Layout.BOTTOM_TO_TOP: "bottom_to_top", -} - -# Misc utilities ############################################################### - - -class VCGPrinter(Printer): - def _open_graph(self) -> None: - """Emit the header lines.""" - self.emit("graph:{\n") - self._inc_indent() - self._write_attributes( - GRAPH_ATTRS, - title=self.title, - layoutalgorithm="dfs", - late_edge_labels="yes", - port_sharing="no", - manhattan_edges="yes", - ) - if self.layout: - self._write_attributes(GRAPH_ATTRS, orientation=ORIENTATION[self.layout]) - - def _close_graph(self) -> None: - """Emit the lines needed to properly close the graph.""" - self._dec_indent() - self.emit("}") - - def emit_node( - self, - name: str, - type_: NodeType, - properties: NodeProperties | None = None, - ) -> None: - """Create a new node. - - Nodes can be classes, packages, participants etc. - """ - if properties is None: - properties = NodeProperties(label=name) - elif properties.label is None: - properties.label = name - self.emit(f'node: {{title:"{name}"', force_newline=False) - self._write_attributes( - NODE_ATTRS, - label=self._build_label_for_node(properties), - shape=SHAPES[type_], - ) - self.emit("}") - - @staticmethod - def _build_label_for_node(properties: NodeProperties) -> str: - fontcolor = "\f09" if properties.fontcolor == "red" else "" - label = rf"\fb{fontcolor}{properties.label}\fn" - if properties.attrs is None and properties.methods is None: - # return a compact form which only displays the classname in a box - return label - attrs = properties.attrs or [] - methods = properties.methods or [] - method_names = [func.name for func in methods] - # box width for UML like diagram - maxlen = max(len(name) for name in [properties.label] + method_names + attrs) - line = "_" * (maxlen + 2) - label = rf"{label}\n\f{line}" - for attr in attrs: - label = rf"{label}\n\f08{attr}" - if attrs: - label = rf"{label}\n\f{line}" - for func in method_names: - label = rf"{label}\n\f10{func}()" - return label - - def emit_edge( - self, - from_node: str, - to_node: str, - type_: EdgeType, - label: str | None = None, - ) -> None: - """Create an edge from one node to another to display relationships.""" - self.emit( - f'edge: {{sourcename:"{from_node}" targetname:"{to_node}"', - force_newline=False, - ) - attributes = ARROWS[type_] - if label: - attributes["label"] = label - self._write_attributes( - EDGE_ATTRS, - **attributes, - ) - self.emit("}") - - def _write_attributes( - self, attributes_dict: Mapping[str, Any], **args: Any - ) -> None: - """Write graph, node or edge attributes.""" - for key, value in args.items(): - try: - _type = attributes_dict[key] - except KeyError as e: - raise AttributeError( - f"no such attribute {key}\npossible attributes are {attributes_dict.keys()}" - ) from e - - if not _type: - self.emit(f'{key}:"{value}"\n') - elif _type == 1: - self.emit(f"{key}:{int(value)}\n") - elif value in _type: - self.emit(f"{key}:{value}\n") - else: - raise ValueError( - f"value {value} isn't correct for attribute {key} correct values are {type}" - ) diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index d92f4e2e5..58e967115 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -1,8 +1,8 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt -"""Utilities for creating VCG and Dot diagrams.""" +"""Utilities for creating diagrams.""" from __future__ import annotations @@ -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,9 +81,17 @@ 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() - type_ = NodeType.INTERFACE if obj.shape == "interface" else NodeType.CLASS + 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_=type_, properties=self.get_class_properties(obj) + obj.fig_id, + type_=NodeType.CLASS, + properties=self.get_class_properties(obj), ) # inheritance links for rel in diagram.get_relationships("specialization"): @@ -86,13 +100,6 @@ class DiagramWriter: rel.to_object.fig_id, type_=EdgeType.INHERITS, ) - # implementation links - for rel in diagram.get_relationships("implements"): - self.printer.emit_edge( - rel.from_object.fig_id, - rel.to_object.fig_id, - type_=EdgeType.IMPLEMENTS, - ) # generate associations for rel in diagram.get_relationships("association"): self.printer.emit_edge( |