diff --git a/docs/test_cases.md b/docs/test_cases.md index cece826a..9b9ccb92 100644 --- a/docs/test_cases.md +++ b/docs/test_cases.md @@ -14,6 +14,7 @@ * [t00013](./test_cases/t00013.md) - Template instantiation relationships * [t00014](./test_cases/t00014.md) - Alias template instantiation * [t00015](./test_cases/t00015.md) - Namespace fun + * [t00016](./test_cases/t00016.md) - Unnamed enums and empty templates ## Sequence diagrams * [t20001](./test_cases/t20001.md) - Basic sequence diagram ## Configuration diagrams diff --git a/docs/test_cases/t00002_class.png b/docs/test_cases/t00002_class.png index 0ef83bd0..6f06077b 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 28595aa2..36c68afe 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 a87dc02c..6c8a47b2 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 4c6d518c..9e5ce5f6 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 bc74dc32..18d7ce5d 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 23d4606e..875ae54e 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 ef2c7320..35255dee 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 48202bcb..ce51c215 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 3771bb06..5393151d 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 0075ae78..fdf63196 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 99023c3e..0a5bfd89 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 52e4f5b3..71a64e61 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 82b0c082..6bbd48d2 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 02ca9ee9..00000fb5 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.md b/docs/test_cases/t00016.md new file mode 100644 index 00000000..09bbee54 --- /dev/null +++ b/docs/test_cases/t00016.md @@ -0,0 +1,47 @@ +# t00016 - Unnamed enums and empty templates +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00016_class: + type: class + glob: + - ../../tests/t00016/t00016.cc + using_namespace: + - clanguml::t00016 + include: + namespaces: + - clanguml::t00016 + +``` +## Source code +```cpp +namespace clanguml { +namespace t00016 { + +template struct is_numeric { + enum { value = false }; +}; + +template <> struct is_numeric { + enum { value = true }; +}; + +template <> struct is_numeric { + enum { value = true }; +}; + +template <> struct is_numeric { + enum { value = true }; +}; + +template <> struct is_numeric { + enum { value = false }; +}; +} +} + +``` +## Generated UML diagrams +![t00016_class](./t00016_class.png "Unnamed enums and empty templates") diff --git a/docs/test_cases/t00016_class.png b/docs/test_cases/t00016_class.png new file mode 100644 index 00000000..e6b2686b Binary files /dev/null and b/docs/test_cases/t00016_class.png differ diff --git a/docs/test_cases/t20001_sequence.png b/docs/test_cases/t20001_sequence.png index 9e529af5..65779c1c 100644 Binary files a/docs/test_cases/t20001_sequence.png and b/docs/test_cases/t20001_sequence.png differ diff --git a/docs/test_cases/t90000_class.png b/docs/test_cases/t90000_class.png index b423cd26..9da1afdf 100644 Binary files a/docs/test_cases/t90000_class.png and b/docs/test_cases/t90000_class.png differ diff --git a/src/uml/class_diagram_visitor.cc b/src/uml/class_diagram_visitor.cc index c15e6b51..7b7be600 100644 --- a/src/uml/class_diagram_visitor.cc +++ b/src/uml/class_diagram_visitor.cc @@ -97,6 +97,18 @@ void tu_visitor::operator()(const cppast::cpp_entity &file) ctx.namespace_.pop_back(); } } + else if (e.kind() == + cppast::cpp_entity_kind::class_template_specialization_t) { + LOG_DBG("========== Visiting '{}' - {}", + cx::util::full_name(ctx.namespace_, e), + cppast::to_string(e.kind())); + + auto &tspec = static_cast< + const cppast::cpp_class_template_specialization &>(e); + + process_class_declaration( + tspec.class_(), type_safe::ref(tspec)); + } else if (e.kind() == cppast::cpp_entity_kind::class_t) { LOG_DBG("========== Visiting '{}' - {}", cx::util::full_name(ctx.namespace_, e), @@ -112,6 +124,7 @@ void tu_visitor::operator()(const cppast::cpp_entity &file) return; } } + if (ctx.config.should_include( cx::util::fully_prefixed(ctx.namespace_, cls))) process_class_declaration(cls); @@ -170,6 +183,12 @@ void tu_visitor::operator()(const cppast::cpp_entity &file) void tu_visitor::process_enum_declaration(const cppast::cpp_enum &enm) { + if (enm.name().empty()) { + // Anonymous enum values should be rendered as class fields + // with type enum + return; + } + enum_ e; e.name = cx::util::full_name(ctx.namespace_, enm); @@ -179,7 +198,7 @@ void tu_visitor::process_enum_declaration(const cppast::cpp_enum &enm) } } - // Find if class is contained in another class + // Find if enum is contained in a class for (auto cur = enm.parent(); cur; cur = cur.value().parent()) { // find nearest parent class, if any if (cur.value().kind() == cppast::cpp_entity_kind::class_t) { @@ -198,7 +217,8 @@ void tu_visitor::process_enum_declaration(const cppast::cpp_enum &enm) ctx.d.add_enum(std::move(e)); } -void tu_visitor::process_class_declaration(const cppast::cpp_class &cls) +void tu_visitor::process_class_declaration(const cppast::cpp_class &cls, + type_safe::optional_ref tspec) { class_ c; c.is_struct = cls.class_kind() == cppast::cpp_class_kind::struct_t; @@ -246,6 +266,14 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls) auto &mc = static_cast(child); process_destructor(mc, c, last_access_specifier); } + else if (child.kind() == cppast::cpp_entity_kind::enum_t) { + auto &en = static_cast(child); + if (en.name().empty()) { + // Here we only want to handle anonymous enums, regular nested + // enums are handled in the file-level visitor + process_anonymous_enum(en, c, last_access_specifier); + } + } else if (child.kind() == cppast::cpp_entity_kind::friend_t) { auto &fr = static_cast(child); @@ -296,30 +324,131 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls) if (cppast::is_templated(cls)) { LOG_DBG("Processing class template parameters..."); auto scope = cppast::cpp_scope_name(type_safe::ref(cls)); - 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); + // 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 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 { + 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) { + class_template ct; + ct.type = t; + ct.default_value = ""; + ct.is_variadic = false; + ct.name = ""; + c.templates.emplace_back(std::move(ct)); + + 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_usr = static_cast( + primary_template_ref.user_data()); + LOG_DBG("Primary template ref set to: {}", + base_template_usr); + // Add template specialization/instantiation + // relationship + class_relationship r; + r.type = relationship_t::kInstantiation; + r.label = ""; + r.destination = base_template_usr; + c.add_relationship(std::move(r)); + } + 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 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_DBG("Skipping template class declaration which has only " + "unexposed arguments but no tspec provided"); + return; } } } @@ -476,6 +605,21 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c, c.members.emplace_back(std::move(m)); } +void tu_visitor::process_anonymous_enum( + const cppast::cpp_enum &en, class_ &c, cppast::cpp_access_specifier_kind as) +{ + for (const auto &ev : en) { + if (ev.kind() == cppast::cpp_entity_kind::enum_value_t) { + class_member m; + m.name = ev.name(); + m.type = "enum"; // TODO: Try to figure out real enum type + m.scope = detail::cpp_access_specifier_to_scope(as); + m.is_static = false; + c.members.emplace_back(std::move(m)); + } + } +} + void tu_visitor::process_static_field(const cppast::cpp_variable &mv, class_ &c, cppast::cpp_access_specifier_kind as) { diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index 916fa1a9..c826d0c4 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -149,10 +149,16 @@ public: void operator()(const cppast::cpp_entity &file); - void process_class_declaration(const cppast::cpp_class &cls); + void process_class_declaration(const cppast::cpp_class &cls, + type_safe::optional_ref + tspec = nullptr); void process_enum_declaration(const cppast::cpp_enum &enm); + void process_anonymous_enum(const cppast::cpp_enum &en, + clanguml::model::class_diagram::class_ &c, + cppast::cpp_access_specifier_kind as); + void process_field(const cppast::cpp_member_variable &mv, clanguml::model::class_diagram::class_ &c, cppast::cpp_access_specifier_kind as); diff --git a/tests/t00016/.clanguml b/tests/t00016/.clanguml new file mode 100644 index 00000000..31881559 --- /dev/null +++ b/tests/t00016/.clanguml @@ -0,0 +1,12 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00016_class: + type: class + glob: + - ../../tests/t00016/t00016.cc + using_namespace: + - clanguml::t00016 + include: + namespaces: + - clanguml::t00016 diff --git a/tests/t00016/t00016.cc b/tests/t00016/t00016.cc new file mode 100644 index 00000000..519df160 --- /dev/null +++ b/tests/t00016/t00016.cc @@ -0,0 +1,24 @@ +namespace clanguml { +namespace t00016 { + +template struct is_numeric { + enum { value = false }; +}; + +template <> struct is_numeric { + enum { value = true }; +}; + +template <> struct is_numeric { + enum { value = true }; +}; + +template <> struct is_numeric { + enum { value = true }; +}; + +template <> struct is_numeric { + enum { value = false }; +}; +} +} diff --git a/tests/t00016/test_case.h b/tests/t00016/test_case.h new file mode 100644 index 00000000..96ac4d2e --- /dev/null +++ b/tests/t00016/test_case.h @@ -0,0 +1,61 @@ +/** + * tests/t00016/test_case.cc + * + * Copyright (c) 2021 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("t00016", "[test-case][class]") +{ + auto [config, db] = load_config("t00016"); + + auto diagram = config.diagrams["t00016_class"]; + + REQUIRE(diagram->name == "t00016_class"); + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t00016"})); + + REQUIRE(diagram->exclude.namespaces.size() == 0); + + REQUIRE(diagram->should_include("clanguml::t00016::is_numeric")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name == "t00016_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("is_numeric", "")); + REQUIRE_THAT(puml, IsClassTemplate("is_numeric", "int")); + REQUIRE_THAT(puml, IsClassTemplate("is_numeric", "bool")); + REQUIRE_THAT(puml, IsClassTemplate("is_numeric", "char")); + REQUIRE_THAT(puml, IsClassTemplate("is_numeric", "unsigned char")); + + REQUIRE_THAT( + puml, IsInstantiation(_A("is_numeric<>"), _A("is_numeric"))); + REQUIRE_THAT( + puml, IsInstantiation(_A("is_numeric<>"), _A("is_numeric"))); + REQUIRE_THAT( + puml, IsInstantiation(_A("is_numeric<>"), _A("is_numeric"))); + REQUIRE_THAT(puml, + IsInstantiation(_A("is_numeric<>"), _A("is_numeric"))); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 865e72c3..247dd161 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -119,6 +119,7 @@ using namespace clanguml::test::matchers; #include "t00013/test_case.h" #include "t00014/test_case.h" #include "t00015/test_case.h" +#include "t00016/test_case.h" // // Sequence diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 33e70e19..12a11e65 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -42,6 +42,9 @@ test_cases: - name: t00015 title: Namespace fun description: + - name: t00016 + title: Unnamed enums and empty templates + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram