diff --git a/README.md b/README.md index c343dfb5..e2b8ef80 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ![linux build](https://github.com/bkryza/clang-uml/actions/workflows/build.yml/badge.svg) -`clang-uml` is an automatic C++ to [PlantUML](https://plantuml.com) class and sequence -diagram generator, driven by YAML configuration files. The main idea behind the +`clang-uml` is an automatic C++ to [PlantUML](https://plantuml.com) class, sequence +and package diagram generator, driven by YAML configuration files. The main idea behind the project is to easily maintain up-to-date diagrams within a code-base or document existing project code. The configuration file or files for `clang-uml` define the -type and contents of each diagram. +type and contents of each generated diagram. ## Features Main features supported so far include: @@ -17,11 +17,14 @@ Main features supported so far include: * Template instantiation relationships * Relationship inference from C++ containers and smart pointers * Namespace based content filtering + * Optional package generation from namespaces * Sequence diagram generation - * Generation of sequence diagram from one code location to another + * Generation of sequence diagram from one code location to another (currently only for non-template code) * Package diagram generation * Generation of package diagram based on C++ namespaces +To see what `clang-uml` can do so far, checkout the diagrams generated for unit test cases [here](./docs/test_cases.md). + ## Installation ### Building from source diff --git a/docs/test_cases.md b/docs/test_cases.md index 4f2b88f4..f4cc24d9 100644 --- a/docs/test_cases.md +++ b/docs/test_cases.md @@ -34,6 +34,7 @@ * [t00033](./test_cases/t00033.md) - Nested template instantiation dependency test case * [t00034](./test_cases/t00034.md) - Template metaprogramming type function test case * [t00035](./test_cases/t00035.md) - PlantUML class diagram layout hints test case + * [t00036](./test_cases/t00036.md) - Class diagram with namespaces generated as packages ## Sequence diagrams * [t20001](./test_cases/t20001.md) - Basic sequence diagram test case * [t20002](./test_cases/t20002.md) - Free function sequence diagram test case diff --git a/docs/test_cases/t00002_class.png b/docs/test_cases/t00002_class.png index dd2ad759..606887e9 100644 Binary files a/docs/test_cases/t00002_class.png and b/docs/test_cases/t00002_class.png differ diff --git a/docs/test_cases/t00003_class.png b/docs/test_cases/t00003_class.png index d1eb9f95..38c4884d 100644 Binary files a/docs/test_cases/t00003_class.png and b/docs/test_cases/t00003_class.png differ diff --git a/docs/test_cases/t00004_class.png b/docs/test_cases/t00004_class.png index 132275f7..30e724f2 100644 Binary files a/docs/test_cases/t00004_class.png and b/docs/test_cases/t00004_class.png differ diff --git a/docs/test_cases/t00005_class.png b/docs/test_cases/t00005_class.png index 785beba3..d094d44a 100644 Binary files a/docs/test_cases/t00005_class.png and b/docs/test_cases/t00005_class.png differ diff --git a/docs/test_cases/t00006_class.png b/docs/test_cases/t00006_class.png index aab0c532..391163f0 100644 Binary files a/docs/test_cases/t00006_class.png and b/docs/test_cases/t00006_class.png differ diff --git a/docs/test_cases/t00007_class.png b/docs/test_cases/t00007_class.png index e59b437e..6db091fc 100644 Binary files a/docs/test_cases/t00007_class.png and b/docs/test_cases/t00007_class.png differ diff --git a/docs/test_cases/t00008_class.png b/docs/test_cases/t00008_class.png index 3390a88d..508d6029 100644 Binary files a/docs/test_cases/t00008_class.png and b/docs/test_cases/t00008_class.png differ diff --git a/docs/test_cases/t00009_class.png b/docs/test_cases/t00009_class.png index 1a918d19..be6770b5 100644 Binary files a/docs/test_cases/t00009_class.png and b/docs/test_cases/t00009_class.png differ diff --git a/docs/test_cases/t00010_class.png b/docs/test_cases/t00010_class.png index d0891517..064e0abf 100644 Binary files a/docs/test_cases/t00010_class.png and b/docs/test_cases/t00010_class.png differ diff --git a/docs/test_cases/t00011_class.png b/docs/test_cases/t00011_class.png index 13058526..a3350b01 100644 Binary files a/docs/test_cases/t00011_class.png and b/docs/test_cases/t00011_class.png differ diff --git a/docs/test_cases/t00012_class.png b/docs/test_cases/t00012_class.png index c4215544..4a20b22f 100644 Binary files a/docs/test_cases/t00012_class.png and b/docs/test_cases/t00012_class.png differ diff --git a/docs/test_cases/t00013_class.png b/docs/test_cases/t00013_class.png index 4e1b2484..234e7008 100644 Binary files a/docs/test_cases/t00013_class.png and b/docs/test_cases/t00013_class.png differ diff --git a/docs/test_cases/t00014_class.png b/docs/test_cases/t00014_class.png index f69b2167..1cacbd21 100644 Binary files a/docs/test_cases/t00014_class.png and b/docs/test_cases/t00014_class.png differ diff --git a/docs/test_cases/t00015_class.png b/docs/test_cases/t00015_class.png index b1752e0b..b1db060f 100644 Binary files a/docs/test_cases/t00015_class.png and b/docs/test_cases/t00015_class.png differ diff --git a/docs/test_cases/t00016_class.png b/docs/test_cases/t00016_class.png index d7effe54..ad5cc26f 100644 Binary files a/docs/test_cases/t00016_class.png and b/docs/test_cases/t00016_class.png differ diff --git a/docs/test_cases/t00017_class.png b/docs/test_cases/t00017_class.png index b1cfc0a8..0c3b9319 100644 Binary files a/docs/test_cases/t00017_class.png and b/docs/test_cases/t00017_class.png differ diff --git a/docs/test_cases/t00018_class.png b/docs/test_cases/t00018_class.png index 7bbc693b..0ff85809 100644 Binary files a/docs/test_cases/t00018_class.png and b/docs/test_cases/t00018_class.png differ diff --git a/docs/test_cases/t00019_class.png b/docs/test_cases/t00019_class.png index 5ee2f31e..2ae951a9 100644 Binary files a/docs/test_cases/t00019_class.png and b/docs/test_cases/t00019_class.png differ diff --git a/docs/test_cases/t00020_class.png b/docs/test_cases/t00020_class.png index 6189d478..6ec07dd2 100644 Binary files a/docs/test_cases/t00020_class.png and b/docs/test_cases/t00020_class.png differ diff --git a/docs/test_cases/t00021_class.png b/docs/test_cases/t00021_class.png index 13a26288..046b3d28 100644 Binary files a/docs/test_cases/t00021_class.png and b/docs/test_cases/t00021_class.png differ diff --git a/docs/test_cases/t00022_class.png b/docs/test_cases/t00022_class.png index 9e8810f4..a549aa9c 100644 Binary files a/docs/test_cases/t00022_class.png and b/docs/test_cases/t00022_class.png differ diff --git a/docs/test_cases/t00023_class.png b/docs/test_cases/t00023_class.png index bb7b7297..f72e5e6f 100644 Binary files a/docs/test_cases/t00023_class.png and b/docs/test_cases/t00023_class.png differ diff --git a/docs/test_cases/t00024_class.png b/docs/test_cases/t00024_class.png index edcbf280..e0f4592a 100644 Binary files a/docs/test_cases/t00024_class.png and b/docs/test_cases/t00024_class.png differ diff --git a/docs/test_cases/t00025_class.png b/docs/test_cases/t00025_class.png index 0cf73322..912b9393 100644 Binary files a/docs/test_cases/t00025_class.png and b/docs/test_cases/t00025_class.png differ diff --git a/docs/test_cases/t00026_class.png b/docs/test_cases/t00026_class.png index b99fb6dd..8a368706 100644 Binary files a/docs/test_cases/t00026_class.png and b/docs/test_cases/t00026_class.png differ diff --git a/docs/test_cases/t00027_class.png b/docs/test_cases/t00027_class.png index 8515edf9..63d68bde 100644 Binary files a/docs/test_cases/t00027_class.png and b/docs/test_cases/t00027_class.png differ diff --git a/docs/test_cases/t00028_class.png b/docs/test_cases/t00028_class.png index d11d4e73..00159de8 100644 Binary files a/docs/test_cases/t00028_class.png and b/docs/test_cases/t00028_class.png differ diff --git a/docs/test_cases/t00029_class.png b/docs/test_cases/t00029_class.png index 8a186767..3f177d7a 100644 Binary files a/docs/test_cases/t00029_class.png and b/docs/test_cases/t00029_class.png differ diff --git a/docs/test_cases/t00030_class.png b/docs/test_cases/t00030_class.png index 6ab171f1..366010e9 100644 Binary files a/docs/test_cases/t00030_class.png and b/docs/test_cases/t00030_class.png differ diff --git a/docs/test_cases/t00031_class.png b/docs/test_cases/t00031_class.png index 5fa47576..0884451b 100644 Binary files a/docs/test_cases/t00031_class.png and b/docs/test_cases/t00031_class.png differ diff --git a/docs/test_cases/t00032_class.png b/docs/test_cases/t00032_class.png index c9fe322c..f985cf2b 100644 Binary files a/docs/test_cases/t00032_class.png and b/docs/test_cases/t00032_class.png differ diff --git a/docs/test_cases/t00033_class.png b/docs/test_cases/t00033_class.png index d3d6200e..2ede2d79 100644 Binary files a/docs/test_cases/t00033_class.png and b/docs/test_cases/t00033_class.png differ diff --git a/docs/test_cases/t00034_class.png b/docs/test_cases/t00034_class.png index d6ad5510..bd8616ae 100644 Binary files a/docs/test_cases/t00034_class.png and b/docs/test_cases/t00034_class.png differ diff --git a/docs/test_cases/t00035_class.png b/docs/test_cases/t00035_class.png index 3acf67b1..b172e543 100644 Binary files a/docs/test_cases/t00035_class.png and b/docs/test_cases/t00035_class.png differ diff --git a/docs/test_cases/t00036.md b/docs/test_cases/t00036.md new file mode 100644 index 00000000..6a9947ce --- /dev/null +++ b/docs/test_cases/t00036.md @@ -0,0 +1,57 @@ +# t00036 - Class diagram with namespaces generated as packages +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00036_class: + type: class + generate_packages: true + glob: + - ../../tests/t00036/t00036.cc + using_namespace: + - clanguml::t00036 + include: + namespaces: + - clanguml::t00036 +``` +## Source code +File t00036.cc +```cpp +namespace clanguml { +namespace t00036 { + +namespace ns1 { + +enum class E { blue, yellow }; + +namespace ns11 { + +template struct A { + T a; +}; + +namespace ns111 { + +struct B { + A a_int; +}; + +} +} +} + +namespace ns2 { +namespace ns22 { + +struct C; + +} +} + +} // namespace t00036 +} // namespace clanguml + +``` +## Generated UML diagrams +![t00036_class](./t00036_class.png "Class diagram with namespaces generated as packages") diff --git a/docs/test_cases/t00036_class.png b/docs/test_cases/t00036_class.png new file mode 100644 index 00000000..8f95c3c8 Binary files /dev/null and b/docs/test_cases/t00036_class.png differ diff --git a/docs/test_cases/t30001_package.png b/docs/test_cases/t30001_package.png index 18ed09a8..31b48ded 100644 Binary files a/docs/test_cases/t30001_package.png and b/docs/test_cases/t30001_package.png differ diff --git a/docs/test_cases/t30002_package.png b/docs/test_cases/t30002_package.png index c8f91d6f..d5fc7e57 100644 Binary files a/docs/test_cases/t30002_package.png and b/docs/test_cases/t30002_package.png differ diff --git a/docs/test_cases/t30003_package.png b/docs/test_cases/t30003_package.png index 66a9b732..35b17732 100644 Binary files a/docs/test_cases/t30003_package.png and b/docs/test_cases/t30003_package.png differ diff --git a/docs/test_cases/t30004_package.png b/docs/test_cases/t30004_package.png index 31a11390..3c55a308 100644 Binary files a/docs/test_cases/t30004_package.png and b/docs/test_cases/t30004_package.png differ diff --git a/docs/test_cases/t30005_package.png b/docs/test_cases/t30005_package.png index b97d27f1..42d4066e 100644 Binary files a/docs/test_cases/t30005_package.png and b/docs/test_cases/t30005_package.png differ diff --git a/docs/test_cases/t30006_package.png b/docs/test_cases/t30006_package.png index 51f2e6ee..f015546a 100644 Binary files a/docs/test_cases/t30006_package.png and b/docs/test_cases/t30006_package.png differ diff --git a/docs/test_cases/t30007_package.png b/docs/test_cases/t30007_package.png index f97a8a02..fab55077 100644 Binary files a/docs/test_cases/t30007_package.png and b/docs/test_cases/t30007_package.png differ diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 3e7b906d..909f0d54 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -33,20 +33,30 @@ void generator::generate_alias(const class_ &c, std::ostream &ostr) const if (c.is_abstract()) class_type = "abstract"; - ostr << class_type << " \"" << c.full_name(); + auto full_name = c.full_name(); + + if (m_config.generate_packages()) + ostr << class_type << " \"" << c.full_name_no_ns(); + else + ostr << class_type << " \"" << c.full_name(); ostr << "\" as " << c.alias() << '\n'; } void generator::generate_alias(const enum_ &e, std::ostream &ostr) const { - ostr << "enum" - << " \"" << e.full_name(); + if (m_config.generate_packages()) + ostr << "enum" + << " \"" << e.name(); + else + ostr << "enum" + << " \"" << e.full_name(); ostr << "\" as " << e.alias() << '\n'; } -void generator::generate(const class_ &c, std::ostream &ostr) const +void generator::generate( + const class_ &c, std::ostream &ostr, std::ostream &relationships_ostr) const { namespace plantuml_common = clanguml::common::generators::plantuml; @@ -89,7 +99,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const return mp.to_string(m_config.using_namespace()); }); auto args_string = fmt::format("{}", fmt::join(params, ", ")); - if (m_config.generate_method_arguments() != + if (m_config.generate_method_arguments() == config::method_arguments::abbreviated) { args_string = clanguml::util::abbreviate(args_string, 10); } @@ -213,10 +223,11 @@ void generator::generate(const class_ &c, std::ostream &ostr) const generate_notes(ostr, c); // Print relationships - ostr << all_relations_str.str(); + relationships_ostr << all_relations_str.str(); } -void generator::generate(const enum_ &e, std::ostream &ostr) const +void generator::generate( + const enum_ &e, std::ostream &ostr, std::ostream &relationships_ostr) const { ostr << "enum " << e.alias(); @@ -255,7 +266,7 @@ void generator::generate(const enum_ &e, std::ostream &ostr) const relstr << '\n'; - ostr << relstr.str(); + relationships_ostr << relstr.str(); } catch (error::uml_alias_missing &ex) { LOG_ERROR("Skipping {} relation from {} to {} due " @@ -269,40 +280,72 @@ void generator::generate(const enum_ &e, std::ostream &ostr) const generate_notes(ostr, e); } +void generator::generate(const package &p, std::ostream &ostr, + std::ostream &relationships_ostr) const +{ + if (m_config.generate_packages()) { + LOG_DBG("Generating package {}", p.name()); + + ostr << "package [" << p.name() << "] "; + ostr << "as " << p.alias(); + + if (p.is_deprecated()) + ostr << " <>"; + + if (!p.style().empty()) + ostr << " " << p.style(); + + ostr << " {" << '\n'; + } + + for (const auto &subpackage : p) { + if (dynamic_cast(subpackage.get())) { + generate( + dynamic_cast(*subpackage), ostr, relationships_ostr); + } + if (dynamic_cast(subpackage.get())) { + generate_alias(dynamic_cast(*subpackage), ostr); + generate( + dynamic_cast(*subpackage), ostr, relationships_ostr); + } + if (dynamic_cast(subpackage.get())) { + generate_alias(dynamic_cast(*subpackage), ostr); + generate( + dynamic_cast(*subpackage), ostr, relationships_ostr); + } + } + + if (m_config.generate_packages()) { + ostr << "}" << '\n'; + } + + generate_notes(ostr, p); +} + void generator::generate(std::ostream &ostr) const { ostr << "@startuml" << '\n'; + std::stringstream relationship_ostr; + generate_plantuml_directives(ostr, m_config.puml().before); - if (m_config.should_include_entities("classes")) { - for (const auto &c : m_model.classes()) { - if (!m_config.should_include(c.name())) - continue; - generate_alias(c, ostr); - ostr << '\n'; + for (const auto &p : m_model) { + if (dynamic_cast(p.get())) { + generate(dynamic_cast(*p), ostr, relationship_ostr); } - - for (const auto &e : m_model.enums()) { - if (!m_config.should_include(e.name())) - continue; - generate_alias(e, ostr); - ostr << '\n'; + if (dynamic_cast(p.get())) { + generate_alias(dynamic_cast(*p), ostr); + generate(dynamic_cast(*p), ostr, relationship_ostr); } - - for (const auto &c : m_model.classes()) { - if (!m_config.should_include(c.name())) - continue; - generate(c, ostr); - ostr << '\n'; + if (dynamic_cast(p.get())) { + generate_alias(dynamic_cast(*p), ostr); + generate(dynamic_cast(*p), ostr, relationship_ostr); } + ostr << '\n'; } - if (m_config.should_include_entities("enums")) - for (const auto &e : m_model.enums()) { - generate(e, ostr); - ostr << '\n'; - } + ostr << relationship_ostr.str(); generate_config_layout_hints(ostr); @@ -310,5 +353,4 @@ void generator::generate(std::ostream &ostr) const ostr << "@enduml" << '\n'; } - } diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.h b/src/class_diagram/generators/plantuml/class_diagram_generator.h index ea6bf845..cb570ca3 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.h +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.h @@ -49,6 +49,7 @@ using common_generator = using clanguml::class_diagram::model::class_; using clanguml::class_diagram::model::enum_; +using clanguml::common::model::package; using clanguml::common::model::relationship_t; using clanguml::common::model::scope_t; @@ -62,9 +63,14 @@ public: void generate_alias(const enum_ &e, std::ostream &ostr) const; - void generate(const class_ &c, std::ostream &ostr) const; + void generate(const class_ &c, std::ostream &ostr, + std::ostream &relationships_ostr) const; - void generate(const enum_ &e, std::ostream &ostr) const; + void generate(const enum_ &e, std::ostream &ostr, + std::ostream &relationships_ostr) const; + + void generate(const package &p, std::ostream &ostr, + std::ostream &relationships_ostr) const; void generate(std::ostream &ostr) const override; }; diff --git a/src/class_diagram/model/class.cc b/src/class_diagram/model/class.cc index fc886186..fdb459ed 100644 --- a/src/class_diagram/model/class.cc +++ b/src/class_diagram/model/class.cc @@ -96,16 +96,36 @@ void class_::add_type_alias(type_alias &&ta) type_aliases_[ta.alias()] = std::move(ta); } +std::string class_::full_name_no_ns() const +{ + using namespace clanguml::util; + + std::ostringstream ostr; + + ostr << name(); + + render_template_params(ostr); + + return ostr.str(); +} + std::string class_::full_name(bool relative) const { using namespace clanguml::util; std::ostringstream ostr; - if (relative) - ostr << ns_relative(using_namespaces(), name()); + if (relative && starts_with(get_namespace(), using_namespaces())) + ostr << ns_relative(using_namespaces(), name_and_ns()); else - ostr << name(); + ostr << name_and_ns(); + render_template_params(ostr); + + return ostr.str(); +} +std::ostringstream &class_::render_template_params( + std::ostringstream &ostr) const +{ if (!templates_.empty()) { std::vector tnames; std::transform(templates_.cbegin(), templates_.cend(), @@ -114,11 +134,11 @@ std::string class_::full_name(bool relative) const if (!tmplt.type().empty()) res.push_back( - ns_relative(using_namespaces(), tmplt.type())); + util::ns_relative(using_namespaces(), tmplt.type())); if (!tmplt.name().empty()) res.push_back( - ns_relative(using_namespaces(), tmplt.name())); + util::ns_relative(using_namespaces(), tmplt.name())); if (!tmplt.default_value().empty()) { res.push_back("="); @@ -129,8 +149,7 @@ std::string class_::full_name(bool relative) const }); ostr << fmt::format("<{}>", fmt::join(tnames, ",")); } - - return ostr.str(); + return ostr; } bool class_::is_abstract() const diff --git a/src/class_diagram/model/class.h b/src/class_diagram/model/class.h index e84fba7d..62de2f15 100644 --- a/src/class_diagram/model/class.h +++ b/src/class_diagram/model/class.h @@ -36,6 +36,11 @@ class class_ : public common::model::element, public: class_(const std::vector &using_namespaces); + class_(const class_ &) = delete; + class_(class_ &&) noexcept = delete; + class_ &operator=(const class_ &) = delete; + class_ &operator=(class_ &&) = delete; + bool is_struct() const; void is_struct(bool is_struct); @@ -64,9 +69,13 @@ public: std::string full_name(bool relative = true) const override; + std::string full_name_no_ns() const; + bool is_abstract() const; private: + std::ostringstream &render_template_params(std::ostringstream &ostr) const; + bool is_struct_{false}; bool is_template_{false}; bool is_template_instantiation_{false}; diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 46ec8185..80c9c810 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -21,42 +21,89 @@ #include "util/error.h" #include "util/util.h" +#include +#include + namespace clanguml::class_diagram::model { -const std::vector diagram::classes() const { return classes_; } +const std::vector> diagram::classes() const +{ + return classes_; +} -const std::vector diagram::enums() const { return enums_; } +const std::vector> diagram::enums() const +{ + return enums_; +} bool diagram::has_class(const class_ &c) const { return std::any_of(classes_.cbegin(), classes_.cend(), - [&c](const auto &cc) { return cc.full_name() == c.full_name(); }); + [&c](const auto &cc) { return cc.get().full_name() == c.full_name(); }); } -void diagram::add_type_alias(type_alias &&ta) +bool diagram::has_enum(const enum_ &e) const { - LOG_DBG("Adding global alias: {} -> {}", ta.alias(), ta.underlying_type()); - - type_aliases_[ta.alias()] = std::move(ta); + return std::any_of(enums_.cbegin(), enums_.cend(), + [&e](const auto &ee) { return ee.get().full_name() == e.full_name(); }); } -void diagram::add_class(class_ &&c) +void diagram::add_type_alias(std::unique_ptr &&ta) { - LOG_DBG("Adding class: {}, {}", c.name(), c.full_name()); - if (!has_class(c)) - classes_.emplace_back(std::move(c)); + LOG_DBG( + "Adding global alias: {} -> {}", ta->alias(), ta->underlying_type()); + + type_aliases_[ta->alias()] = std::move(ta); +} + +void diagram::add_package(std::unique_ptr &&p) +{ + LOG_DBG("Adding namespace package: {}, {}", p->name(), p->full_name(true)); + + auto ns = p->get_relative_namespace(); + add_element(ns, std::move(p)); +} + +void diagram::add_class(std::unique_ptr &&c) +{ + LOG_DBG("Adding class: {}, {}", c->name(), c->full_name()); + + if (util::contains(c->name(), "::")) + throw std::runtime_error("Name cannot contain namespace: " + c->name()); + + if (util::contains(c->name(), "<")) + throw std::runtime_error("Name cannot contain <: " + c->name()); + + if (util::contains(c->name(), "*")) + throw std::runtime_error("Name cannot contain *: " + c->name()); + + if (!has_class(*c)) { + classes_.emplace_back(*c); + auto ns = c->get_relative_namespace(); + auto name = c->name(); + add_element(ns, std::move(c)); + ns.push_back(name); + const auto ccc = get_element(ns); + assert(ccc.value().name() == name); + } else - LOG_DBG("Class {} ({}) already in the model", c.name(), c.full_name()); + LOG_DBG( + "Class {} ({}) already in the model", c->name(), c->full_name()); } -void diagram::add_enum(enum_ &&e) +void diagram::add_enum(std::unique_ptr &&e) { - LOG_DBG("Adding enum: {}", e.name()); - auto it = std::find(enums_.begin(), enums_.end(), e); - if (it == enums_.end()) - enums_.emplace_back(std::move(e)); + LOG_DBG("Adding enum: {}", e->name()); + + assert(!util::contains(e->name(), "::")); + + if (!has_enum(*e)) { + enums_.emplace_back(*e); + auto ns = e->get_relative_namespace(); + add_element(ns, std::move(e)); + } else - LOG_DBG("Enum {} already in the model", e.name()); + LOG_DBG("Enum {} already in the model", e->name()); } std::string diagram::to_alias(const std::string &full_name) const @@ -64,18 +111,20 @@ std::string diagram::to_alias(const std::string &full_name) const LOG_DBG("Looking for alias for {}", full_name); for (const auto &c : classes_) { - if (c.full_name() == full_name) { - return c.alias(); + const auto &cc = c.get(); + if (cc.full_name() == full_name) { + return c->alias(); } } for (const auto &e : enums_) { - if (e.full_name() == full_name) { - return e.alias(); + if (e.get().full_name() == full_name) { + return e->alias(); } } throw error::uml_alias_missing( fmt::format("Missing alias for {}", full_name)); } + } diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index 5ceecfb9..86f8acd7 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -19,6 +19,8 @@ #include "class.h" #include "common/model/diagram.h" +#include "common/model/nested_trait.h" +#include "common/model/package.h" #include "enum.h" #include "type_alias.h" @@ -27,7 +29,9 @@ namespace clanguml::class_diagram::model { -class diagram : public clanguml::common::model::diagram { +class diagram : public clanguml::common::model::diagram, + public clanguml::common::model::nested_trait< + clanguml::common::model::element> { public: diagram() = default; @@ -36,23 +40,29 @@ public: diagram &operator=(const diagram &) = delete; diagram &operator=(diagram &&) = default; - const std::vector classes() const; + const std::vector> classes() const; - const std::vector enums() const; + const std::vector> enums() const; bool has_class(const class_ &c) const; - void add_type_alias(type_alias &&ta); + bool has_enum(const enum_ &e) const; - void add_class(class_ &&c); + void add_type_alias(std::unique_ptr &&ta); - void add_enum(enum_ &&e); + void add_class(std::unique_ptr &&c); + + void add_enum(std::unique_ptr &&e); + + void add_package(std::unique_ptr &&p); std::string to_alias(const std::string &full_name) const; + friend void print_diagram_tree(const diagram &d, const int level); + private: - std::vector classes_; - std::vector enums_; - std::map type_aliases_; + std::vector> classes_; + std::vector> enums_; + std::map> type_aliases_; }; } diff --git a/src/class_diagram/model/enum.cc b/src/class_diagram/model/enum.cc index 4b8fb341..49b75c77 100644 --- a/src/class_diagram/model/enum.cc +++ b/src/class_diagram/model/enum.cc @@ -29,7 +29,10 @@ enum_::enum_(const std::vector &using_namespaces) { } -bool operator==(const enum_ &l, const enum_ &r) { return l.name() == r.name(); } +bool operator==(const enum_ &l, const enum_ &r) +{ + return (l.get_namespace() == r.get_namespace()) && (l.name() == r.name()); +} std::string enum_::full_name(bool relative) const { diff --git a/src/class_diagram/model/enum.h b/src/class_diagram/model/enum.h index cd2070fa..f54da03e 100644 --- a/src/class_diagram/model/enum.h +++ b/src/class_diagram/model/enum.h @@ -29,6 +29,12 @@ class enum_ : public common::model::element, public: enum_(const std::vector &using_namespaces); + enum_(const enum_ &) = delete; + enum_(enum_ &&) = default; + enum_ &operator=(const enum_ &) = delete; + enum_ &operator=(enum_ &&) = default; + + // TODO: Do we need this? friend bool operator==(const enum_ &l, const enum_ &r); std::string full_name(bool relative = true) const override; diff --git a/src/class_diagram/visitor/translation_unit_context.cc b/src/class_diagram/visitor/translation_unit_context.cc index 77e9c50e..7d9f612d 100644 --- a/src/class_diagram/visitor/translation_unit_context.cc +++ b/src/class_diagram/visitor/translation_unit_context.cc @@ -32,6 +32,53 @@ translation_unit_context::translation_unit_context( { } +bool translation_unit_context::has_namespace_alias( + const std::string &full_name) const +{ + bool res = + namespace_alias_index_.find(full_name) != namespace_alias_index_.end(); + + LOG_DBG("Alias {} {} found in index", full_name, res ? "" : "not"); + + return res; +} + +void translation_unit_context::add_namespace_alias(const std::string &full_name, + type_safe::object_ref ref) +{ + if (!has_namespace_alias(full_name)) { + LOG_DBG( + "Stored namespace alias: {} -> {} ", full_name, ref.get().name()); + + namespace_alias_index_.emplace(full_name, std::move(ref)); + } +} + +type_safe::object_ref +translation_unit_context::get_namespace_alias( + const std::string &full_name) const +{ + assert(has_namespace_alias(full_name)); + + return namespace_alias_index_.at(full_name); +} + +type_safe::object_ref +translation_unit_context::get_namespace_alias_final( + const cppast::cpp_namespace &ns) const +{ + auto ns_full_name = cx::util::full_name({}, ns); + + ns_full_name = cx::util::ns(ns) + "::" + ns_full_name; + + if (has_namespace_alias(ns_full_name)) { + return get_namespace_alias_final( + namespace_alias_index_.at(ns_full_name).get()); + } + + return type_safe::ref(ns); +} + bool translation_unit_context::has_type_alias( const std::string &full_name) const { @@ -132,4 +179,16 @@ clanguml::class_diagram::model::diagram &translation_unit_context::diagram() return diagram_; } +void translation_unit_context::set_current_package( + type_safe::optional_ref p) +{ + current_package_ = p; +} + +type_safe::optional_ref +translation_unit_context::get_current_package() const +{ + return current_package_; +} + } diff --git a/src/class_diagram/visitor/translation_unit_context.h b/src/class_diagram/visitor/translation_unit_context.h index a5dff73c..453d2805 100644 --- a/src/class_diagram/visitor/translation_unit_context.h +++ b/src/class_diagram/visitor/translation_unit_context.h @@ -20,6 +20,7 @@ #include "config/config.h" #include +#include #include #include @@ -31,6 +32,17 @@ public: clanguml::class_diagram::model::diagram &diagram, const clanguml::config::class_diagram &config); + bool has_namespace_alias(const std::string &full_name) const; + + void add_namespace_alias(const std::string &full_name, + type_safe::object_ref ref); + + type_safe::object_ref get_namespace_alias( + const std::string &full_name) const; + + type_safe::object_ref + get_namespace_alias_final(const cppast::cpp_namespace &t) const; + bool has_type_alias(const std::string &full_name) const; void add_type_alias(const std::string &full_name, @@ -62,6 +74,10 @@ public: clanguml::class_diagram::model::diagram &diagram(); + void set_current_package(type_safe::optional_ref p); + + type_safe::optional_ref get_current_package() const; + private: // Current visitor namespace std::vector namespace_; @@ -75,6 +91,10 @@ private: // Reference to class diagram config const clanguml::config::class_diagram &config_; + // Map of discovered aliases (declared with 'namespace' keyword) + std::map> + namespace_alias_index_; + // Map of discovered aliases (declared with 'using' keyword) std::map> alias_index_; @@ -82,6 +102,8 @@ private: // Map of discovered template aliases (declared with 'using' keyword) std::map> alias_template_index_; + + type_safe::optional_ref current_package_; }; } diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 2ce17a31..18e50fab 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -32,8 +32,6 @@ #include #include -#include - namespace clanguml::class_diagram::visitor { using clanguml::class_diagram::model::class_; @@ -94,8 +92,10 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) const auto &ns_declaration = static_cast(e); if (!ns_declaration.is_anonymous() && - !ns_declaration.is_inline()) - ctx.push_namespace(e.name()); + !ns_declaration.is_inline()) { + + process_namespace(e, ns_declaration); + } } else { LOG_DBG("========== Leaving '{}' - {}", e.name(), @@ -108,6 +108,15 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) ctx.pop_namespace(); } } + else if (e.kind() == cppast::cpp_entity_kind::namespace_alias_t) { + auto &na = static_cast(e); + + for (const auto &alias_target : + na.target().get(ctx.entity_index())) { + auto full_ns = cx::util::full_name(ctx.get_namespace(), na); + ctx.add_namespace_alias(full_ns, alias_target); + } + } else if (e.kind() == cppast::cpp_entity_kind::class_template_specialization_t) { LOG_DBG("========== Visiting '{}' - {}", @@ -138,7 +147,7 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) } if (ctx.config().should_include( - cx::util::fully_prefixed(ctx.get_namespace(), cls))) + ctx.get_namespace(), cls.name())) process_class_declaration(cls); } else if (e.kind() == cppast::cpp_entity_kind::enum_t) { @@ -149,7 +158,7 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) auto &enm = static_cast(e); if (ctx.config().should_include( - cx::util::fully_prefixed(ctx.get_namespace(), enm))) + ctx.get_namespace(), enm.name())) process_enum_declaration(enm); } else if (e.kind() == cppast::cpp_entity_kind::type_alias_t) { @@ -158,15 +167,7 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) cppast::to_string(e.kind())); auto &ta = static_cast(e); - type_alias t; - t.set_alias(cx::util::full_name(ctx.get_namespace(), ta)); - t.set_underlying_type(cx::util::full_name(ta.underlying_type(), - ctx.entity_index(), cx::util::is_inside_class(e))); - - ctx.add_type_alias(cx::util::full_name(ctx.get_namespace(), ta), - type_safe::ref(ta.underlying_type())); - - ctx.diagram().add_type_alias(std::move(t)); + process_type_alias(ta); } else if (e.kind() == cppast::cpp_entity_kind::alias_template_t) { LOG_DBG("========== Visiting '{}' - {}", @@ -175,24 +176,88 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) auto &at = static_cast(e); - if (at.type_alias().underlying_type().kind() == - cppast::cpp_type_kind::unexposed_t) { - LOG_WARN("Template alias has unexposed underlying type: {}", - static_cast( - at.type_alias().underlying_type()) - .name()); - } - else { - class_ tinst = build_template_instantiation(static_cast< - const cppast::cpp_template_instantiation_type &>( - at.type_alias().underlying_type())); - - ctx.diagram().add_class(std::move(tinst)); - } + process_type_alias_template(at); } }); } +void translation_unit_visitor::process_type_alias_template( + const cppast::cpp_alias_template &at) +{ + if (at.type_alias().underlying_type().kind() == + cppast::cpp_type_kind::unexposed_t) { + LOG_WARN("Template alias has unexposed underlying type: {}", + static_cast( + at.type_alias().underlying_type()) + .name()); + } + else { + auto tinst = build_template_instantiation( + static_cast( + resolve_alias(at.type_alias().underlying_type()))); + + ctx.add_type_alias_template( + cx::util::full_name(ctx.get_namespace(), at), + type_safe::ref(at.type_alias().underlying_type())); + + if (ctx.config().should_include(tinst->get_namespace(), tinst->name())) + ctx.diagram().add_class(std::move(tinst)); + } +} + +void translation_unit_visitor::process_type_alias( + const cppast::cpp_type_alias &ta) +{ + auto t = std::make_unique(); + t->set_alias(cx::util::full_name(ctx.get_namespace(), ta)); + t->set_underlying_type(cx::util::full_name(ta.underlying_type(), + ctx.entity_index(), cx::util::is_inside_class(ta))); + + ctx.add_type_alias(cx::util::full_name(ctx.get_namespace(), ta), + type_safe::ref(ta.underlying_type())); + + ctx.diagram().add_type_alias(std::move(t)); +} + +void translation_unit_visitor::process_namespace( + const cppast::cpp_entity &e, const cppast::cpp_namespace &ns_declaration) +{ + std::vector package_parent = ctx.get_namespace(); + auto package_path = package_parent; + package_path.push_back(e.name()); + + auto usn = util::split(ctx.config().using_namespace()[0], "::"); + + if (!util::starts_with(usn, package_path)) { + auto p = std::make_unique(usn); + util::remove_prefix(package_path, usn); + + p->set_name(e.name()); + p->set_namespace(package_parent); + + if (ns_declaration.comment().has_value()) + p->add_decorators( + decorators::parse(ns_declaration.comment().value())); + + p->set_style(p->style_spec()); + + for (const auto &attr : ns_declaration.attributes()) { + if (attr.kind() == cppast::cpp_attribute_kind::deprecated) { + p->set_deprecated(true); + break; + } + } + + if (!p->skip()) { + ctx.diagram().add_package(std::move(p)); + ctx.set_current_package( + ctx.diagram().get_element( + package_path)); + } + } + ctx.push_namespace(e.name()); +} + void translation_unit_visitor::process_enum_declaration( const cppast::cpp_enum &enm) { @@ -202,8 +267,11 @@ void translation_unit_visitor::process_enum_declaration( return; } - enum_ e{ctx.config().using_namespace()}; - e.set_name(cx::util::full_name(ctx.get_namespace(), enm)); + auto e_ptr = std::make_unique( + util::split(ctx.config().using_namespace()[0], "::")); + auto &e = *e_ptr; + e.set_name(enm.name()); + e.set_namespace(ctx.get_namespace()); if (enm.comment().has_value()) e.add_decorators(decorators::parse(enm.comment().value())); @@ -230,28 +298,31 @@ void translation_unit_visitor::process_enum_declaration( e.add_relationship({relationship_t::kContainment, cx::util::full_name(ctx.get_namespace(), cur.value())}); - LOG_DBG("Added containment relationship {} +-- {}", e.name()); + LOG_DBG("Added containment relationship {} +-- {}", + cx::util::full_name(ctx.get_namespace(), cur.value()), + e.name()); break; } } - ctx.diagram().add_enum(std::move(e)); + ctx.diagram().add_enum(std::move(e_ptr)); } void translation_unit_visitor::process_class_declaration( const cppast::cpp_class &cls, type_safe::optional_ref tspec) { - class_ c{ctx.config().using_namespace()}; + auto c_ptr = std::make_unique( + util::split(ctx.config().using_namespace()[0], "::")); + auto &c = *c_ptr; c.is_struct(cls.class_kind() == cppast::cpp_class_kind::struct_t); - c.set_name(cx::util::full_name(ctx.get_namespace(), cls)); + // c.set_name(cx::util::full_name(ctx.get_namespace(), cls)); + c.set_name(cls.name()); + c.set_namespace(ctx.get_namespace()); if (cls.comment().has_value()) c.add_decorators(decorators::parse(cls.comment().value())); - cppast::cpp_access_specifier_kind last_access_specifier = - cppast::cpp_access_specifier_kind::cpp_private; - // Process class documentation comment if (cppast::is_templated(cls)) { if (cls.parent().value().comment().has_value()) @@ -269,6 +340,229 @@ void translation_unit_visitor::process_class_declaration( c.set_style(c.style_spec()); // Process class child entities + process_class_children(cls, c); + + // Process class bases + process_class_bases(cls, c); + + // Process class template arguments + if (cppast::is_templated(cls)) { + bool skip = process_template_parameters(cls, c, tspec); + if (skip) + return; + } + + // Find if class is contained in another class + process_class_containment(cls, c); + + cls.set_user_data(strdup(c.full_name().c_str())); + + LOG_DBG("Setting user data for class {}, {}", + static_cast(cls.user_data()), + fmt::ptr(reinterpret_cast(&cls))); + + assert(c_ptr); + ctx.diagram().add_class(std::move(c_ptr)); +} + +void translation_unit_visitor::process_class_containment( + const cppast::cpp_class &cls, class_ &c) const +{ + for (auto cur = cls.parent(); cur; cur = cur.value().parent()) { + // find nearest parent class, if any + if (cur.value().kind() == cppast::cpp_entity_kind::class_t) { + c.add_relationship({relationship_t::kContainment, + cx::util::full_name(ctx.get_namespace(), cur.value())}); + + LOG_DBG("Added containment relationship {}", c.full_name()); + + break; + } + } +} + +bool translation_unit_visitor::process_template_parameters( + const cppast::cpp_class &cls, class_ &c, + const type_safe::optional_ref + &tspec) +{ + LOG_DBG("Processing class {} template parameters...", cls.name()); + + auto scope = cppast::cpp_scope_name(type_safe::ref(cls)); + // Even if this is a template the scope.is_templated() returns + // false when the template parameter list is empty + if (scope.is_templated()) { + process_scope_template_parameters(c, scope); + } + else { + LOG_WARN("Class {} is templated but it's scope {} is not - " + "probably this is a specialization", + cls.name(), scope.name()); + + // Add specialization arguments + if (tspec) { + if (!tspec.value().arguments_exposed()) { + process_unexposed_template_specialization_parameters(tspec, c); + } + else { + process_exposed_template_specialization_parameters(tspec, c); + } + } + else { + LOG_DBG("Skipping template class declaration which has only " + "unexposed arguments but no tspec provided"); + return true; + } + } + + return false; +} + +void translation_unit_visitor::process_scope_template_parameters( + class_ &c, const cppast::cpp_scope_name &scope) +{ + for (const auto &tp : scope.template_parameters()) { + if (tp.kind() == cppast::cpp_entity_kind::template_type_parameter_t) { + LOG_DBG("Processing template type parameter {}", tp.name()); + process_template_type_parameter( + static_cast(tp), + c); + } + else if (tp.kind() == + cppast::cpp_entity_kind::non_type_template_parameter_t) { + LOG_DBG("Processing template nontype parameter {}", tp.name()); + process_template_nontype_parameter( + static_cast( + tp), + c); + } + else if (tp.kind() == + cppast::cpp_entity_kind::template_template_parameter_t) { + LOG_DBG("Processing template template parameter {}", tp.name()); + process_template_template_parameter( + static_cast( + tp), + c); + } + } +} + +void translation_unit_visitor:: + process_exposed_template_specialization_parameters( + const type_safe::optional_ref + &tspec, + class_ &c) +{ + for (auto &tp : tspec.value().parameters()) { + switch (tp.kind()) { + case cppast::cpp_entity_kind::template_type_parameter_t: { + LOG_DBG("Processing template type parameter {}", tp.name()); + process_template_type_parameter( + static_cast(tp), + c); + } break; + case cppast::cpp_entity_kind::non_type_template_parameter_t: { + LOG_DBG("Processing template nontype parameter {}", tp.name()); + process_template_nontype_parameter( + static_cast( + tp), + c); + } break; + case cppast::cpp_entity_kind::template_template_parameter_t: { + LOG_DBG("Processing template template parameter {}", tp.name()); + process_template_template_parameter( + static_cast( + tp), + c); + } break; + default: + LOG_DBG("Unhandled template parameter " + "type {}", + cppast::to_string(tp.kind())); + break; + } + } +} + +void translation_unit_visitor:: + process_unexposed_template_specialization_parameters( + const type_safe::optional_ref + &tspec, + class_ &c) const +{ + auto ua = tspec.value().unexposed_arguments().as_string(); + + // Naive parse of template arguments: + auto toks = util::split(ua, ","); + for (const auto &t : toks) { + c.add_template({t}); + + if (!tspec.value().primary_template().is_overloaded()) { + if (tspec.value() + .primary_template() + .get(ctx.entity_index()) + .size() == 0) { + LOG_WARN("Template {} has no exposed parameters", + tspec.value().name()); + + continue; + } + } + + const auto &primary_template_ref = static_cast< + const cppast::cpp_class_template &>( + tspec.value().primary_template().get(ctx.entity_index())[0].get()) + .class_(); + + if (primary_template_ref.user_data()) { + auto base_template_full_name = + static_cast(primary_template_ref.user_data()); + LOG_DBG("Primary template ref set to: {}", base_template_full_name); + // Add template specialization/instantiation + // relationship + c.add_relationship( + {relationship_t::kInstantiation, base_template_full_name}); + } + else { + LOG_WARN("No user data for base template {}", + primary_template_ref.name()); + } + } +} +void translation_unit_visitor::process_class_bases( + const cppast::cpp_class &cls, class_ &c) const +{ + for (auto &base : cls.bases()) { + class_parent cp; + cp.set_name(cx::util::fully_prefixed(ctx.get_namespace(), base)); + cp.is_virtual(base.is_virtual()); + + switch (base.access_specifier()) { + case cppast::cpp_private: + cp.set_access(access_t::kPrivate); + break; + case cppast::cpp_public: + cp.set_access(access_t::kPublic); + break; + case cppast::cpp_protected: + cp.set_access(access_t::kProtected); + break; + default: + cp.set_access(access_t::kPublic); + } + + LOG_DBG("Found base class {} for class {}", cp.name(), c.name()); + + c.add_parent(std::move(cp)); + } +} + +void translation_unit_visitor::process_class_children( + const cppast::cpp_class &cls, class_ &c) +{ + cppast::cpp_access_specifier_kind last_access_specifier = + cppast::cpp_access_specifier_kind::cpp_private; + if (c.is_struct()) last_access_specifier = cppast::cpp_access_specifier_kind::cpp_public; @@ -336,191 +630,6 @@ void translation_unit_visitor::process_class_declaration( cppast::to_string(child.kind())); } } - - // Process class bases - for (auto &base : cls.bases()) { - class_parent cp; - cp.set_name( - clanguml::cx::util::fully_prefixed(ctx.get_namespace(), base)); - cp.is_virtual(base.is_virtual()); - - switch (base.access_specifier()) { - case cppast::cpp_access_specifier_kind::cpp_private: - cp.set_access(access_t::kPrivate); - break; - case cppast::cpp_access_specifier_kind::cpp_public: - cp.set_access(access_t::kPublic); - break; - case cppast::cpp_access_specifier_kind::cpp_protected: - cp.set_access(access_t::kProtected); - break; - default: - cp.set_access(access_t::kPublic); - } - - LOG_DBG("Found base class {} for class {}", cp.name(), c.name()); - - c.add_parent(std::move(cp)); - } - - // Process class template arguments - if (cppast::is_templated(cls)) { - LOG_DBG("Processing class template parameters..."); - auto scope = cppast::cpp_scope_name(type_safe::ref(cls)); - // Even if this is a template the scope.is_templated() returns - // false when the template parameter list is empty - if (scope.is_templated()) { - for (const auto &tp : scope.template_parameters()) { - if (tp.kind() == - cppast::cpp_entity_kind::template_type_parameter_t) { - LOG_DBG("Processing template type parameter {}", tp.name()); - process_template_type_parameter( - static_cast< - const cppast::cpp_template_type_parameter &>(tp), - c); - } - else if (tp.kind() == - cppast::cpp_entity_kind::non_type_template_parameter_t) { - LOG_DBG( - "Processing template nontype parameter {}", tp.name()); - process_template_nontype_parameter( - static_cast< - const cppast::cpp_non_type_template_parameter &>( - tp), - c); - } - else if (tp.kind() == - cppast::cpp_entity_kind::template_template_parameter_t) { - LOG_DBG( - "Processing template template parameter {}", tp.name()); - process_template_template_parameter( - static_cast< - const cppast::cpp_template_template_parameter &>( - tp), - c); - } - } - } - else { - LOG_WARN("Class {} is templated but it's scope {} is not - " - "probably this is a specialization", - cls.name(), scope.name()); - - // Add specialization arguments - if (tspec) { - if (!tspec.value().arguments_exposed()) { - // Create template specialization with unexposed arguments - auto ua = tspec.value().unexposed_arguments().as_string(); - - // Naive parse of template arguments: - auto toks = util::split(ua, ","); - for (const auto &t : toks) { - c.add_template({t}); - - if (!tspec.value().primary_template().is_overloaded()) { - if (tspec.value() - .primary_template() - .get(ctx.entity_index()) - .size() == 0) { - LOG_WARN("Template {} has no exposed arguments", - tspec.value().name()); - - continue; - } - } - - const auto &primary_template_ref = - static_cast( - tspec.value() - .primary_template() - .get(ctx.entity_index())[0] - .get()) - .class_(); - - if (primary_template_ref.user_data()) { - auto base_template_full_name = - static_cast( - primary_template_ref.user_data()); - LOG_DBG("Primary template ref set to: {}", - base_template_full_name); - // Add template specialization/instantiation - // relationship - c.add_relationship({relationship_t::kInstantiation, - base_template_full_name}); - } - else { - LOG_WARN("No user data for base template {}", - primary_template_ref.name()); - } - } - } - else { - for (auto &tp : tspec.value().parameters()) { - switch (tp.kind()) { - case cppast::cpp_entity_kind:: - template_type_parameter_t: { - LOG_DBG("Processing template type parameter {}", - tp.name()); - process_template_type_parameter( - static_cast(tp), - c); - } break; - case cppast::cpp_entity_kind:: - non_type_template_parameter_t: { - LOG_DBG("Processing template nontype parameter {}", - tp.name()); - process_template_nontype_parameter( - static_cast(tp), - c); - } break; - case cppast::cpp_entity_kind:: - template_template_parameter_t: { - LOG_DBG("Processing template template parameter {}", - tp.name()); - process_template_template_parameter( - static_cast(tp), - c); - } break; - default: - LOG_DBG("Unhandled template parameter " - "type {}", - cppast::to_string(tp.kind())); - break; - } - } - } - } - else { - LOG_DBG("Skipping template class declaration which has only " - "unexposed arguments but no tspec provided"); - return; - } - } - } - - // Find if class is contained in another class - for (auto cur = cls.parent(); cur; cur = cur.value().parent()) { - // find nearest parent class, if any - if (cur.value().kind() == cppast::cpp_entity_kind::class_t) { - c.add_relationship({relationship_t::kContainment, - cx::util::full_name(ctx.get_namespace(), cur.value())}); - - LOG_DBG("Added containment relationship {}", c.full_name()); - - break; - } - } - - cls.set_user_data(strdup(c.full_name().c_str())); - - LOG_DBG("Setting user data for class {}, {}", - static_cast(cls.user_data()), - fmt::ptr(reinterpret_cast(&cls))); - - ctx.diagram().add_class(std::move(c)); } bool translation_unit_visitor::process_field_with_template_instantiation( @@ -532,6 +641,8 @@ bool translation_unit_visitor::process_field_with_template_instantiation( bool res = false; + auto tr_declaration = cppast::to_string(tr); + const auto &template_instantiation_type = static_cast(tr); @@ -539,7 +650,15 @@ bool translation_unit_visitor::process_field_with_template_instantiation( static_cast( resolve_alias(template_instantiation_type)); - class_ tinst = build_template_instantiation(unaliased); + auto tr_unaliased_declaration = cppast::to_string(unaliased); + + std::unique_ptr tinst_ptr; + if (util::contains(tr_declaration, "<")) + tinst_ptr = build_template_instantiation(template_instantiation_type); + else + tinst_ptr = build_template_instantiation(unaliased); + + auto &tinst = *tinst_ptr; // Infer the relationship of this field to the template // instantiation @@ -547,7 +666,6 @@ bool translation_unit_visitor::process_field_with_template_instantiation( if (mv.type().kind() == cppast::cpp_type_kind::pointer_t || mv.type().kind() == cppast::cpp_type_kind::reference_t) relationship_type = relationship_t::kAssociation; - else relationship_type = relationship_t::kAggregation; @@ -566,18 +684,24 @@ bool translation_unit_visitor::process_field_with_template_instantiation( } } - if (ctx.config().should_include(tinst.name())) { + if (ctx.config().should_include(tinst.get_namespace(), tinst.name())) { LOG_DBG("Adding field instantiation relationship {} {} {} : {}", rr.destination(), clanguml::common::model::to_string(rr.type()), c.full_name(), rr.label()); c.add_relationship(std::move(rr)); + if (tr_declaration != tr_unaliased_declaration) { + // Add template instantiation/specialization relationship; + tinst.add_relationship( + {relationship_t::kInstantiation, tr_unaliased_declaration}); + } + res = true; LOG_DBG("Created template instantiation: {}", tinst.full_name()); - ctx.diagram().add_class(std::move(tinst)); + ctx.diagram().add_class(std::move(tinst_ptr)); } return res; @@ -611,11 +735,14 @@ void translation_unit_visitor::process_field( else if (tr.kind() == cppast::cpp_type_kind::user_defined_t) { LOG_DBG("Processing user defined type field {} {}", cppast::to_string(tr), mv.name()); + if (resolve_alias(tr).kind() == + cppast::cpp_type_kind::template_instantiation_t) + template_instantiation_added_as_aggregation = + process_field_with_template_instantiation(mv, tr, c, m, as); } else if (tr.kind() == cppast::cpp_type_kind::template_instantiation_t) { template_instantiation_added_as_aggregation = - process_field_with_template_instantiation( - mv, resolve_alias(tr), c, m, as); + process_field_with_template_instantiation(mv, tr, c, m, as); } else if (tr.kind() == cppast::cpp_type_kind::unexposed_t) { LOG_DBG( @@ -623,6 +750,8 @@ void translation_unit_visitor::process_field( // TODO } + auto tr_declaration = cppast::to_string(tr); + if (!m.skip_relationship() && !template_instantiation_added_as_aggregation && (tr.kind() != cppast::cpp_type_kind::builtin_t) && @@ -869,11 +998,14 @@ void translation_unit_visitor::process_function_parameter( if (!mp.skip_relationship()) { // find relationship for the type std::vector> relationships; + find_relationships(cppast::remove_cv(param.type()), relationships, relationship_t::kDependency); + for (const auto &[type, relationship_type] : relationships) { - if ((relationship_type != relationship_t::kNone) && - (type != c.name())) { + if (ctx.config().should_include(cx::util::split_ns(type)) && + (relationship_type != relationship_t::kNone) && + (type != c.name_and_ns())) { relationship r{relationship_t::kDependency, type}; LOG_DBG("Adding field relationship {} {} {} : {}", @@ -885,98 +1017,99 @@ void translation_unit_visitor::process_function_parameter( } } - // Also consider the container itself if it is user defined type + // Also consider the container itself if it is a template instantiation + // it's arguments could count as reference to relevant types const auto &t = cppast::remove_cv(cx::util::unreferenced(param.type())); if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { - auto &template_instantiation_type = - static_cast(t); - - if (template_instantiation_type.primary_template() - .get(ctx.entity_index()) - .size()) { - - // Here we need the name of the primary template with full - // namespace prefix to apply config inclusion filters - auto primary_template_name = - cx::util::full_name(ctx.get_namespace(), - template_instantiation_type.primary_template() - .get(ctx.entity_index())[0] - .get()); - // Now check if the template arguments of this function param - // are a subset of the method template params - if yes this is - // not an instantiation but just a reference to an existing - // template - bool template_is_not_instantiation{false}; - if (template_instantiation_type.arguments_exposed()) { - LOG_DBG("Processing template method argument exposed " - "parameters..."); - - for (const auto &template_argument : - template_instantiation_type.arguments().value()) { - const auto template_argument_name = - cppast::to_string(template_argument.type().value()); - if (template_parameter_names.count( - template_argument_name) > 0) { - template_is_not_instantiation = true; - break; - } - } - } - else { - LOG_DBG("Processing template method argument unexposed " - "parameters: ", - template_instantiation_type.unexposed_arguments()); - // TODO: Process unexposed arguments by manually parsing the - // arguments string - } - - LOG_DBG("Maybe building instantiation for: {}", - primary_template_name); - - if (ctx.config().should_include(primary_template_name)) { - - if (template_is_not_instantiation) { - LOG_DBG("Template is not an instantiation - " - "only adding reference to template {}", - cx::util::full_name(cppast::remove_cv(t), - ctx.entity_index(), false)); - relationship rr{relationship_t::kDependency, - cx::util::full_name(cppast::remove_cv(t), - ctx.entity_index(), false)}; - LOG_DBG("Adding field template dependency relationship " - "{} {} {} " - ": {}", - rr.destination(), - clanguml::common::model::to_string(rr.type()), - c.full_name(), rr.label()); - c.add_relationship(std::move(rr)); - } - else { - // First check if tinst already exists - class_ tinst = build_template_instantiation( - template_instantiation_type); - - relationship rr{ - relationship_t::kDependency, tinst.full_name()}; - - LOG_DBG("Adding field dependency relationship {} {} {} " - ": {}", - rr.destination(), - clanguml::common::model::to_string(rr.type()), - c.full_name(), rr.label()); - - c.add_relationship(std::move(rr)); - - ctx.diagram().add_class(std::move(tinst)); - } - } - } + process_function_parameter_find_relationships_in_template( + c, template_parameter_names, t); } } m.add_parameter(std::move(mp)); } +void translation_unit_visitor:: + process_function_parameter_find_relationships_in_template(class_ &c, + const std::set &template_parameter_names, + const cppast::cpp_type &t) +{ + auto &template_instantiation_type = + static_cast(t); + + if (template_instantiation_type.primary_template() + .get(ctx.entity_index()) + .size()) { + // Check if the template arguments of this function param + // are a subset of the method template params - if yes this is + // not an instantiation but just a reference to an existing + // template + bool template_is_not_instantiation{false}; + if (template_instantiation_type.arguments_exposed()) { + LOG_DBG("Processing template method argument exposed " + "parameters..."); + + for (const auto &template_argument : + template_instantiation_type.arguments().value()) { + const auto template_argument_name = + cppast::to_string(template_argument.type().value()); + if (template_parameter_names.count(template_argument_name) > + 0) { + template_is_not_instantiation = true; + break; + } + } + } + else { + LOG_DBG("Processing template method argument unexposed " + "parameters: ", + template_instantiation_type.unexposed_arguments()); + // TODO: Process unexposed arguments by manually parsing the + // arguments string + } + + if (!ctx.config().should_include(ctx.get_namespace(), + template_instantiation_type.primary_template() + .get(ctx.entity_index())[0] + .get() + .name())) { + return; + } + + if (template_is_not_instantiation) { + LOG_DBG("Template is not an instantiation - " + "only adding reference to template {}", + cx::util::full_name( + cppast::remove_cv(t), ctx.entity_index(), false)); + relationship rr{relationship_t::kDependency, + cx::util::full_name( + cppast::remove_cv(t), ctx.entity_index(), false)}; + LOG_DBG("Adding field template dependency relationship " + "{} {} {} " + ": {}", + rr.destination(), common::model::to_string(rr.type()), + c.full_name(), rr.label()); + c.add_relationship(std::move(rr)); + } + else { + // First check if tinst already exists + auto tinst_ptr = + build_template_instantiation(template_instantiation_type); + auto &tinst = *tinst_ptr; + relationship rr{relationship_t::kDependency, tinst.full_name()}; + + LOG_DBG("Adding field dependency relationship {} {} {} " + ": {}", + rr.destination(), common::model::to_string(rr.type()), + c.full_name(), rr.label()); + + c.add_relationship(std::move(rr)); + + ctx.diagram().add_class(std::move(tinst_ptr)); + } + } +} + void translation_unit_visitor::process_template_type_parameter( const cppast::cpp_template_type_parameter &t, class_ &parent) { @@ -1016,17 +1149,18 @@ void translation_unit_visitor::process_friend( return; if (f.type()) { - auto name = cppast::to_string(f.type().value()); - - if (!ctx.config().should_include(name)) + const auto [name_with_ns, name] = + cx::util::split_ns(cppast::to_string(f.type().value())); + if (!ctx.config().should_include(name_with_ns, name)) return; LOG_DBG("Type friend declaration {}", name); + // TODO: Destination should include namespace... r.set_destination(name); } else if (f.entity()) { - std::string name{}; + std::string name; if (f.entity().value().kind() == cppast::cpp_entity_kind::class_template_t) { @@ -1056,7 +1190,7 @@ void translation_unit_visitor::process_friend( name = cx::util::full_name(ctx.get_namespace(), f.entity().value()); } - if (!ctx.config().should_include(name)) + if (!ctx.config().should_include(ctx.get_namespace(), name)) return; r.set_destination(name); @@ -1070,8 +1204,7 @@ void translation_unit_visitor::process_friend( } bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, - std::vector> &relationships, - relationship_t relationship_hint) + found_relationships_t &relationships, relationship_t relationship_hint) { bool found{false}; @@ -1086,35 +1219,92 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, relationship_t relationship_type = relationship_hint; const auto &t = cppast::remove_cv(cx::util::unreferenced(t_)); + auto name = cppast::to_string(t); + if (t.kind() == cppast::cpp_type_kind::array_t) { - auto &a = static_cast(t); - found = find_relationships( - a.value_type(), relationships, relationship_t::kAggregation); - return found; + found = find_relationships_in_array(relationships, t); } + else if (t_.kind() == cppast::cpp_type_kind::pointer_t) { + found = + find_relationships_in_pointer(t_, relationships, relationship_hint); + } + else if (t_.kind() == cppast::cpp_type_kind::reference_t) { + found = find_relationships_in_reference( + t_, relationships, relationship_hint); + } + else if (cppast::remove_cv(t_).kind() == + cppast::cpp_type_kind::user_defined_t) { + found = find_relationships_in_user_defined_type( + t_, relationships, fn, relationship_type, t); + } + else if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { + found = find_relationships_in_template_instantiation( + t, fn, relationships, relationship_type); + } + + return found; +} + +bool translation_unit_visitor::find_relationships_in_template_instantiation( + const cppast::cpp_type &t_, const std::string &fn, + found_relationships_t &relationships, relationship_t relationship_type) +{ + const auto &t = cppast::remove_cv(cx::util::unreferenced(t_)); + + const auto &tinst = + static_cast(t); auto name = cppast::to_string(t); - if (t_.kind() == cppast::cpp_type_kind::pointer_t) { - auto &p = static_cast(t_); - auto rt = relationship_t::kAssociation; - if (relationship_hint == relationship_t::kDependency) - rt = relationship_hint; - found = find_relationships(p.pointee(), relationships, rt); + bool found = false; + + if (!tinst.arguments_exposed()) { + LOG_DBG("Template instantiation {} has no exposed arguments", name); + + return found; } - else if (t_.kind() == cppast::cpp_type_kind::reference_t) { - auto &r = static_cast(t_); - auto rt = relationship_t::kAssociation; - if (r.reference_kind() == cppast::cpp_reference::cpp_ref_rvalue) { - rt = relationship_t::kAggregation; - } - if (relationship_hint == relationship_t::kDependency) - rt = relationship_hint; - found = find_relationships(r.referee(), relationships, rt); + + assert(tinst.arguments().has_value()); + assert(tinst.arguments().value().size() > 0u); + + [[maybe_unused]] const auto args_count = tinst.arguments().value().size(); + + const auto args = tinst.arguments().value(); + + const auto [ns, base_name] = cx::util::split_ns(fn); + + auto ns_and_name = ns; + ns_and_name.push_back(base_name); + + auto full_name = fmt::format("{}", fmt::join(ns_and_name, "::")); + + // Try to match common containers + // TODO: Refactor to a separate class with configurable + // container list + if (full_name.find("std::unique_ptr") == 0) { + found = find_relationships(args[0u].type().value(), relationships, + relationship_t::kAggregation); } - if (cppast::remove_cv(t_).kind() == cppast::cpp_type_kind::user_defined_t) { - LOG_DBG("User defined type: {} | {}", cppast::to_string(t_), - cppast::to_string(t_.canonical())); + else if (full_name.find("std::shared_ptr") == 0) { + found = find_relationships(args[0u].type().value(), relationships, + relationship_t::kAssociation); + } + else if (full_name.find("std::weak_ptr") == 0) { + found = find_relationships(args[0u].type().value(), relationships, + relationship_t::kAssociation); + } + else if (full_name.find("std::vector") == 0) { + if (args[0u].type().has_value()) + found = find_relationships(args[0u].type().value(), relationships, + relationship_t::kAggregation); + else + LOG_WARN( + "Failed to process template argument of std::vector at: {}", + fn); + } + else if (ctx.config().should_include(ns, name)) { + LOG_DBG("User defined template instantiation: {} | {}", + cppast::to_string(t_), cppast::to_string(t_.canonical())); if (relationship_type != relationship_t::kNone) relationships.emplace_back(cppast::to_string(t), relationship_type); @@ -1128,72 +1318,13 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, cppast::to_string(ctx.get_type_alias(fn).get())); found = find_relationships( ctx.get_type_alias(fn).get(), relationships, relationship_type); - if (found) - return found; } } - else if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { - // class_relationship r; - - auto &tinst = - static_cast(t); - - if (!tinst.arguments_exposed()) { - LOG_DBG("Template instantiation {} has no exposed arguments", name); - - return found; - } - - const auto &args = tinst.arguments().value(); - - // Try to match common containers - // TODO: Refactor to a separate class with configurable - // container list - if (name.find("std::unique_ptr") == 0) { - found = find_relationships(args[0u].type().value(), relationships, - relationship_t::kAggregation); - } - else if (name.find("std::shared_ptr") == 0) { - found = find_relationships(args[0u].type().value(), relationships, - relationship_t::kAssociation); - } - else if (name.find("std::weak_ptr") == 0) { - found = find_relationships(args[0u].type().value(), relationships, - relationship_t::kAssociation); - } - else if (name.find("std::vector") == 0) { - found = find_relationships(args[0u].type().value(), relationships, - relationship_t::kAggregation); - } - else if (ctx.config().should_include(fn)) { - LOG_DBG("User defined template instantiation: {} | {}", - cppast::to_string(t_), cppast::to_string(t_.canonical())); - - if (relationship_type != relationship_t::kNone) - relationships.emplace_back( - cppast::to_string(t), relationship_type); - else - relationships.emplace_back( - cppast::to_string(t), relationship_t::kAggregation); - - // Check if t_ has an alias in the alias index - if (ctx.has_type_alias(fn)) { - LOG_DBG("Find relationship in alias of {} | {}", fn, - cppast::to_string(ctx.get_type_alias(fn).get())); - found = find_relationships(ctx.get_type_alias(fn).get(), - relationships, relationship_type); - if (found) - return found; - } - - return found; - } - else { - for (const auto &arg : args) { - if (arg.type()) { - found = find_relationships( - arg.type().value(), relationships, relationship_type); - } + else { + for (const auto &arg : args) { + if (arg.type().has_value()) { + found = find_relationships( + arg.type().value(), relationships, relationship_type); } } } @@ -1201,95 +1332,89 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, return found; } -class_ translation_unit_visitor::build_template_instantiation( +bool translation_unit_visitor::find_relationships_in_user_defined_type( + const cppast::cpp_type &t_, found_relationships_t &relationships, + const std::string &fn, relationship_t &relationship_type, + const cppast::cpp_type &t) +{ + bool found; + LOG_DBG("Finding relationships in user defined type: {} | {}", + cppast::to_string(t_), cppast::to_string(t_.canonical())); + + if (relationship_type != relationship_t::kNone) + relationships.emplace_back(cppast::to_string(t), relationship_type); + else + relationships.emplace_back( + cppast::to_string(t), relationship_t::kAggregation); + + // Check if t_ has an alias in the alias index + if (ctx.has_type_alias(fn)) { + LOG_DBG("Find relationship in alias of {} | {}", fn, + cppast::to_string(ctx.get_type_alias(fn).get())); + found = find_relationships( + ctx.get_type_alias(fn).get(), relationships, relationship_type); + } + return found; +} + +bool translation_unit_visitor::find_relationships_in_reference( + const cppast::cpp_type &t_, found_relationships_t &relationships, + const relationship_t &relationship_hint) +{ + bool found; + auto &r = static_cast(t_); + auto rt = relationship_t::kAssociation; + if (r.reference_kind() == cppast::cpp_ref_rvalue) { + rt = relationship_t::kAggregation; + } + if (relationship_hint == relationship_t::kDependency) + rt = relationship_hint; + found = find_relationships(r.referee(), relationships, rt); + return found; +} + +bool translation_unit_visitor::find_relationships_in_pointer( + const cppast::cpp_type &t_, found_relationships_t &relationships, + const relationship_t &relationship_hint) +{ + bool found; + auto &p = static_cast(t_); + auto rt = relationship_t::kAssociation; + if (relationship_hint == relationship_t::kDependency) + rt = relationship_hint; + found = find_relationships(p.pointee(), relationships, rt); + return found; +} + +bool translation_unit_visitor::find_relationships_in_array( + found_relationships_t &relationships, const cppast::cpp_type &t) +{ + bool found; + auto &a = static_cast(t); + found = find_relationships( + a.value_type(), relationships, relationship_t::kAggregation); + return found; +} + +std::unique_ptr translation_unit_visitor::build_template_instantiation( const cppast::cpp_template_instantiation_type &t, std::optional parent) { - class_ tinst{ctx.config().using_namespace()}; + // Create class_ instance to hold the template instantiation + auto tinst_ptr = std::make_unique( + util::split(ctx.config().using_namespace()[0], "::")); + auto &tinst = *tinst_ptr; std::string full_template_name; std::deque> template_base_params{}; + tinst.set_namespace(ctx.get_namespace()); + + auto tinst_full_name = cppast::to_string(t); + if (t.primary_template().get(ctx.entity_index()).size()) { - const auto &primary_template_ref = - static_cast( - t.primary_template().get(ctx.entity_index())[0].get()) - .class_(); - - if (parent) - LOG_DBG("Template parent is {}", (*parent)->full_name()); - else - LOG_DBG("Template parent is empty"); - - full_template_name = - cx::util::full_name(ctx.get_namespace(), primary_template_ref); - - LOG_DBG("Found template instantiation: " - "type={}, canonical={}, primary_template={}, full_" - "template={}", - cppast::to_string(t), cppast::to_string(t.canonical()), - t.primary_template().name(), full_template_name); - - if (full_template_name.back() == ':') { - tinst.set_name(full_template_name + tinst.name()); - } - - std::vector> template_parameter_names{}; - if (primary_template_ref.scope_name().has_value()) { - for (const auto &tp : primary_template_ref.scope_name() - .value() - .template_parameters()) { - template_parameter_names.emplace_back( - tp.name(), tp.is_variadic()); - } - } - - // Check if the primary template has any base classes - int base_index = 0; - for (const auto &base : primary_template_ref.bases()) { - if (base.kind() == cppast::cpp_entity_kind::base_class_t) { - const auto &base_class = - static_cast(base); - - const auto base_class_name = - cppast::to_string(base_class.type()); - - LOG_DBG("Found template instantiation base: {}, {}, {}", - cppast::to_string(base.kind()), base_class_name, - base_index); - - // Check if any of the primary template arguments has a - // parameter equal to this type - auto it = std::find_if(template_parameter_names.begin(), - template_parameter_names.end(), - [&base_class_name]( - const auto &p) { return p.first == base_class_name; }); - - if (it != template_parameter_names.end()) { - // Found base class which is a template parameter - LOG_DBG("Found base class which is a template parameter " - "{}, {}, {}", - it->first, it->second, - std::distance(template_parameter_names.begin(), it)); - template_base_params.emplace_back(it->first, it->second, - std::distance(template_parameter_names.begin(), it)); - } - else { - // This is a regular base class - it is handled by - // process_template - } - } - base_index++; - } - - if (primary_template_ref.user_data()) { - tinst.set_base_template( - static_cast(primary_template_ref.user_data())); - LOG_DBG("Primary template ref set to: {}", tinst.base_template()); - } - else - LOG_WARN("No user data for base template {}", - primary_template_ref.name()); + build_template_instantiation_primary_template( + t, tinst, template_base_params, parent, full_template_name); } else { LOG_WARN("Template instantiation {} has no primary template", @@ -1301,158 +1426,50 @@ class_ translation_unit_visitor::build_template_instantiation( LOG_DBG("Building template instantiation for {}", full_template_name); // Extract namespace from base template name - std::vector ns_toks; - ns_toks = clanguml::util::split( - full_template_name.substr(0, full_template_name.find('<')), "::"); + const auto [ns, name] = cx::util::split_ns(tinst_full_name); + tinst.set_name(name); + if (ns.empty()) + tinst.set_namespace(ctx.get_namespace()); + else + tinst.set_namespace(ns); - std::string ns; - if (ns_toks.size() > 1) { - ns = fmt::format( - "{}::", fmt::join(ns_toks.begin(), ns_toks.end() - 1, "::")); + if (tinst_full_name.find('<') != std::string::npos) { + tinst.set_name(tinst_full_name.substr(0, tinst_full_name.find('<'))); } - LOG_DBG("Template namespace is {}", ns); - - tinst.set_name(ns + util::split(cppast::to_string(t), "<")[0]); - tinst.is_template_instantiation(true); if (tinst.full_name().substr(0, tinst.full_name().find('<')).find("::") == std::string::npos) { - tinst.set_name(ns + tinst.full_name()); + tinst.set_name(name); } - // Process template argumetns - int arg_index{0}; - bool variadic_params{false}; if (t.arguments_exposed()) { + auto arg_index = 0U; + // We can figure this only when we encounter variadic param in + // the list of template params, from then this variable is true + // and we can process following template parameters as belonging + // to the variadic tuple + auto has_variadic_params = false; + for (const auto &targ : t.arguments().value()) { - bool add_template_argument_as_base_class{false}; class_template ct; if (targ.type()) { - ct.set_type(cppast::to_string(targ.type().value())); - - LOG_DBG("Template argument is a type {}", ct.type()); - auto fn = cx::util::full_name( - cppast::remove_cv( - cx::util::unreferenced(targ.type().value())), - ctx.entity_index(), false); - - if (targ.type().value().kind() == - cppast::cpp_type_kind::template_instantiation_t) { - - const auto &nested_template_parameter = static_cast< - const cppast::cpp_template_instantiation_type &>( - targ.type().value()); - - std::string nnn{"empty"}; - if (parent) - nnn = (*parent)->name(); - - class_ nested_tinst = - build_template_instantiation(nested_template_parameter, - ctx.config().should_include(tinst.full_name(false)) - ? std::make_optional(&tinst) - : parent); - - relationship tinst_dependency{ - relationship_t::kDependency, nested_tinst.full_name()}; - - auto nested_tinst_full_name = nested_tinst.full_name(); - - if (ctx.config().should_include(fn)) { - ctx.diagram().add_class(std::move(nested_tinst)); - } - - if (ctx.config().should_include(tinst.full_name(false))) { - LOG_DBG( - "Creating nested template dependency to template " - "instantiation {}, {} -> {}", - fn, tinst.full_name(), - tinst_dependency.destination()); - - tinst.add_relationship(std::move(tinst_dependency)); - } - else if (parent) { - LOG_DBG("Creating nested template dependency to parent " - "template " - "instantiation {}, {} -> {}", - fn, (*parent)->full_name(), - tinst_dependency.destination()); - - (*parent)->add_relationship( - std::move(tinst_dependency)); - } - else { - LOG_DBG("No nested template dependency to template " - "instantiation: {}, {} -> {}", - fn, tinst.full_name(), - tinst_dependency.destination()); - } - } - else if (targ.type().value().kind() == - cppast::cpp_type_kind::user_defined_t) { - relationship tinst_dependency{relationship_t::kDependency, - cx::util::full_name( - cppast::remove_cv( - cx::util::unreferenced(targ.type().value())), - ctx.entity_index(), false)}; - - LOG_DBG( - "Creating nested template dependency to user defined " - "type {} -> {}", - tinst.full_name(), tinst_dependency.destination()); - - if (ctx.config().should_include(fn)) { - tinst.add_relationship(std::move(tinst_dependency)); - } - else if (parent) { - (*parent)->add_relationship( - std::move(tinst_dependency)); - } - } + build_template_instantiation_process_type_argument( + parent, tinst, targ, ct); } else if (targ.expression()) { - const auto &exp = targ.expression().value(); - if (exp.kind() == cppast::cpp_expression_kind::literal_t) - ct.set_type( - static_cast(exp) - .value()); - else if (exp.kind() == cppast::cpp_expression_kind::unexposed_t) - ct.set_type( - static_cast( - exp) - .expression() - .as_string()); - - LOG_DBG("Template argument is an expression {}", ct.type()); + build_template_instantiation_process_expression_argument( + targ, ct); } // In case any of the template arguments are base classes, add // them as parents of the current template instantiation class if (template_base_params.size() > 0) { - auto [arg_name, is_variadic, index] = - template_base_params.front(); - if (variadic_params) - add_template_argument_as_base_class = true; - else { - variadic_params = is_variadic; - if (arg_index == index) { - add_template_argument_as_base_class = true; - template_base_params.pop_front(); - } - } - - if (add_template_argument_as_base_class) { - LOG_DBG("Adding template argument '{}' as base class", - ct.type()); - - class_parent cp; - cp.set_access(access_t::kPublic); - cp.set_name(ct.type()); - - tinst.add_parent(std::move(cp)); - } + has_variadic_params = + build_template_instantiation_add_base_classes(tinst, + template_base_params, arg_index, has_variadic_params, + ct); } LOG_DBG("Adding template argument '{}'", ct.type()); @@ -1480,19 +1497,259 @@ class_ translation_unit_visitor::build_template_instantiation( relationship r{relationship_t::kInstantiation, destination}; tinst.add_relationship(std::move(r)); - return tinst; + return tinst_ptr; +} + +bool translation_unit_visitor::build_template_instantiation_add_base_classes( + class_ &tinst, + std::deque> &template_base_params, + int arg_index, bool variadic_params, const class_template &ct) const +{ + bool add_template_argument_as_base_class = false; + + auto [arg_name, is_variadic, index] = template_base_params.front(); + if (variadic_params) + add_template_argument_as_base_class = true; + else { + variadic_params = is_variadic; + if (arg_index == index) { + add_template_argument_as_base_class = true; + template_base_params.pop_front(); + } + } + + if (add_template_argument_as_base_class) { + LOG_DBG("Adding template argument as base class '{}'", ct.type()); + + class_parent cp; + cp.set_access(access_t::kPublic); + cp.set_name(ct.type()); + + tinst.add_parent(std::move(cp)); + } + + return variadic_params; +} + +void translation_unit_visitor:: + build_template_instantiation_process_expression_argument( + const cppast::cpp_template_argument &targ, class_template &ct) const +{ + const auto &exp = targ.expression().value(); + if (exp.kind() == cppast::cpp_expression_kind::literal_t) + ct.set_type( + static_cast(exp).value()); + else if (exp.kind() == cppast::cpp_expression_kind::unexposed_t) + ct.set_type(static_cast(exp) + .expression() + .as_string()); + + LOG_DBG("Template argument is an expression {}", ct.type()); +} + +void translation_unit_visitor:: + build_template_instantiation_process_type_argument( + const std::optional &parent, + class_ &tinst, const cppast::cpp_template_argument &targ, + class_template &ct) +{ + ct.set_type(cppast::to_string(targ.type().value())); + + LOG_DBG("Template argument is a type {}", ct.type()); + auto fn = cx::util::full_name( + cppast::remove_cv(cx::util::unreferenced(targ.type().value())), + ctx.entity_index(), false); + + auto [fn_ns, fn_name] = cx::util::split_ns(fn); + + if (targ.type().value().kind() == + cppast::cpp_type_kind::template_instantiation_t) { + + const auto &nested_template_parameter = + static_cast( + targ.type().value()); + + std::string nnn{"empty"}; + if (parent) + nnn = (*parent)->name(); + + auto [tinst_ns, tinst_name] = + cx::util::split_ns(tinst.full_name(false)); + + auto nested_tinst = + build_template_instantiation(nested_template_parameter, + ctx.config().should_include(tinst_ns, tinst_name) + ? std::make_optional(&tinst) + : parent); + + relationship tinst_dependency{ + relationship_t::kDependency, nested_tinst->full_name()}; + + auto nested_tinst_full_name = nested_tinst->full_name(); + + if (ctx.config().should_include(fn_ns, fn_name)) { + ctx.diagram().add_class(std::move(nested_tinst)); + } + + if (ctx.config().should_include(tinst_ns, tinst_name) + // TODO: check why this breaks t00033: + // && ctx.config().should_include( + // cx::util::split_ns(tinst_dependency.destination())) + ) { + LOG_DBG("Creating nested template dependency to template " + "instantiation {}, {} -> {}", + fn, tinst.full_name(), tinst_dependency.destination()); + + tinst.add_relationship(std::move(tinst_dependency)); + } + else if (parent) { + LOG_DBG("Creating nested template dependency to parent " + "template " + "instantiation {}, {} -> {}", + fn, (*parent)->full_name(), tinst_dependency.destination()); + + (*parent)->add_relationship(std::move(tinst_dependency)); + } + else { + LOG_DBG("No nested template dependency to template " + "instantiation: {}, {} -> {}", + fn, tinst.full_name(), tinst_dependency.destination()); + } + } + else if (targ.type().value().kind() == + cppast::cpp_type_kind::user_defined_t) { + relationship tinst_dependency{relationship_t::kDependency, + cx::util::full_name( + cppast::remove_cv(cx::util::unreferenced(targ.type().value())), + ctx.entity_index(), false)}; + + LOG_DBG("Creating nested template dependency to user defined " + "type {} -> {}", + tinst.full_name(), tinst_dependency.destination()); + + if (ctx.config().should_include(fn_ns, fn_name)) { + tinst.add_relationship(std::move(tinst_dependency)); + } + else if (parent) { + (*parent)->add_relationship(std::move(tinst_dependency)); + } + } +} + +void translation_unit_visitor::build_template_instantiation_primary_template( + const cppast::cpp_template_instantiation_type &t, class_ &tinst, + std::deque> &template_base_params, + std::optional &parent, + std::string &full_template_name) const +{ + const auto &primary_template_ref = + static_cast( + t.primary_template().get(ctx.entity_index())[0].get()) + .class_(); + + if (parent) + LOG_DBG("Template parent is {}", (*parent)->full_name()); + else + LOG_DBG("Template parent is empty"); + + full_template_name = + cx::util::full_name(ctx.get_namespace(), primary_template_ref); + + LOG_DBG("Found template instantiation: " + "type={}, canonical={}, primary_template={}, full_" + "template={}", + cppast::to_string(t), cppast::to_string(t.canonical()), + t.primary_template().name(), full_template_name); + + if (full_template_name.back() == ':') { + tinst.set_name(full_template_name + tinst.name()); + } + + std::vector> template_parameter_names{}; + if (primary_template_ref.scope_name().has_value()) { + for (const auto &tp : + primary_template_ref.scope_name().value().template_parameters()) { + template_parameter_names.emplace_back(tp.name(), tp.is_variadic()); + } + } + + // Check if the primary template has any base classes + int base_index = 0; + for (const auto &base : primary_template_ref.bases()) { + if (base.kind() == cppast::cpp_entity_kind::base_class_t) { + const auto &base_class = + static_cast(base); + + const auto base_class_name = cppast::to_string(base_class.type()); + + LOG_DBG("Found template instantiation base: {}, {}, {}", + cppast::to_string(base.kind()), base_class_name, base_index); + + // Check if any of the primary template arguments has a + // parameter equal to this type + auto it = std::find_if(template_parameter_names.begin(), + template_parameter_names.end(), + [&base_class_name]( + const auto &p) { return p.first == base_class_name; }); + + if (it != template_parameter_names.end()) { + // Found base class which is a template parameter + LOG_DBG("Found base class which is a template parameter " + "{}, {}, {}", + it->first, it->second, + std::distance(template_parameter_names.begin(), it)); + + template_base_params.emplace_back(it->first, it->second, + std::distance(template_parameter_names.begin(), it)); + } + else { + // This is a regular base class - it is handled by + // process_template + } + } + base_index++; + } + + if (primary_template_ref.user_data()) { + tinst.set_base_template( + static_cast(primary_template_ref.user_data())); + LOG_DBG("Primary template ref set to: {}", tinst.base_template()); + } + else + LOG_WARN( + "No user data for base template {}", primary_template_ref.name()); } const cppast::cpp_type &translation_unit_visitor::resolve_alias( const cppast::cpp_type &type) { const auto &raw_type = cppast::remove_cv(cx::util::unreferenced(type)); - const auto type_full_name = + auto type_full_name = cx::util::full_name(raw_type, ctx.entity_index(), false); - if (ctx.has_type_alias(type_full_name)) { + + if (util::contains(type_full_name, "<")) + type_full_name = util::split(type_full_name, "<")[0]; + + if (ctx.has_type_alias_template(type_full_name)) { + return ctx.get_type_alias(type_full_name).get(); + } + else if (ctx.has_type_alias(type_full_name)) { return ctx.get_type_alias_final(raw_type).get(); } return type; } + +const cppast::cpp_type &translation_unit_visitor::resolve_alias_template( + const cppast::cpp_type &type) +{ + const auto &raw_type = cppast::remove_cv(cx::util::unreferenced(type)); + const auto type_full_name = + cx::util::full_name(raw_type, ctx.entity_index(), false); + if (ctx.has_type_alias_template(type_full_name)) { + return ctx.get_type_alias_template(type_full_name).get(); + } + + return type; +} } diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index 508866f8..2cbe8d71 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -34,6 +34,11 @@ #include #include +#include +#include +#include +#include +#include #include #include #include @@ -41,6 +46,9 @@ namespace clanguml::class_diagram::visitor { +using found_relationships_t = + std::vector>; + class translation_unit_visitor { public: translation_unit_visitor(cppast::cpp_entity_index &idx, @@ -99,8 +107,7 @@ public: const std::set &template_parameter_names = {}); bool find_relationships(const cppast::cpp_type &t, - std::vector> &relationships, + found_relationships_t &relationships, clanguml::common::model::relationship_t relationship_hint = clanguml::common::model::relationship_t::kNone); @@ -119,8 +126,42 @@ public: void process_friend(const cppast::cpp_friend &t, clanguml::class_diagram::model::class_ &parent); + void process_namespace(const cppast::cpp_entity &e, + const cppast::cpp_namespace &ns_declaration); + + void process_type_alias(const cppast::cpp_type_alias &ta); + + void process_type_alias_template(const cppast::cpp_alias_template &at); + + void process_class_children(const cppast::cpp_class &cls, model::class_ &c); + + void process_class_bases( + const cppast::cpp_class &cls, model::class_ &c) const; + + void process_unexposed_template_specialization_parameters( + const type_safe::optional_ref + &tspec, + model::class_ &c) const; + + void process_exposed_template_specialization_parameters( + const type_safe::optional_ref + &tspec, + model::class_ &c); + + void process_scope_template_parameters( + model::class_ &c, const cppast::cpp_scope_name &scope); + + bool process_template_parameters(const cppast::cpp_class &cls, + model::class_ &c, + const type_safe::optional_ref + &tspec); + + void process_class_containment( + const cppast::cpp_class &cls, model::class_ &c) const; + private: - clanguml::class_diagram::model::class_ build_template_instantiation( + std::unique_ptr + build_template_instantiation( const cppast::cpp_template_instantiation_type &t, std::optional parent = {}); @@ -130,6 +171,54 @@ private: */ const cppast::cpp_type &resolve_alias(const cppast::cpp_type &t); + const cppast::cpp_type &resolve_alias_template( + const cppast::cpp_type &type); + + bool find_relationships_in_array( + found_relationships_t &relationships, const cppast::cpp_type &t); + + bool find_relationships_in_pointer(const cppast::cpp_type &t_, + found_relationships_t &relationships, + const common::model::relationship_t &relationship_hint); + + bool find_relationships_in_reference(const cppast::cpp_type &t_, + found_relationships_t &relationships, + const common::model::relationship_t &relationship_hint); + + bool find_relationships_in_user_defined_type(const cppast::cpp_type &t_, + found_relationships_t &relationships, const std::string &fn, + common::model::relationship_t &relationship_type, + const cppast::cpp_type &t); + + bool find_relationships_in_template_instantiation(const cppast::cpp_type &t, + const std::string &fn, found_relationships_t &relationships, + common::model::relationship_t relationship_type); + + void build_template_instantiation_primary_template( + const cppast::cpp_template_instantiation_type &t, + clanguml::class_diagram::model::class_ &tinst, + std::deque> &template_base_params, + std::optional &parent, + std::string &full_template_name) const; + + void build_template_instantiation_process_type_argument( + const std::optional &parent, + model::class_ &tinst, const cppast::cpp_template_argument &targ, + model::class_template &ct); + + void build_template_instantiation_process_expression_argument( + const cppast::cpp_template_argument &targ, + model::class_template &ct) const; + + bool build_template_instantiation_add_base_classes(model::class_ &tinst, + std::deque> &template_base_params, + int arg_index, bool variadic_params, + const model::class_template &ct) const; + + void process_function_parameter_find_relationships_in_template( + model::class_ &c, const std::set &template_parameter_names, + const cppast::cpp_type &t); + // ctx allows to track current visitor context, e.g. current namespace translation_unit_context ctx; }; diff --git a/src/common/model/element.cc b/src/common/model/element.cc index ec1b2549..b79645d3 100644 --- a/src/common/model/element.cc +++ b/src/common/model/element.cc @@ -20,6 +20,8 @@ #include "util/util.h" +#include + namespace clanguml::common::model { std::atomic_uint64_t element::m_nextId = 1; @@ -28,6 +30,8 @@ element::element(const std::vector &using_namespaces) : using_namespaces_{using_namespaces} , m_id{m_nextId++} { + for (const auto &n : using_namespaces_) + assert(!util::contains(n, "::")); } std::string element::alias() const { return fmt::format("C_{:010}", m_id); } @@ -41,6 +45,13 @@ void element::add_relationship(relationship &&cr) return; } + if ((cr.type() == relationship_t::kInstantiation) && + (cr.destination() == full_name(true))) { + LOG_WARN("Skipping self instantiation relationship for {}", + cr.destination()); + return; + } + LOG_DBG("Adding relationship: '{}' - {} - '{}'", cr.destination(), to_string(cr.type()), full_name(true)); @@ -50,6 +61,9 @@ void element::add_relationship(relationship &&cr) void element::set_using_namespaces(const std::vector &un) { + for (const auto &n : un) + assert(!util::contains(n, "::")); + using_namespaces_ = un; } @@ -66,4 +80,19 @@ const std::vector &element::relationships() const } void element::append(const element &e) { decorated_element::append(e); } + +bool operator==(const element &l, const element &r) +{ + return l.full_name(false) == r.full_name(false); +} + +std::ostream &operator<<(std::ostream &out, const element &rhs) +{ + out << "(" << rhs.name() << ", ns=[" + << util::join(rhs.get_namespace(), "::") << "], full_name=[" + << rhs.full_name(true) << "])"; + + return out; +} + } diff --git a/src/common/model/element.h b/src/common/model/element.h index 6704a124..4c8ea662 100644 --- a/src/common/model/element.h +++ b/src/common/model/element.h @@ -19,8 +19,10 @@ #include "decorated_element.h" #include "relationship.h" +#include "util/util.h" #include +#include #include #include @@ -30,16 +32,32 @@ class element : public decorated_element { public: element(const std::vector &using_namespaces); + virtual ~element() = default; + std::string alias() const; void set_name(const std::string &name) { name_ = name; } std::string name() const { return name_; } + std::string name_and_ns() const + { + auto ns = namespace_; + ns.push_back(name()); + return util::join(ns, "::"); + } + void set_namespace(const std::vector &ns) { namespace_ = ns; } std::vector get_namespace() const { return namespace_; } + std::vector get_relative_namespace() const + { + auto relative_ns = namespace_; + util::remove_prefix(relative_ns, using_namespaces_); + return relative_ns; + } + virtual std::string full_name(bool relative) const { return name(); } void set_using_namespaces(const std::vector &un); @@ -54,6 +72,10 @@ public: void append(const element &e); + friend bool operator==(const element &l, const element &r); + + friend std::ostream &operator<<(std::ostream &out, const element &rhs); + protected: const uint64_t m_id{0}; diff --git a/src/common/model/nested_trait.h b/src/common/model/nested_trait.h index 37644d12..1cc7779a 100644 --- a/src/common/model/nested_trait.h +++ b/src/common/model/nested_trait.h @@ -21,6 +21,7 @@ #include +#include #include #include @@ -37,7 +38,7 @@ public: virtual ~nested_trait() = default; - void add_element(std::unique_ptr p) + template void add_element(std::unique_ptr p) { auto it = std::find_if(elements_.begin(), elements_.end(), [&p](const auto &e) { return *e == *p; }); @@ -50,11 +51,12 @@ public: } } - void add_element(std::vector path, std::unique_ptr p) + template + void add_element(std::vector path, std::unique_ptr p) { assert(p); - LOG_DBG("Adding nested element {} at path '{}'", p->name(), + LOG_DBG("Adding nested element {} at path {}", p->name(), fmt::join(path, "::")); if (path.empty()) { @@ -64,42 +66,57 @@ public: auto parent = get_element(path); - if (parent) - parent.value().add_element(std::move(p)); - else + if (parent && dynamic_cast *>(&parent.value())) + dynamic_cast &>(parent.value()) + .template add_element(std::move(p)); + else { spdlog::error( "No parent element found at: {}", fmt::join(path, "::")); + throw std::runtime_error("No parent element found"); + } } - type_safe::optional_ref get_element(std::vector path) const + template + auto get_element(std::vector path) const { LOG_DBG("Getting nested element at path: {}", fmt::join(path, "::")); if (path.empty() || !has_element(path.at(0))) { LOG_WARN("Nested element {} not found in element", fmt::join(path, "::")); - return {}; + return type_safe::optional_ref{}; } - auto p = get_element(path.at(0)); if (path.size() == 1) - return p; + return get_element(path.at(0)); - return p.value().get_element( - std::vector(path.begin() + 1, path.end())); + auto p = get_element(path.at(0)); + + if (!p) + return type_safe::optional_ref{}; + + if (dynamic_cast *>(&p.value())) + return dynamic_cast &>(p.value()).get_element( + std::vector(path.begin() + 1, path.end())); + + return type_safe::optional_ref{}; } - type_safe::optional_ref get_element(const std::string &name) const + template auto get_element(const std::string &name) const { auto it = std::find_if(elements_.cbegin(), elements_.cend(), [&](const auto &p) { return name == p->name(); }); if (it == elements_.end()) - return {}; + return type_safe::optional_ref{type_safe::nullopt}; assert(it->get() != nullptr); - return type_safe::ref(*(it->get())); + if (dynamic_cast(it->get())) + return type_safe::optional_ref{ + type_safe::ref(dynamic_cast(*it->get()))}; + + return type_safe::optional_ref{type_safe::nullopt}; } bool has_element(const std::string &name) const @@ -118,6 +135,24 @@ public: auto begin() const { return elements_.begin(); } auto end() const { return elements_.end(); } + void print_tree(const int level) + { + const auto &d = *this; + + if (level == 0) { + std::cout << "--- Printing tree:\n"; + } + for (const auto &e : d) { + if (dynamic_cast *>(e.get())) { + std::cout << std::string(level, ' ') << "[" << *e << "]\n"; + dynamic_cast *>(e.get())->print_tree(level + 1); + } + else { + std::cout << std::string(level, ' ') << "- " << *e << "]\n"; + } + } + } + private: std::vector> elements_; }; diff --git a/src/package_diagram/model/package.cc b/src/common/model/package.cc similarity index 87% rename from src/package_diagram/model/package.cc rename to src/common/model/package.cc index 719689ae..9d12601d 100644 --- a/src/package_diagram/model/package.cc +++ b/src/common/model/package.cc @@ -1,5 +1,5 @@ /** - * src/package_diagram/model/class.h + * src/common/model/class.h * * Copyright (c) 2021-2022 Bartek Kryza * @@ -22,7 +22,7 @@ #include -namespace clanguml::package_diagram::model { +namespace clanguml::common::model { package::package(const std::vector &using_namespaces) : element{using_namespaces} { @@ -43,11 +43,6 @@ std::string package::full_name(bool relative) const return fmt::format("{}", fmt::join(fn, "::")); } -bool operator==(const package &l, const package &r) -{ - return l.full_name(false) == r.full_name(false); -} - bool package::is_deprecated() const { return is_deprecated_; } void package::set_deprecated(bool deprecated) { is_deprecated_ = deprecated; } diff --git a/src/package_diagram/model/package.h b/src/common/model/package.h similarity index 83% rename from src/package_diagram/model/package.h rename to src/common/model/package.h index dc96518d..e11fafdb 100644 --- a/src/package_diagram/model/package.h +++ b/src/common/model/package.h @@ -29,11 +29,11 @@ #include #include -namespace clanguml::package_diagram::model { +namespace clanguml::common::model { -class package : public common::model::element, - public common::model::stylable_element, - public common::model::nested_trait { +class package : public element, + public stylable_element, + public nested_trait { public: package(const std::vector &using_namespaces); @@ -44,8 +44,6 @@ public: std::string full_name(bool relative) const override; - friend bool operator==(const package &l, const package &r); - bool is_deprecated() const; void set_deprecated(bool deprecated); diff --git a/src/config/config.cc b/src/config/config.cc index 5489529d..39c67509 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -129,6 +129,20 @@ bool diagram::should_include_relationship(const std::string &rel) return false; } +bool diagram::should_include( + const std::pair, std::string> &name) const +{ + return should_include(std::get<0>(name), std::get<1>(name)); +} + +bool diagram::should_include( + const std::vector &ns, const std::string &name) const +{ + auto ns_and_name = ns; + ns_and_name.push_back(name); + return should_include(util::join(ns_and_name, "::")); +} + bool diagram::should_include(const std::string &name_) const { auto name = clanguml::util::unqualify(name_); @@ -150,7 +164,7 @@ bool diagram::should_include(const std::string &name_) const return true; } - spdlog::debug("Skipping from diagram: {}", name); + LOG_DBG("Skipping from diagram: {}", name); return false; } @@ -396,6 +410,7 @@ template <> struct convert { get_option(node, rhs.layout); get_option(node, rhs.include_relations_also_as_members); get_option(node, rhs.generate_method_arguments); + get_option(node, rhs.generate_packages); return true; } @@ -476,6 +491,7 @@ template <> struct convert { get_option(node, rhs.include_relations_also_as_members); get_option(node, rhs.puml); get_option(node, rhs.generate_method_arguments); + get_option(node, rhs.generate_packages); auto diagrams = node["diagrams"]; diff --git a/src/config/config.h b/src/config/config.h index 9ae7b614..d220f7c7 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -87,6 +87,7 @@ struct inheritable_diagram_options { option puml{"plantuml", option_inherit_mode::append}; option generate_method_arguments{ "generate_method_arguments", method_arguments::full}; + option generate_packages{"generate_packages", false}; void inherit(const inheritable_diagram_options &parent); }; @@ -102,9 +103,16 @@ struct diagram : public inheritable_diagram_options { bool should_include_relationship(const std::string &rel); - bool should_include(const std::string &name_) const; + bool should_include( + const std::pair, std::string> &name) const; + + bool should_include( + const std::vector &ns, const std::string &name) const; bool should_include(const common::model::scope_t scope) const; + bool should_include(const std::string &name_) const; + +private: }; struct source_location { diff --git a/src/config/option.h b/src/config/option.h index 3ba61f4c..0fe1d17b 100644 --- a/src/config/option.h +++ b/src/config/option.h @@ -56,6 +56,8 @@ template struct option { } } + void operator()(const T &v) { set(v); } + T &operator()() { return value; } const T &operator()() const { return value; } diff --git a/src/cx/util.cc b/src/cx/util.cc index 721acc09..fb4f0fdf 100644 --- a/src/cx/util.cc +++ b/src/cx/util.cc @@ -126,6 +126,16 @@ bool is_inside_class(const cppast::cpp_entity &e) return false; } +std::pair, std::string> split_ns( + const std::string &full_name) +{ + auto name_before_template = ::clanguml::util::split(full_name, "<")[0]; + auto ns = ::clanguml::util::split(name_before_template, "::"); + auto name = ns.back(); + ns.pop_back(); + return {ns, name}; +} + std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx) { if (t.kind() == cppast::cpp_type_kind::user_defined_t && diff --git a/src/cx/util.h b/src/cx/util.h index 9a0392e6..94fe8265 100644 --- a/src/cx/util.h +++ b/src/cx/util.h @@ -58,6 +58,9 @@ type_safe::optional_ref entity_ns( std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx); +std::pair, std::string> split_ns( + const std::string &full_name); + bool is_inside_class(const cppast::cpp_entity &e); } // namespace util } // namespace cx diff --git a/src/package_diagram/generators/plantuml/package_diagram_generator.cc b/src/package_diagram/generators/plantuml/package_diagram_generator.cc index befc0bc4..d4ff8b11 100644 --- a/src/package_diagram/generators/plantuml/package_diagram_generator.cc +++ b/src/package_diagram/generators/plantuml/package_diagram_generator.cc @@ -54,8 +54,9 @@ void generator::generate_relationships( } // Process it's subpackages relationships - for (const std::unique_ptr &subpackage : p) { - generate_relationships(*subpackage, ostr); + for (const auto &subpackage : p) { + generate_relationships( + dynamic_cast(*subpackage), ostr); } } @@ -77,7 +78,7 @@ void generator::generate(const package &p, std::ostream &ostr) const ostr << " {" << '\n'; for (const auto &subpackage : p) { - generate(*subpackage, ostr); + generate(dynamic_cast(*subpackage), ostr); } ostr << "}" << '\n'; @@ -93,14 +94,14 @@ void generator::generate(std::ostream &ostr) const if (m_config.should_include_entities("packages")) { for (const auto &p : m_model) { - generate(*p, ostr); + generate(dynamic_cast(*p), ostr); ostr << '\n'; } } // Process package relationships for (const auto &p : m_model) { - generate_relationships(*p, ostr); + generate_relationships(dynamic_cast(*p), ostr); ostr << '\n'; } diff --git a/src/package_diagram/generators/plantuml/package_diagram_generator.h b/src/package_diagram/generators/plantuml/package_diagram_generator.h index a29e0743..075bcef0 100644 --- a/src/package_diagram/generators/plantuml/package_diagram_generator.h +++ b/src/package_diagram/generators/plantuml/package_diagram_generator.h @@ -18,11 +18,11 @@ #pragma once #include "common/generators/plantuml/generator.h" +#include "common/model/package.h" #include "common/model/relationship.h" #include "config/config.h" #include "cx/compilation_database.h" #include "package_diagram/model/diagram.h" -#include "package_diagram/model/package.h" #include "package_diagram/visitor/translation_unit_visitor.h" #include "util/util.h" @@ -46,9 +46,9 @@ template using common_generator = clanguml::common::generators::plantuml::generator; +using clanguml::common::model::package; using clanguml::common::model::relationship_t; using clanguml::common::model::scope_t; -using clanguml::package_diagram::model::package; using namespace clanguml::util; class generator : public common_generator { diff --git a/src/package_diagram/model/diagram.cc b/src/package_diagram/model/diagram.cc index 1d7b1b92..04748b43 100644 --- a/src/package_diagram/model/diagram.cc +++ b/src/package_diagram/model/diagram.cc @@ -33,7 +33,7 @@ std::string diagram::to_alias(const std::string &full_name) const throw error::uml_alias_missing( fmt::format("Missing alias for '{}'", full_name)); - auto package = get_element(fn); + auto package = get_element(fn); if (!package) throw error::uml_alias_missing( diff --git a/src/package_diagram/model/diagram.h b/src/package_diagram/model/diagram.h index fd6778ce..ba092552 100644 --- a/src/package_diagram/model/diagram.h +++ b/src/package_diagram/model/diagram.h @@ -18,7 +18,7 @@ #pragma once #include "common/model/diagram.h" -#include "package.h" +#include "common/model/package.h" #include @@ -28,7 +28,8 @@ namespace clanguml::package_diagram::model { class diagram : public clanguml::common::model::diagram, - public clanguml::common::model::nested_trait { + public clanguml::common::model::nested_trait< + clanguml::common::model::element> { public: diagram() = default; diff --git a/src/package_diagram/visitor/translation_unit_context.cc b/src/package_diagram/visitor/translation_unit_context.cc index c95355c2..6cbd7de6 100644 --- a/src/package_diagram/visitor/translation_unit_context.cc +++ b/src/package_diagram/visitor/translation_unit_context.cc @@ -180,12 +180,12 @@ clanguml::package_diagram::model::diagram &translation_unit_context::diagram() } void translation_unit_context::set_current_package( - type_safe::optional_ref p) + type_safe::optional_ref p) { current_package_ = p; } -type_safe::optional_ref +type_safe::optional_ref translation_unit_context::get_current_package() const { return current_package_; diff --git a/src/package_diagram/visitor/translation_unit_context.h b/src/package_diagram/visitor/translation_unit_context.h index 10c71a4f..65522418 100644 --- a/src/package_diagram/visitor/translation_unit_context.h +++ b/src/package_diagram/visitor/translation_unit_context.h @@ -17,6 +17,7 @@ */ #pragma once +#include "common/model/package.h" #include "config/config.h" #include "package_diagram/model/diagram.h" @@ -75,9 +76,9 @@ public: clanguml::package_diagram::model::diagram &diagram(); - void set_current_package(type_safe::optional_ref p); + void set_current_package(type_safe::optional_ref p); - type_safe::optional_ref get_current_package() const; + type_safe::optional_ref get_current_package() const; private: // Current visitor namespace @@ -104,7 +105,7 @@ private: std::map> alias_template_index_; - type_safe::optional_ref current_package_; + type_safe::optional_ref current_package_; }; } diff --git a/src/package_diagram/visitor/translation_unit_visitor.cc b/src/package_diagram/visitor/translation_unit_visitor.cc index 7906df1b..a0867a33 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.cc +++ b/src/package_diagram/visitor/translation_unit_visitor.cc @@ -37,11 +37,11 @@ namespace clanguml::package_diagram::visitor { using clanguml::class_diagram::model::type_alias; using clanguml::common::model::access_t; +using clanguml::common::model::package; using clanguml::common::model::relationship; using clanguml::common::model::relationship_t; using clanguml::common::model::scope_t; using clanguml::package_diagram::model::diagram; -using clanguml::package_diagram::model::package; namespace detail { scope_t cpp_access_specifier_to_scope( @@ -98,10 +98,8 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) ctx.config().using_namespace()[0], "::"); if (!util::starts_with(usn, package_path)) { - auto p = std::make_unique( - ctx.config().using_namespace()); + auto p = std::make_unique(usn); util::remove_prefix(package_path, usn); - util::remove_prefix(package_parent, usn); p->set_name(e.name()); p->set_namespace(package_parent); @@ -122,10 +120,11 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) } if (!p->skip()) { - ctx.diagram().add_element( - package_parent, std::move(p)); + auto rns = p->get_relative_namespace(); + ctx.diagram().add_element(rns, std::move(p)); ctx.set_current_package( - ctx.diagram().get_element(package_path)); + ctx.diagram().get_element( + package_path)); } } @@ -352,6 +351,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, const auto fn = cx::util::full_name( resolve_alias(cppast::remove_cv(t_)), ctx.entity_index(), false); auto t_ns = util::split(fn, "::"); + auto t_name = t_ns.back(); t_ns.pop_back(); const auto &t_raw = resolve_alias(cppast::remove_cv(t_)); @@ -464,7 +464,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, found = find_relationships(args[0u].type().value(), relationships, relationship_t::kDependency); } - else if (ctx.config().should_include(fn)) { + else if (ctx.config().should_include(t_ns, t_name)) { LOG_DBG("User defined template instantiation: {} | {}", cppast::to_string(t_), cppast::to_string(t_.canonical())); diff --git a/src/package_diagram/visitor/translation_unit_visitor.h b/src/package_diagram/visitor/translation_unit_visitor.h index ca237e9c..f290072c 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.h +++ b/src/package_diagram/visitor/translation_unit_visitor.h @@ -35,10 +35,10 @@ #include #include +#include #include #include #include -#include #include namespace clanguml::package_diagram::visitor { diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index 65244d07..29614816 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -80,7 +80,8 @@ void translation_unit_visitor::process_activities(const cppast::cpp_function &e) .value(); m.from = cx::util::ns(caller) + "::" + caller.name(); - if (!ctx.config().should_include(m.from)) + if (!ctx.config().should_include( + util::split(cx::util::ns(caller), "::"), caller.name())) continue; if (caller.kind() == cpp_entity_kind::function_t) @@ -96,7 +97,8 @@ void translation_unit_visitor::process_activities(const cppast::cpp_function &e) if (callee.kind() == cpp_entity_kind::function_t) m.to += "()"; - if (!ctx.config().should_include(m.to)) + if (!ctx.config().should_include( + util::split(cx::util::ns(callee), "::"), callee.name())) continue; m.to_usr = type_safe::get(function_call.get_callee_method_id()); diff --git a/src/util/util.cc b/src/util/util.cc index 0a43824b..92e79db3 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -44,22 +44,21 @@ std::vector split(std::string str, std::string delimiter) { std::vector result; - while (str.size()) { - int index = str.find(delimiter); - if (index != std::string::npos) { - result.push_back(str.substr(0, index)); - str = str.substr(index + delimiter.size()); - if (str.size() == 0) - result.push_back(str); - } - else { - result.push_back(str); - str = ""; - } - } - - if (result.empty()) + if (!contains(str, delimiter)) result.push_back(str); + else + while (str.size()) { + int index = str.find(delimiter); + if (index != std::string::npos) { + result.push_back(str.substr(0, index)); + str = str.substr(index + delimiter.size()); + } + else { + if (!str.empty()) + result.push_back(str); + str = ""; + } + } return result; } diff --git a/src/util/util.h b/src/util/util.h index 88332b68..d929f41a 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace clanguml { @@ -190,6 +191,9 @@ bool contains(const T &container, const E &element) [&element](const auto &e) { return *e == *element; }) != container.end(); } + else if constexpr (std::is_same_v, std::string>) { + return container.find(element) != std::string::npos; + } else { return std::find(container.begin(), container.end(), element) != container.end(); diff --git a/tests/t00002/test_case.h b/tests/t00002/test_case.h index c4f4c879..4c46f175 100644 --- a/tests/t00002/test_case.h +++ b/tests/t00002/test_case.h @@ -30,8 +30,8 @@ TEST_CASE("t00002", "[test-case][class]") REQUIRE(diagram->exclude().namespaces.size() == 0); - REQUIRE(diagram->should_include("clanguml::t00002::A")); - REQUIRE(!diagram->should_include("std::vector")); + REQUIRE(diagram->should_include({"clanguml", "t00002"}, "A")); + REQUIRE(!diagram->should_include({"std"}, "vector")); auto model = generate_class_diagram(db, diagram); diff --git a/tests/t00014/test_case.h b/tests/t00014/test_case.h index 0b81e88d..ec4e5cd7 100644 --- a/tests/t00014/test_case.h +++ b/tests/t00014/test_case.h @@ -39,12 +39,23 @@ TEST_CASE("t00014", "[test-case][class]") REQUIRE_THAT(puml, IsClassTemplate("A", "T,std::string")); REQUIRE_THAT(puml, IsClassTemplate("A", "bool,std::string")); REQUIRE_THAT(puml, IsClassTemplate("AString", "float")); + REQUIRE_THAT(puml, IsClassTemplate("AString", "int")); + REQUIRE_THAT(puml, IsClassTemplate("AString", "std::string")); REQUIRE_THAT( puml, !IsClassTemplate("std::std::function", "void(T...,int),int)")); REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); REQUIRE_THAT( puml, IsInstantiation(_A("A"), _A("AString"))); + REQUIRE_THAT( + puml, IsInstantiation(_A("A"), _A("AString"))); + REQUIRE_THAT( + puml, !IsInstantiation(_A("AString"), _A("AString"))); + REQUIRE_THAT(puml, + IsInstantiation(_A("A"), _A("AString"))); + REQUIRE_THAT(puml, + !IsInstantiation( + _A("AString"), _A("AString"))); REQUIRE_THAT( puml, IsAggregation(_A("R"), _A("A"), "-boolstring")); REQUIRE_THAT( diff --git a/tests/t00036/.clang-uml b/tests/t00036/.clang-uml new file mode 100644 index 00000000..7d6d5449 --- /dev/null +++ b/tests/t00036/.clang-uml @@ -0,0 +1,13 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00036_class: + type: class + generate_packages: true + glob: + - ../../tests/t00036/t00036.cc + using_namespace: + - clanguml::t00036 + include: + namespaces: + - clanguml::t00036 \ No newline at end of file diff --git a/tests/t00036/t00036.cc b/tests/t00036/t00036.cc new file mode 100644 index 00000000..9fc4f627 --- /dev/null +++ b/tests/t00036/t00036.cc @@ -0,0 +1,33 @@ +namespace clanguml { +namespace t00036 { + +namespace ns1 { + +enum class E { blue, yellow }; + +namespace ns11 { + +template struct A { + T a; +}; + +namespace ns111 { + +struct B { + A a_int; +}; + +} +} +} + +namespace ns2 { +namespace ns22 { + +struct C; + +} +} + +} // namespace t00036 +} // namespace clanguml diff --git a/tests/t00036/test_case.h b/tests/t00036/test_case.h new file mode 100644 index 00000000..ee8a3ae4 --- /dev/null +++ b/tests/t00036/test_case.h @@ -0,0 +1,50 @@ +/** + * tests/t00036/test_case.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t00036", "[test-case][class]") +{ + auto [config, db] = load_config("t00036"); + + auto diagram = config.diagrams["t00036_class"]; + + REQUIRE(diagram->name == "t00036_class"); + REQUIRE(diagram->generate_packages() == true); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name() == "t00036_class"); + + auto puml = generate_class_puml(diagram, model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, IsClassTemplate("A", "T")); + REQUIRE_THAT(puml, IsClassTemplate("A", "int")); + REQUIRE_THAT(puml, IsEnum(_A("E"))); + REQUIRE_THAT(puml, IsClass(_A("B"))); + REQUIRE_THAT(puml, IsClass(_A("C"))); + REQUIRE_THAT(puml, IsPackage("ns111")); + REQUIRE_THAT(puml, IsPackage("ns22")); + + REQUIRE_THAT(puml, IsAggregation(_A("B"), _A("A"), "+a_int")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 3a9435aa..30e3727c 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -178,6 +178,7 @@ using namespace clanguml::test::matchers; #include "t00033/test_case.h" #include "t00034/test_case.h" #include "t00035/test_case.h" +#include "t00036/test_case.h" // // Sequence diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 524d2570..6a50c833 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -102,6 +102,9 @@ test_cases: - name: t00035 title: PlantUML class diagram layout hints test case description: + - name: t00036 + title: Class diagram with namespaces generated as packages + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram test case diff --git a/tests/test_config.cc b/tests/test_config.cc index 95091f8e..56cb4e88 100644 --- a/tests/test_config.cc +++ b/tests/test_config.cc @@ -33,6 +33,7 @@ TEST_CASE("Test config simple", "[unit-test]") CHECK(clanguml::util::contains(diagram.using_namespace(), "clanguml")); CHECK(diagram.generate_method_arguments() == clanguml::config::method_arguments::full); + CHECK(diagram.generate_packages() == true); } TEST_CASE("Test config inherited", "[unit-test]") @@ -46,6 +47,7 @@ TEST_CASE("Test config inherited", "[unit-test]") CHECK(def.glob()[0] == "src/**/*.cc"); CHECK(def.glob()[1] == "src/**/*.h"); CHECK(clanguml::util::contains(def.using_namespace(), "clanguml")); + CHECK(def.generate_packages() == false); auto &cus = *cfg.diagrams["class_custom"]; CHECK(cus.type() == clanguml::config::diagram_type::class_diagram); @@ -53,6 +55,7 @@ TEST_CASE("Test config inherited", "[unit-test]") CHECK(cus.glob()[0] == "src/main.cc"); CHECK(clanguml::util::contains(cus.using_namespace(), "clanguml::ns1")); CHECK(cus.include_relations_also_as_members()); + CHECK(def.generate_packages() == false); } TEST_CASE("Test config includes", "[unit-test]") diff --git a/tests/test_config_data/simple.yml b/tests/test_config_data/simple.yml index 22e6d4f7..7b0e0952 100644 --- a/tests/test_config_data/simple.yml +++ b/tests/test_config_data/simple.yml @@ -9,6 +9,7 @@ diagrams: using_namespace: - clanguml generate_method_arguments: full + generate_packages: true include: namespaces: - clanguml diff --git a/tests/test_util.cc b/tests/test_util.cc index 9fa461da..8e7b93fa 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -32,6 +32,8 @@ TEST_CASE("Test split", "[unit-test]") CHECK(split("ABCD", " ") == C{"ABCD"}); CHECK(split("std::vector::detail", "::") == C{"std", "vector", "detail"}); + + CHECK(split("std::vector::detail::", "::") == C{"std", "vector", "detail"}); } TEST_CASE("Test ns_relative", "[unit-test]")