diff --git a/README.md b/README.md index 8e7f281e..4a553e34 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Nowadays, this file can be generated rather easily using multiple methods: ### Invocation -By default, `config-uml` will assume that the configuration file `.clang-uml` +By default, `clang-uml` will assume that the configuration file `.clang-uml` and compilation database `compile_commands.json` files are in the current directory, so if they are in the top level directory of a project, simply run: diff --git a/docs/class_diagrams.md b/docs/class_diagrams.md index 68a2fc71..611f02c3 100644 --- a/docs/class_diagrams.md +++ b/docs/class_diagrams.md @@ -37,7 +37,8 @@ diagrams: ``` ## Classes and their properties -The basic class diagram generated by `clang-uml` and rendered using PlantUML looks like this: +The basic class diagram generated by `clang-uml` and rendered using PlantUML +looks like this: ![extension](test_cases/t00003_class.svg) @@ -45,12 +46,15 @@ Member types and method return types are rendered at the end after `:` sign. Static methods and members are underlined. -In case method argument lists are too long and not required for diagram readability, they can be suppressed completely -or abbreviated by setting `generate_method_arguments` option to either `none`, `abbreviated` or `full` (default). +In case method argument lists are too long and not required for diagram +readability, they can be suppressed completely or abbreviated by setting +`generate_method_arguments` option to either `none`, `abbreviated` or `full` +(default). ### Excluding private or protected members from the diagram -In order to only include public members in the class diagrams, we can add the following inclusion filters: +In order to only include public members in the class diagrams, we can add the +following inclusion filters: ```yaml include: access: @@ -68,7 +72,8 @@ To render only classes without any properties an exclusion filter can be added: ## Relationships -The following table presents the PlantUML arrows representing each relationship in the class diagrams. +The following table presents the PlantUML arrows representing each relationship +in the class diagrams. | UML | PlantUML | | ---- | --- | @@ -81,17 +86,18 @@ The following table presents the PlantUML arrows representing each relationship | Nesting (inner class/enum) | ![nesting](img/puml_nested.png) | -By default, a member from which a relationship has been added to the diagram between 2 classes will also be rendered -inside the class. This behaviour can be however disabled by adding the following option to the -diagram definition: +By default, a member from which a relationship has been added to the diagram +between 2 classes will also be rendered inside the class. This behaviour can be +however disabled by adding the following option to the diagram definition: ```yaml include_relations_also_as_members: false ``` ### Relationships to classes in containers or smart pointers -`clang-uml` will automatically detect class members as well as method arguments, which reference or own -values of types relevant for a given diagram but wrapped in smart pointers or containers and still generate -relationship between these classes, for instance the following code: +`clang-uml` will automatically detect class members as well as method arguments, +which reference or own values of types relevant for a given diagram but wrapped +in smart pointers or containers and still generate relationship between these +classes, for instance the following code: ```cpp class A { }; @@ -114,9 +120,9 @@ results in the following diagram: ## Inheritance diagrams -A common type of class diagram is an inheritance diagram, where only subclasses of a specific base class are -included and only the inheritance relationships are rendered. This can be easily achieved in `clang-uml` through -inclusion filters: +A common type of class diagram is an inheritance diagram, where only subclasses +of a specific base class are included and only the inheritance relationships are +rendered. This can be easily achieved in `clang-uml` through inclusion filters: ```yaml include: subclasses: @@ -126,9 +132,10 @@ inclusion filters: ``` ## Including packages in the diagram -By default, `clang-uml` will render all element names including a namespace (relative to `using_namespace` property), -e.g. `ns1::ns2::MyClass`. -In order to generate packages in the diagram for each namespace instead, the following option must be set to `true`: +By default, `clang-uml` will render all element names including a namespace +(relative to `using_namespace` property), e.g. `ns1::ns2::MyClass`. +In order to generate packages in the diagram for each namespace instead, the +following option must be set to `true`: ```yaml generate_packages: true @@ -138,8 +145,9 @@ which results in the following diagram: ![t00036_class](test_cases/t00036_class.svg) -In case the code base is structured based on subdirectory instead of namespaces, packages can be generated -based on the location of a given declaration in the filesystem tree, by adding also the following option: +In case the code base is structured based on subdirectory instead of namespaces, +packages can be generated based on the location of a given declaration in the +filesystem tree, by adding also the following option: ```yaml package_type: directory @@ -149,13 +157,15 @@ which results in the following diagram: ![t00065_class](test_cases/t00065_class.svg) -> In this case make sure that the root path of the configuration file is properly configured -> for your project, if necessary add `relative_to` option to denote the root path -> against which all relative paths in the config file are calculated. +> In this case make sure that the root path of the configuration file is +> properly configured for your project, if necessary add `relative_to` option to +> denote the root path against which all relative paths in the config file are +> calculated. ## Class context diagram -Sometimes it's helpful to generate a class diagram depicting only direct relationships of a given class, e.g. -within the classes' documentation page, this can be easily achieved using `context` inclusion filter: +Sometimes it's helpful to generate a class diagram depicting only direct +relationships of a given class, e.g. within the classes' documentation page, +this can be easily achieved using `context` inclusion filter: ```yaml include: @@ -164,23 +174,25 @@ within the classes' documentation page, this can be easily achieved using `conte ``` ## Disabling dependency relationships -In many cases, dependency relationships between classes can clutter the diagram too much, for instance consider this -diagram: +Dependency relationships are inferred whenever a class uses another class, thus +often dependency relationship will be rendered in addition to other +relationships such as association or inheritance. By default, `clang-uml` will +remove these redundant dependency relationships, however if it is necessary to +retain them it can be done using the following option: -![t00019_class](test_cases/t00019_class.svg) +```yaml +skip_redundant_dependencies: false +``` -where the dependency relationships do not bring much information into the diagram. In such cases it might -be useful to disable dependency relationships for this diagram completely using the following exclusion filter: +In many cases, dependency relationships between classes can clutter the diagram +too much. In such cases it might be useful to disable dependency relationships +completely for this diagram completely using the following exclusion filter: ```yaml exclude: relationships: - dependency ``` -Dependency relationships are inferred whenever a class uses another class, thus often dependency relationship -will be rendered in addition to other relationships such as association or inheritance. In the future there might -be an option to remove the redundant dependency relationships from the diagram automatically. - It is also possible to only disable dependency relationships generated from template arguments to other templates. By default, the following code: diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 9de710ba..64c53dc6 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -208,6 +208,33 @@ inja::json diagram::context() const return ctx; } +void diagram::remove_redundant_dependencies() +{ + using common::id_t; + using common::model::relationship; + using common::model::relationship_t; + + for (auto &c : element_view::view()) { + std::set dependency_relationships_to_remove; + + for (auto &r : c.get().relationships()) { + if (r.type() != relationship_t::kDependency) + dependency_relationships_to_remove.emplace(r.destination()); + } + + for (const auto &base : c.get().parents()) { + dependency_relationships_to_remove.emplace(base.id()); + } + + util::erase_if(c.get().relationships(), + [&dependency_relationships_to_remove](const auto &r) { + return r.type() == relationship_t::kDependency && + dependency_relationships_to_remove.count(r.destination()) > + 0; + }); + } +} + } // namespace clanguml::class_diagram::model namespace clanguml::common::model { diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index 01f12363..86bf86d0 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -227,6 +227,11 @@ public: */ bool has_element(diagram_element::id_t id) const override; + /** + * @brief Remove redundant dependency relationships + */ + void remove_redundant_dependencies(); + /** * @brief Return the elements JSON context for inja templates. * diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 321a004d..4469e46b 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -2061,6 +2061,9 @@ void translation_unit_visitor::finalize() { add_incomplete_forward_declarations(); resolve_local_to_global_ids(); + if (config().skip_redundant_dependencies()) { + diagram().remove_redundant_dependencies(); + } } void translation_unit_visitor::extract_constrained_template_param_name( diff --git a/src/common/model/element_view.h b/src/common/model/element_view.h index 35e1b1a2..8d34f0a4 100644 --- a/src/common/model/element_view.h +++ b/src/common/model/element_view.h @@ -45,6 +45,13 @@ public: */ const reference_vector &view() const { return elements_; } + /** + * @brief Get collection of reference to diagram elements + * + * @return + */ + reference_vector &view() { return elements_; } + /** * @brief Get typed diagram element by id * @param id Global id of a diagram element diff --git a/src/config/config.cc b/src/config/config.cc index ee964f08..a77571ea 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -189,6 +189,7 @@ void inheritable_diagram_options::inherit( package_type.override(parent.package_type); generate_template_argument_dependencies.override( parent.generate_template_argument_dependencies); + skip_redundant_dependencies.override(parent.skip_redundant_dependencies); generate_links.override(parent.generate_links); generate_system_headers.override(parent.generate_system_headers); git.override(parent.git); diff --git a/src/config/config.h b/src/config/config.h index 26872c6d..ea4444b8 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -440,6 +440,8 @@ struct inheritable_diagram_options { "package_type", package_type_t::kNamespace}; option generate_template_argument_dependencies{ "generate_template_argument_dependencies", true}; + option skip_redundant_dependencies{ + "skip_redundant_dependencies", true}; option generate_links{"generate_links"}; option git{"git"}; option layout{"layout"}; diff --git a/src/config/schema.h b/src/config/schema.h index 118b6700..1ff18c05 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -160,6 +160,7 @@ types: generate_packages: !optional bool package_type: !optional package_type_t generate_template_argument_dependencies: !optional bool + skip_redundant_dependencies: !optional bool member_order: !optional member_order_t group_methods: !optional bool type_aliases: !optional map_t @@ -292,6 +293,7 @@ root: group_methods: !optional bool package_type: !optional package_type_t generate_template_argument_dependencies: !optional bool + skip_redundant_dependencies: !optional bool )"; } // namespace clanguml::config \ No newline at end of file diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 8614ed4e..f1fd98b7 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -552,6 +552,7 @@ template <> struct convert { get_option(node, rhs.generate_packages); get_option(node, rhs.package_type); get_option(node, rhs.generate_template_argument_dependencies); + get_option(node, rhs.skip_redundant_dependencies); get_option(node, rhs.relationship_hints); get_option(node, rhs.type_aliases); get_option(node, rhs.relative_to); @@ -757,6 +758,7 @@ template <> struct convert { get_option(node, rhs.generate_packages); get_option(node, rhs.package_type); get_option(node, rhs.generate_template_argument_dependencies); + get_option(node, rhs.skip_redundant_dependencies); get_option(node, rhs.generate_links); get_option(node, rhs.generate_system_headers); get_option(node, rhs.git); diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index 89d01c69..f653b8d1 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -313,6 +313,7 @@ YAML::Emitter &operator<<( out << c.member_order; out << c.package_type; out << c.generate_template_argument_dependencies; + out << c.skip_redundant_dependencies; } else if (dynamic_cast(&c) != nullptr) { out << c.combine_free_functions_into_file_participants; diff --git a/tests/t00031/.clang-uml b/tests/t00031/.clang-uml index 276a156f..f06027c1 100644 --- a/tests/t00031/.clang-uml +++ b/tests/t00031/.clang-uml @@ -5,6 +5,7 @@ diagrams: type: class glob: - ../../tests/t00031/t00031.cc + skip_redundant_dependencies: false using_namespace: - clanguml::t00031 include: diff --git a/tests/t00031/t00031.cc b/tests/t00031/t00031.cc index 96bb8dde..c8c57e8e 100644 --- a/tests/t00031/t00031.cc +++ b/tests/t00031/t00031.cc @@ -25,6 +25,8 @@ struct R { /// @uml{style[#green,dashed,thickness=4]} std::vector bbb; + void add_b(B b) { bbb.push_back(b); } + /// @uml{style[#blue,dotted,thickness=8]} C ccc; diff --git a/tests/t00031/test_case.h b/tests/t00031/test_case.h index bc453a4a..f86f88a0 100644 --- a/tests/t00031/test_case.h +++ b/tests/t00031/test_case.h @@ -46,6 +46,7 @@ TEST_CASE("t00031", "[test-case][class]") REQUIRE_THAT(puml, IsCompositionWithStyle( _A("R"), _A("B"), "+bbb", "#green,dashed,thickness=4")); + REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B"))); REQUIRE_THAT(puml, IsAggregationWithStyle( _A("R"), _A("C"), "+ccc", "#blue,dotted,thickness=8")); diff --git a/tests/t00032/test_case.h b/tests/t00032/test_case.h index 065e3a38..587fd72f 100644 --- a/tests/t00032/test_case.h +++ b/tests/t00032/test_case.h @@ -53,6 +53,14 @@ TEST_CASE("t00032", "[test-case][class]") puml, IsBaseClass(_A("B"), _A("Overload"))); REQUIRE_THAT( puml, IsBaseClass(_A("C"), _A("Overload"))); + REQUIRE_THAT( + puml, !IsDependency(_A("Overload"), _A("TBase"))); + REQUIRE_THAT( + puml, !IsDependency(_A("Overload"), _A("A"))); + REQUIRE_THAT( + puml, !IsDependency(_A("Overload"), _A("B"))); + REQUIRE_THAT( + puml, !IsDependency(_A("Overload"), _A("C"))); save_puml( config.output_directory() + "/" + diagram->name + ".puml", puml);