diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 6fa1f5d3..00000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ - -set(SOURCE_FILES " - config/config.cc -") - - diff --git a/src/cx/type.h b/src/cx/type.h index bfb1fab8..67882b2a 100644 --- a/src/cx/type.h +++ b/src/cx/type.h @@ -21,6 +21,7 @@ #include #include +#include "uml/class_diagram_model.h" #include "util/util.h" namespace clanguml { @@ -151,9 +152,14 @@ public: (kind() == CXType_RValueReference); } + bool is_array() const { return clang_getArraySize(m_type) > -1; } + + type array_type() const { return {clang_getArrayElementType(m_type)}; } + bool is_relationship() const { - return is_pointer() || is_record() || is_reference() || !is_pod(); + return is_pointer() || is_record() || is_reference() || !is_pod() || + is_array() || (spelling().find("std::array") == 0); } type element_type() const { return clang_getElementType(m_type); } @@ -180,6 +186,8 @@ public: type value_type() const { return clang_Type_getValueType(m_type); } + bool is_template() const { return template_arguments_count() > 0; } + int template_arguments_count() const { return clang_Type_getNumTemplateArguments(m_type); @@ -203,10 +211,12 @@ public: const std::vector qualifiers = { "static", "const", "volatile", "register", "mutable"}; - while (toks.size() > 0 && - std::count(qualifiers.begin(), qualifiers.end(), toks.front())) { - toks.erase(toks.begin()); - } + toks.erase(toks.begin(), + std::find_if( + toks.begin(), toks.end(), [&qualifiers](const auto &t) { + return std::count( + qualifiers.begin(), qualifiers.end(), t) == 0; + })); return fmt::format("{}", fmt::join(toks, " ")); } diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index 01593b00..fe47904b 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -103,6 +103,109 @@ scope_t cx_access_specifier_to_scope(CX_CXXAccessSpecifier as) return res; } +cx::type get_underlying_type(cx::type t) +{ + if (t.is_array()) { + return t.array_type(); + } + + auto template_arguments_count = t.template_arguments_count(); + + if (template_arguments_count == 0) + return t; + + auto name = t.canonical().unqualified(); + + std::vector template_arguments; + for (int i = 0; i < template_arguments_count; i++) { + auto tt = t.template_argument_type(i); + template_arguments.push_back(tt); + } + + if (name.find("std::unique_ptr") == 0) { + return get_underlying_type(template_arguments[0]); + } + if (name.find("std::shared_ptr") == 0) { + return get_underlying_type(template_arguments[0]); + } + if (name.find("std::vector") == 0) { + return get_underlying_type(template_arguments[0]); + } + if (name.find("std::array") == 0) { + return get_underlying_type(template_arguments[0]); + } + if (name.find("clanguml::t00006::custom_container") == 0) { + return get_underlying_type(template_arguments[0]); + } + if (name.find("std::map") == 0) { + return get_underlying_type(template_arguments[1]); + } + + return t; +} + +relationship_t get_relationship_type(cx::type t) +{ + relationship_t relationship_type = relationship_t::kNone; + + if (t.is_array()) { + return get_relationship_type(t.array_type()); + } + + auto name = t.canonical().unqualified(); + + const auto template_argument_count = t.template_arguments_count(); + + if (template_argument_count > 0) { + class_relationship r; + + std::vector template_arguments; + for (int i = 0; i < template_argument_count; i++) { + auto tt = t.template_argument_type(i); + template_arguments.push_back(tt); + } + + if (name.find("std::unique_ptr") == 0) { + relationship_type = relationship_t::kComposition; + return get_relationship_type(template_arguments[0]); + } + if (name.find("std::shared_ptr") == 0) { + relationship_type = relationship_t::kAssociation; + return get_relationship_type(template_arguments[0]); + } + if (name.find("std::vector") == 0) { + relationship_type = relationship_t::kComposition; + return get_relationship_type(template_arguments[0]); + } + if (name.find("std::array") == 0) { + relationship_type = relationship_t::kComposition; + return get_relationship_type(template_arguments[0]); + } + if (name.find("clanguml::t00006::custom_container") == 0) { + relationship_type = relationship_t::kComposition; + return get_relationship_type(template_arguments[0]); + } + if (name.find("std::map") == 0) { + relationship_type = relationship_t::kComposition; + return get_relationship_type(template_arguments[1]); + } + } + else if (t.kind() == CXType_Record) { + relationship_type = relationship_t::kOwnership; + } + else if (t.kind() == CXType_Pointer) { + relationship_type = relationship_t::kAssociation; + } + else if (t.kind() == CXType_LValueReference) { + relationship_type = relationship_t::kAssociation; + } + else if (t.kind() == CXType_RValueReference) { + relationship_type = relationship_t::kOwnership; + } + + return relationship_type; +} + static enum CXChildVisitResult enum_visitor( CXCursor cx_cursor, CXCursor cx_parent, CXClientData client_data) { @@ -253,101 +356,52 @@ static enum CXChildVisitResult class_visitor( auto t = cursor.type(); class_member m; m.name = cursor.spelling(); - m.type = cursor.type().canonical().unqualified(); + m.type = t.is_template() ? t.unqualified() + : t.canonical().unqualified(); m.scope = cx_access_specifier_to_scope( cursor.cxxaccess_specifier()); m.is_static = cursor.is_static(); spdlog::info("Adding member {} {}::{} (type kind: {} | {} " - "| {} | {})", + "| {} | {} | {})", m.type, ctx->element.name, cursor.spelling(), t.kind_spelling(), t.pointee_type().spelling(), - t.is_pod(), cursor.type().canonical().spelling()); + t.is_pod(), t.canonical().spelling(), + t.is_relationship()); relationship_t relationship_type = relationship_t::kNone; + auto name = t.canonical().unqualified(); + auto destination = name; + // Parse the field declaration to determine the relationship // type // Skip: // - POD // - function variables + spdlog::info( + "Analyzing possible relationship candidate: {}", + t.canonical().unqualified()); + if (t.is_relationship() && - config.should_include( - cursor.type().canonical().unqualified())) { - spdlog::info( - "Analazing possible relationship candidate: {}", - t.spelling()); - if (t.kind() == CXType_Record) { - spdlog::info( - "Found relationship candidate record: {} | {}", - t.spelling(), t.pointee_type().spelling()); - relationship_type = relationship_t::kOwnership; - } - else if (t.kind() == CXType_Pointer) { - spdlog::info( - "Found relationship candidate pointer: {}", - t.spelling()); - relationship_type = relationship_t::kAssociation; - t = t.referenced(); - } - else if (t.kind() == CXType_LValueReference) { - spdlog::info("Found relationship candidate " - "lvalue reference: {}", - t.spelling()); - relationship_type = relationship_t::kAssociation; - t = t.referenced(); - } - else if (t.kind() == CXType_RValueReference) { - spdlog::info("Found relationship candidate " - "rvalue reference: {}", - t.spelling()); - relationship_type = relationship_t::kOwnership; - t = t.referenced(); - } + (config.should_include(t.canonical().unqualified()) || + t.is_template() || t.is_array())) { + relationship_t relationship_type = + get_relationship_type(t); if (relationship_type != relationship_t::kNone) { - spdlog::info( - "Found unknown candidate: {}", t.spelling()); - spdlog::error("UNKNOWN CXTYPE: {}", t.kind()); class_relationship r; - auto template_argument_count = - t.template_arguments_count(); - std::string name = t.canonical().unqualified(); - - if (template_argument_count > 0) { - std::vector template_arguments; - for (int i = 0; i < template_argument_count; - i++) { - auto tt = t.template_argument_type(i); - template_arguments.push_back(tt); - } - - if (name.rfind("vector") == 0 || - name.rfind("std::vector") == 0) { - r.type = relationship_t::kAggregation; - r.destination = - template_arguments[0].spelling(); - } - if (name.rfind("map") == 0 || - name.rfind("std::map") == 0) { - r.type = relationship_t::kAggregation; - r.destination = - template_arguments[1].spelling(); - } - r.label = m.name; - ctx->element.relationships.emplace_back( - std::move(r)); - } - else { - r.destination = name; - r.type = relationship_type; - r.label = m.name; - ctx->element.relationships.emplace_back( - std::move(r)); - } + r.destination = get_underlying_type(t) + .referenced() + .canonical() + .unqualified(); + r.type = relationship_type; + r.label = m.name; + ctx->element.relationships.emplace_back( + std::move(r)); spdlog::info( - "Adding relationship to: {}", r.destination); + "Added relationship to: {}", r.destination); } } diff --git a/src/util/util.cc b/src/util/util.cc index dafd830f..f608fabd 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -69,4 +69,3 @@ std::string ns_relative( } } } - diff --git a/tests/t00002/test_case.h b/tests/t00002/test_case.h index 2bb4b211..ee4a0930 100644 --- a/tests/t00002/test_case.h +++ b/tests/t00002/test_case.h @@ -54,6 +54,8 @@ TEST_CASE("Test t00002", "[unit-test]") REQUIRE_THAT(puml, IsMethod(Abstract(Public("foo_a")))); REQUIRE_THAT(puml, IsMethod(Abstract(Public("foo_c")))); + REQUIRE_THAT(puml, IsAssociation("D", "A", "as")); + save_puml( "./" + config.output_directory + "/" + diagram->name + ".puml", puml); } diff --git a/tests/t00006/.clanguml b/tests/t00006/.clanguml new file mode 100644 index 00000000..9b44b117 --- /dev/null +++ b/tests/t00006/.clanguml @@ -0,0 +1,12 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00006_class: + type: class + glob: + - ../../tests/t00006/t00006.cc + using_namespace: + - clanguml::t00006 + include: + namespaces: + - clanguml::t00006 diff --git a/tests/t00006/t00006.cc b/tests/t00006/t00006.cc new file mode 100644 index 00000000..e6dc2b77 --- /dev/null +++ b/tests/t00006/t00006.cc @@ -0,0 +1,64 @@ +#include +#include + +namespace clanguml { +namespace t00006 { +class A { +}; + +class B { +}; + +class C { +}; + +class D { +}; + +class E { +}; + +class F { +}; + +class G { +}; + +class H { +}; + +class I { +}; + +class J { +}; + +class K { +}; + +template class custom_container { +public: + std::vector data; +}; + +class R { +public: + std::vector a; + std::vector b; + + std::map c; + std::map d; + + custom_container e; + + std::vector> f; + std::map> g; + + std::array h; + std::array i; + + J j[10]; + K *k[20]; +}; +} +} diff --git a/tests/t00006/test_case.h b/tests/t00006/test_case.h new file mode 100644 index 00000000..3362bab3 --- /dev/null +++ b/tests/t00006/test_case.h @@ -0,0 +1,76 @@ +/** + * tests/t00006/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("Test t00006", "[unit-test]") +{ + spdlog::set_level(spdlog::level::debug); + + auto [config, db] = load_config("t00006"); + + auto diagram = config.diagrams["t00006_class"]; + + REQUIRE(diagram->name == "t00006_class"); + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t00006"})); + + REQUIRE(diagram->exclude.namespaces.size() == 0); + + REQUIRE(diagram->should_include("clanguml::t00006::A")); + REQUIRE(diagram->should_include("clanguml::t00006::B")); + REQUIRE(diagram->should_include("clanguml::t00006::C")); + REQUIRE(diagram->should_include("clanguml::t00006::D")); + REQUIRE(diagram->should_include("clanguml::t00006::E")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name == "t00006_class"); + + auto puml = generate_class_puml(diagram, model); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + REQUIRE_THAT(puml, IsClass("A")); + REQUIRE_THAT(puml, IsClass("B")); + REQUIRE_THAT(puml, IsClass("C")); + REQUIRE_THAT(puml, IsClass("D")); + REQUIRE_THAT(puml, IsClass("E")); + REQUIRE_THAT(puml, IsClass("F")); + REQUIRE_THAT(puml, IsClass("G")); + REQUIRE_THAT(puml, IsClass("H")); + REQUIRE_THAT(puml, IsClass("I")); + REQUIRE_THAT(puml, IsClass("J")); + REQUIRE_THAT(puml, IsClass("K")); + REQUIRE_THAT(puml, IsClass("R")); + + REQUIRE_THAT(puml, IsComposition("R", "A", "a")); + REQUIRE_THAT(puml, IsAssociation("R", "B", "b")); + REQUIRE_THAT(puml, IsComposition("R", "C", "c")); + REQUIRE_THAT(puml, IsAssociation("R", "D", "d")); + REQUIRE_THAT(puml, IsComposition("R", "E", "e")); + REQUIRE_THAT(puml, IsComposition("R", "F", "f")); + REQUIRE_THAT(puml, IsAssociation("R", "G", "g")); + REQUIRE_THAT(puml, IsComposition("R", "H", "h")); + REQUIRE_THAT(puml, IsAssociation("R", "I", "i")); + REQUIRE_THAT(puml, IsComposition("R", "J", "j")); + REQUIRE_THAT(puml, IsAssociation("R", "K", "k")); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index c8825fd7..d308fd7a 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -114,3 +114,4 @@ using clanguml::test::matchers::Static; #include "t00003/test_case.h" #include "t00004/test_case.h" #include "t00005/test_case.h" +#include "t00006/test_case.h"