Added option to skip redundant dependency relationships

This commit is contained in:
Bartek Kryza
2023-08-05 17:52:30 +02:00
parent f6efb7941f
commit 2e1013c12f
15 changed files with 108 additions and 34 deletions

View File

@@ -83,7 +83,7 @@ Nowadays, this file can be generated rather easily using multiple methods:
### Invocation ### 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 and compilation database `compile_commands.json` files are in the
current directory, so if they are in the top level directory of a project, current directory, so if they are in the top level directory of a project,
simply run: simply run:

View File

@@ -37,7 +37,8 @@ diagrams:
``` ```
## Classes and their properties ## 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) ![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. 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 In case method argument lists are too long and not required for diagram
or abbreviated by setting `generate_method_arguments` option to either `none`, `abbreviated` or `full` (default). 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 ### 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 ```yaml
include: include:
access: access:
@@ -68,7 +72,8 @@ To render only classes without any properties an exclusion filter can be added:
## Relationships ## 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 | | 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) | | 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 By default, a member from which a relationship has been added to the diagram
inside the class. This behaviour can be however disabled by adding the following option to the between 2 classes will also be rendered inside the class. This behaviour can be
diagram definition: however disabled by adding the following option to the diagram definition:
```yaml ```yaml
include_relations_also_as_members: false include_relations_also_as_members: false
``` ```
### Relationships to classes in containers or smart pointers ### Relationships to classes in containers or smart pointers
`clang-uml` will automatically detect class members as well as method arguments, which reference or own `clang-uml` will automatically detect class members as well as method arguments,
values of types relevant for a given diagram but wrapped in smart pointers or containers and still generate which reference or own values of types relevant for a given diagram but wrapped
relationship between these classes, for instance the following code: in smart pointers or containers and still generate relationship between these
classes, for instance the following code:
```cpp ```cpp
class A { }; class A { };
@@ -114,9 +120,9 @@ results in the following diagram:
## Inheritance diagrams ## Inheritance diagrams
A common type of class diagram is an inheritance diagram, where only subclasses of a specific base class are A common type of class diagram is an inheritance diagram, where only subclasses
included and only the inheritance relationships are rendered. This can be easily achieved in `clang-uml` through of a specific base class are included and only the inheritance relationships are
inclusion filters: rendered. This can be easily achieved in `clang-uml` through inclusion filters:
```yaml ```yaml
include: include:
subclasses: subclasses:
@@ -126,9 +132,10 @@ inclusion filters:
``` ```
## Including packages in the diagram ## Including packages in the diagram
By default, `clang-uml` will render all element names including a namespace (relative to `using_namespace` property), By default, `clang-uml` will render all element names including a namespace
e.g. `ns1::ns2::MyClass`. (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`: In order to generate packages in the diagram for each namespace instead, the
following option must be set to `true`:
```yaml ```yaml
generate_packages: true generate_packages: true
@@ -138,8 +145,9 @@ which results in the following diagram:
![t00036_class](test_cases/t00036_class.svg) ![t00036_class](test_cases/t00036_class.svg)
In case the code base is structured based on subdirectory instead of namespaces, packages can be generated In case the code base is structured based on subdirectory instead of namespaces,
based on the location of a given declaration in the filesystem tree, by adding also the following option: packages can be generated based on the location of a given declaration in the
filesystem tree, by adding also the following option:
```yaml ```yaml
package_type: directory package_type: directory
@@ -149,13 +157,15 @@ which results in the following diagram:
![t00065_class](test_cases/t00065_class.svg) ![t00065_class](test_cases/t00065_class.svg)
> In this case make sure that the root path of the configuration file is properly configured > In this case make sure that the root path of the configuration file is
> for your project, if necessary add `relative_to` option to denote the root path > properly configured for your project, if necessary add `relative_to` option to
> against which all relative paths in the config file are calculated. > denote the root path against which all relative paths in the config file are
> calculated.
## Class context diagram ## Class context diagram
Sometimes it's helpful to generate a class diagram depicting only direct relationships of a given class, e.g. Sometimes it's helpful to generate a class diagram depicting only direct
within the classes' documentation page, this can be easily achieved using `context` inclusion filter: relationships of a given class, e.g. within the classes' documentation page,
this can be easily achieved using `context` inclusion filter:
```yaml ```yaml
include: include:
@@ -164,23 +174,25 @@ within the classes' documentation page, this can be easily achieved using `conte
``` ```
## Disabling dependency relationships ## Disabling dependency relationships
In many cases, dependency relationships between classes can clutter the diagram too much, for instance consider this Dependency relationships are inferred whenever a class uses another class, thus
diagram: 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 In many cases, dependency relationships between classes can clutter the diagram
be useful to disable dependency relationships for this diagram completely using the following exclusion filter: too much. In such cases it might be useful to disable dependency relationships
completely for this diagram completely using the following exclusion filter:
```yaml ```yaml
exclude: exclude:
relationships: relationships:
- dependency - 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 It is also possible to only disable dependency relationships generated from
template arguments to other templates. By default, the following code: template arguments to other templates. By default, the following code:

View File

@@ -208,6 +208,33 @@ inja::json diagram::context() const
return ctx; 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<class_>::view()) {
std::set<id_t> 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::class_diagram::model
namespace clanguml::common::model { namespace clanguml::common::model {

View File

@@ -227,6 +227,11 @@ public:
*/ */
bool has_element(diagram_element::id_t id) const override; 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. * @brief Return the elements JSON context for inja templates.
* *

View File

@@ -2061,6 +2061,9 @@ void translation_unit_visitor::finalize()
{ {
add_incomplete_forward_declarations(); add_incomplete_forward_declarations();
resolve_local_to_global_ids(); resolve_local_to_global_ids();
if (config().skip_redundant_dependencies()) {
diagram().remove_redundant_dependencies();
}
} }
void translation_unit_visitor::extract_constrained_template_param_name( void translation_unit_visitor::extract_constrained_template_param_name(

View File

@@ -45,6 +45,13 @@ public:
*/ */
const reference_vector<T> &view() const { return elements_; } const reference_vector<T> &view() const { return elements_; }
/**
* @brief Get collection of reference to diagram elements
*
* @return
*/
reference_vector<T> &view() { return elements_; }
/** /**
* @brief Get typed diagram element by id * @brief Get typed diagram element by id
* @param id Global id of a diagram element * @param id Global id of a diagram element

View File

@@ -189,6 +189,7 @@ void inheritable_diagram_options::inherit(
package_type.override(parent.package_type); package_type.override(parent.package_type);
generate_template_argument_dependencies.override( generate_template_argument_dependencies.override(
parent.generate_template_argument_dependencies); parent.generate_template_argument_dependencies);
skip_redundant_dependencies.override(parent.skip_redundant_dependencies);
generate_links.override(parent.generate_links); generate_links.override(parent.generate_links);
generate_system_headers.override(parent.generate_system_headers); generate_system_headers.override(parent.generate_system_headers);
git.override(parent.git); git.override(parent.git);

View File

@@ -440,6 +440,8 @@ struct inheritable_diagram_options {
"package_type", package_type_t::kNamespace}; "package_type", package_type_t::kNamespace};
option<bool> generate_template_argument_dependencies{ option<bool> generate_template_argument_dependencies{
"generate_template_argument_dependencies", true}; "generate_template_argument_dependencies", true};
option<bool> skip_redundant_dependencies{
"skip_redundant_dependencies", true};
option<generate_links_config> generate_links{"generate_links"}; option<generate_links_config> generate_links{"generate_links"};
option<git_config> git{"git"}; option<git_config> git{"git"};
option<layout_hints> layout{"layout"}; option<layout_hints> layout{"layout"};

View File

@@ -160,6 +160,7 @@ types:
generate_packages: !optional bool generate_packages: !optional bool
package_type: !optional package_type_t package_type: !optional package_type_t
generate_template_argument_dependencies: !optional bool generate_template_argument_dependencies: !optional bool
skip_redundant_dependencies: !optional bool
member_order: !optional member_order_t member_order: !optional member_order_t
group_methods: !optional bool group_methods: !optional bool
type_aliases: !optional map_t<string;string> type_aliases: !optional map_t<string;string>
@@ -292,6 +293,7 @@ root:
group_methods: !optional bool group_methods: !optional bool
package_type: !optional package_type_t package_type: !optional package_type_t
generate_template_argument_dependencies: !optional bool generate_template_argument_dependencies: !optional bool
skip_redundant_dependencies: !optional bool
)"; )";
} // namespace clanguml::config } // namespace clanguml::config

View File

@@ -552,6 +552,7 @@ template <> struct convert<class_diagram> {
get_option(node, rhs.generate_packages); get_option(node, rhs.generate_packages);
get_option(node, rhs.package_type); get_option(node, rhs.package_type);
get_option(node, rhs.generate_template_argument_dependencies); 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.relationship_hints);
get_option(node, rhs.type_aliases); get_option(node, rhs.type_aliases);
get_option(node, rhs.relative_to); get_option(node, rhs.relative_to);
@@ -757,6 +758,7 @@ template <> struct convert<config> {
get_option(node, rhs.generate_packages); get_option(node, rhs.generate_packages);
get_option(node, rhs.package_type); get_option(node, rhs.package_type);
get_option(node, rhs.generate_template_argument_dependencies); 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_links);
get_option(node, rhs.generate_system_headers); get_option(node, rhs.generate_system_headers);
get_option(node, rhs.git); get_option(node, rhs.git);

View File

@@ -313,6 +313,7 @@ YAML::Emitter &operator<<(
out << c.member_order; out << c.member_order;
out << c.package_type; out << c.package_type;
out << c.generate_template_argument_dependencies; out << c.generate_template_argument_dependencies;
out << c.skip_redundant_dependencies;
} }
else if (dynamic_cast<const sequence_diagram *>(&c) != nullptr) { else if (dynamic_cast<const sequence_diagram *>(&c) != nullptr) {
out << c.combine_free_functions_into_file_participants; out << c.combine_free_functions_into_file_participants;

View File

@@ -5,6 +5,7 @@ diagrams:
type: class type: class
glob: glob:
- ../../tests/t00031/t00031.cc - ../../tests/t00031/t00031.cc
skip_redundant_dependencies: false
using_namespace: using_namespace:
- clanguml::t00031 - clanguml::t00031
include: include:

View File

@@ -25,6 +25,8 @@ struct R {
/// @uml{style[#green,dashed,thickness=4]} /// @uml{style[#green,dashed,thickness=4]}
std::vector<B> bbb; std::vector<B> bbb;
void add_b(B b) { bbb.push_back(b); }
/// @uml{style[#blue,dotted,thickness=8]} /// @uml{style[#blue,dotted,thickness=8]}
C<int> ccc; C<int> ccc;

View File

@@ -46,6 +46,7 @@ TEST_CASE("t00031", "[test-case][class]")
REQUIRE_THAT(puml, REQUIRE_THAT(puml,
IsCompositionWithStyle( IsCompositionWithStyle(
_A("R"), _A("B"), "+bbb", "#green,dashed,thickness=4")); _A("R"), _A("B"), "+bbb", "#green,dashed,thickness=4"));
REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B")));
REQUIRE_THAT(puml, REQUIRE_THAT(puml,
IsAggregationWithStyle( IsAggregationWithStyle(
_A("R"), _A("C<int>"), "+ccc", "#blue,dotted,thickness=8")); _A("R"), _A("C<int>"), "+ccc", "#blue,dotted,thickness=8"));

View File

@@ -53,6 +53,14 @@ TEST_CASE("t00032", "[test-case][class]")
puml, IsBaseClass(_A("B"), _A("Overload<TBase,int,A,B,C>"))); puml, IsBaseClass(_A("B"), _A("Overload<TBase,int,A,B,C>")));
REQUIRE_THAT( REQUIRE_THAT(
puml, IsBaseClass(_A("C"), _A("Overload<TBase,int,A,B,C>"))); puml, IsBaseClass(_A("C"), _A("Overload<TBase,int,A,B,C>")));
REQUIRE_THAT(
puml, !IsDependency(_A("Overload<TBase,int,A,B,C>"), _A("TBase")));
REQUIRE_THAT(
puml, !IsDependency(_A("Overload<TBase,int,A,B,C>"), _A("A")));
REQUIRE_THAT(
puml, !IsDependency(_A("Overload<TBase,int,A,B,C>"), _A("B")));
REQUIRE_THAT(
puml, !IsDependency(_A("Overload<TBase,int,A,B,C>"), _A("C")));
save_puml( save_puml(
config.output_directory() + "/" + diagram->name + ".puml", puml); config.output_directory() + "/" + diagram->name + ".puml", puml);