diff --git a/src/class_diagram/model/class.cc b/src/class_diagram/model/class.cc index be5ab786..59388157 100644 --- a/src/class_diagram/model/class.cc +++ b/src/class_diagram/model/class.cc @@ -87,8 +87,9 @@ std::string class_::base_template() const { return base_template_full_name_; } bool operator==(const class_ &l, const class_ &r) { - return (l.name_and_ns() == r.name_and_ns()) && - (l.templates_ == r.templates_); + return l.id() == r.id(); + //(l.name_and_ns() == r.name_and_ns()) && + // (l.templates_ == r.templates_); } void class_::add_type_alias(type_alias &&ta) diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 1873ff3d..0bc93a2f 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -165,8 +165,11 @@ bool diagram::add_class(std::unique_ptr &&c) auto name_with_ns = c->name_and_ns(); auto name_and_ns = ns | name; auto &cc = *c; + auto id = cc.id(); if (!has_class(cc)) { + if (base_name == "cpp_function_parameter") + LOG_DBG("AAAAAAAAAAAAAAAAAAAAAAA"); if (add_element(ns, std::move(c))) classes_.push_back(std::ref(cc)); @@ -176,10 +179,13 @@ bool diagram::add_class(std::unique_ptr &&c) throw std::runtime_error( "Invalid element stored in the diagram tree"); + LOG_DBG("Added class {} ({} - [{}])", base_name, full_name, id); + return true; } - LOG_DBG("Class {} ({}) already in the model", base_name, full_name); + LOG_DBG( + "Class {} ({} - [{}]) already in the model", base_name, full_name, id); return false; } diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 1d8e32fb..e3e1445b 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -124,8 +124,6 @@ std::string get_source_text( { clang::LangOptions lo; - // NOTE: sm.getSpellingLoc() used in case the range corresponds to a - // macro/preprocessed source. auto start_loc = sm.getSpellingLoc(range.getBegin()); auto last_token_loc = sm.getSpellingLoc(range.getEnd()); auto end_loc = clang::Lexer::getLocForEndOfToken(last_token_loc, 0, sm, lo); @@ -149,6 +147,10 @@ bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns) if (ns->isAnonymousNamespace() || ns->isInline()) return true; + LOG_DBG("= Visiting namespace declaration {} at {}", + ns->getQualifiedNameAsString(), + ns->getLocation().printToString(source_manager_)); + auto package_path = namespace_{common::get_qualified_name(*ns)}; auto package_parent = package_path; @@ -198,6 +200,10 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm) if (enm->getNameAsString().empty()) return true; + LOG_DBG("= Visiting enum declaration {} at {}", + enm->getQualifiedNameAsString(), + enm->getLocation().printToString(source_manager_)); + auto e_ptr = std::make_unique(config_.using_namespace()); auto &e = *e_ptr; @@ -241,6 +247,10 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) return true; + LOG_DBG("= Visiting template specialization declaration {} at {}", + cls->getQualifiedNameAsString(), + cls->getLocation().printToString(source_manager_)); + // Skip forward declarations if (!cls->isCompleteDefinition()) { // Register this forward declaration in case there is no complete @@ -251,7 +261,7 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( // Check if the class was already processed within // VisitClassTemplateDecl() if (diagram_.has_element(cls->getID())) - return true; + return true; // TODO: Add support for classes defined in function/method bodies if (cls->isLocalClass()) @@ -289,6 +299,10 @@ bool translation_unit_visitor::VisitTypeAliasTemplateDecl( if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) return true; + LOG_DBG("= Visiting template type alias declaration {} at {}", + cls->getQualifiedNameAsString(), + cls->getLocation().printToString(source_manager_)); + auto *template_type_specialization_ptr = cls->getTemplatedDecl() ->getUnderlyingType() @@ -320,9 +334,9 @@ bool translation_unit_visitor::VisitClassTemplateDecl( if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) return true; - // Skip forward declarations - // if (!cls->getTemplatedDecl()->isCompleteDefinition()) - // return true; + LOG_DBG("= Visiting class template declaration {} at {}", + cls->getQualifiedNameAsString(), + cls->getLocation().printToString(source_manager_)); auto c_ptr = create_class_declaration(cls->getTemplatedDecl()); @@ -362,9 +376,27 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) return true; + LOG_DBG("= Visiting class declaration {} at {}", + cls->getQualifiedNameAsString(), + cls->getLocation().printToString(source_manager_)); + // Skip forward declarations - if (!cls->isCompleteDefinition()) - return true; + // if (!cls->isCompleteDefinition()) + // return true; + + if (cls->getQualifiedNameAsString() == "cppast::cpp_function_parameter" && + cls->getLocation().printToString(source_manager_) == + "/home/bartek/devel/clang-uml-showcases/cppast/src/../include/" + "cppast/cpp_function.hpp:16:7") { + LOG_DBG("##############################################################" + "##########################"); + for (const auto &c : diagram().classes()) { + LOG_DBG(" >> {} [{}]", c.get().full_name(false), c.get().id()); + } + const auto &ccc = diagram().get_class(cls->getID()); + if (ccc.has_value()) + LOG_DBG("---------- {}", ccc.get()->full_name(false)); + } // Templated records are handled by VisitClassTemplateDecl() if (cls->isTemplated() || cls->isTemplateDecl() || @@ -373,8 +405,9 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) return true; // Check if the class was already processed within VisitClassTemplateDecl() - if (diagram_.has_element(cls->getID())) - return true; + // auto cls_id = cls->getID(); + // if (diagram_.has_element(cls_id)) + // return true; // TODO: Add support for classes defined in function/method bodies if (cls->isLocalClass()) @@ -385,9 +418,12 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) if (!c_ptr) return true; - auto &class_model = *c_ptr; + auto &class_model = diagram().get_class(cls->getID()).has_value() + ? *diagram().get_class(cls->getID()).get() + : *c_ptr; - process_class_declaration(*cls, class_model); + if (cls->isCompleteDefinition()) + process_class_declaration(*cls, class_model); auto id = class_model.id(); if (!cls->isCompleteDefinition()) { @@ -403,6 +439,10 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) diagram_.add_class(std::move(c_ptr)); } + else { + LOG_DBG("Skipping class {} with id {}", class_model.full_name(), + class_model.id()); + } return true; } @@ -901,7 +941,7 @@ void translation_unit_visitor::process_function_parameter( const auto *default_arg = p.getDefaultArg(); if (default_arg != nullptr) { auto default_arg_str = - default_arg->getSourceRange().printToString(source_manager_); + get_source_text(default_arg->getSourceRange(), source_manager_); parameter.set_default_value(default_arg_str); } } @@ -1864,7 +1904,7 @@ void translation_unit_visitor::add_incomplete_forward_declarations() void translation_unit_visitor::finalize() { - add_incomplete_forward_declarations(); +// add_incomplete_forward_declarations(); } bool translation_unit_visitor::simplify_system_template( diff --git a/src/common/generators/plantuml/generator.h b/src/common/generators/plantuml/generator.h index 44af1602..5d262ea0 100644 --- a/src/common/generators/plantuml/generator.h +++ b/src/common/generators/plantuml/generator.h @@ -262,6 +262,9 @@ public: { visitor_.TraverseDecl(ast_context.getTranslationUnitDecl()); visitor_.finalize(); + +// LOG_DBG("= Finalized translation unit: {}", +// ast_context.getTranslationUnitDecl()); } }; @@ -287,6 +290,8 @@ public: protected: bool BeginSourceFileAction(clang::CompilerInstance &ci) override { + LOG_DBG("Visiting source file: {}", getCurrentFile().str()); + if constexpr (std::is_same_v) { auto find_includes_callback = @@ -351,11 +356,10 @@ std::unique_ptr generate( const auto matches = glob::rglob(g); std::copy(matches.begin(), matches.end(), std::back_inserter(translation_units)); - - LOG_DBG( - "Found translation units: {}", fmt::join(translation_units, ", ")); } + LOG_DBG("Found translation units: {}", fmt::join(translation_units, ", ")); + clang::tooling::ClangTool clang_tool(db, translation_units); auto action_factory = std::make_unique("basic_method"))); REQUIRE_THAT(puml, (IsMethod("static_method", "int"))); REQUIRE_THAT(puml, (IsMethod("const_method"))); + REQUIRE_THAT(puml, (IsMethod("default_int", "int", "int i = 12"))); + REQUIRE_THAT(puml, + (IsMethod("default_string", "std::string", + "int i, std::string s = \"abc\""))); + REQUIRE_THAT(puml, (IsMethod("protected_method"))); REQUIRE_THAT(puml, (IsMethod("private_method"))); REQUIRE_THAT(puml, (IsField("public_member", "int"))); diff --git a/tests/t00048/.clang-uml b/tests/t00048/.clang-uml new file mode 100644 index 00000000..d95684b8 --- /dev/null +++ b/tests/t00048/.clang-uml @@ -0,0 +1,16 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00048_class: + type: class + glob: +# - ../../tests/t00048/*.cc +# - ../../tests/t00048/t00048.cc +# + - ../../tests/t00048/b_t00048.cc + - ../../tests/t00048/a_t00048.cc + using_namespace: clanguml::t00048 + parse_includes: true + include: + namespaces: + - clanguml::t00048 \ No newline at end of file diff --git a/tests/t00048/a_t00048.cc b/tests/t00048/a_t00048.cc new file mode 100644 index 00000000..b67263bb --- /dev/null +++ b/tests/t00048/a_t00048.cc @@ -0,0 +1,9 @@ +#include "a_t00048.h" + +namespace clanguml { +namespace t00048 { + +void A::foo() {} + +} +} \ No newline at end of file diff --git a/tests/t00048/a_t00048.h b/tests/t00048/a_t00048.h new file mode 100644 index 00000000..c130ca0f --- /dev/null +++ b/tests/t00048/a_t00048.h @@ -0,0 +1,21 @@ +#include "t00048.h" + +#pragma once + +namespace clanguml { +namespace t00048 { + +struct A : public Base { + int a; + + void foo() override; +}; + +template struct ATemplate : public BaseTemplate { + T a; + + void foo() override {} +}; + +} +} \ No newline at end of file diff --git a/tests/t00048/b_t00048.cc b/tests/t00048/b_t00048.cc new file mode 100644 index 00000000..1897152e --- /dev/null +++ b/tests/t00048/b_t00048.cc @@ -0,0 +1,9 @@ +#include "b_t00048.h" + +namespace clanguml { +namespace t00048 { + +void B::foo() {} + +} +} \ No newline at end of file diff --git a/tests/t00048/b_t00048.h b/tests/t00048/b_t00048.h new file mode 100644 index 00000000..95c14bb4 --- /dev/null +++ b/tests/t00048/b_t00048.h @@ -0,0 +1,21 @@ +#include "t00048.h" + +#pragma once + +namespace clanguml { +namespace t00048 { + +struct B : public Base { + int b; + + void foo() override; +}; + +template struct BTemplate : public BaseTemplate { + T b; + + void foo() override {} +}; + +} +} \ No newline at end of file diff --git a/tests/t00048/t00048.cc b/tests/t00048/t00048.cc new file mode 100644 index 00000000..22b281f8 --- /dev/null +++ b/tests/t00048/t00048.cc @@ -0,0 +1,7 @@ +#include "t00048.h" + +namespace clanguml { +namespace t00048 +{ +} +} \ No newline at end of file diff --git a/tests/t00048/t00048.h b/tests/t00048/t00048.h new file mode 100644 index 00000000..e7aac8c9 --- /dev/null +++ b/tests/t00048/t00048.h @@ -0,0 +1,19 @@ +#pragma once + +namespace clanguml { +namespace t00048 { + +struct Base { + int base; + + virtual void foo() = 0; +}; + +template struct BaseTemplate { + T base; + + virtual void foo() = 0; +}; + +} +} \ No newline at end of file diff --git a/tests/t00048/test_case.h b/tests/t00048/test_case.h new file mode 100644 index 00000000..a0575fd3 --- /dev/null +++ b/tests/t00048/test_case.h @@ -0,0 +1,75 @@ +/** + * tests/t00048/test_case.h + * + * 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("t00048", "[test-case][class]") +{ + auto [config, db] = load_config("t00048"); + + auto diagram = config.diagrams["t00048_class"]; + + REQUIRE(diagram->name == "t00048_class"); + + auto model = generate_class_diagram(*db, diagram); + + REQUIRE(model->name() == "t00048_class"); + + auto puml = generate_class_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + + // Check if all classes exist + REQUIRE_THAT(puml, IsAbstractClass(_A("Base"))); + REQUIRE_THAT(puml, IsClass(_A("A"))); + REQUIRE_THAT(puml, IsClass(_A("B"))); + + + // Check if class templates exist + REQUIRE_THAT(puml, IsAbstractClassTemplate("BaseTemplate", "T")); + REQUIRE_THAT(puml, IsClassTemplate("ATemplate", "T")); + REQUIRE_THAT(puml, IsClassTemplate("BTemplate", "T")); + + // Check if all enums exist + //REQUIRE_THAT(puml, IsEnum(_A("Lights"))); + + // Check if all inner classes exist + //REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA"))); + + // Check if all inheritance relationships exist + REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("A"))); + REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("B"))); + + // Check if all methods exist + //REQUIRE_THAT(puml, (IsMethod("foo"))); + + // Check if all fields exist + //REQUIRE_THAT(puml, (IsField("private_member", "int"))); + + // Check if all relationships exist + //REQUIRE_THAT(puml, IsAssociation(_A("D"), _A("A"), "-as")); + //REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B"))); + //REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("D"))); + //REQUIRE_THAT(puml, IsComposition(_A("R"), _A("D"))); + //REQUIRE_THAT(puml, IsInstantiation(_A("ABCD::F"), _A("F"))); + + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 6090872d..62e9dfd7 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -236,6 +236,7 @@ using namespace clanguml::test::matchers; #include "t00045/test_case.h" #include "t00046/test_case.h" #include "t00047/test_case.h" +#include "t00048/test_case.h" //// //// Sequence diagram tests diff --git a/tests/test_cases.h b/tests/test_cases.h index 5020c6c9..84aae50f 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -229,6 +229,14 @@ ContainsMatcher IsAbstractClass(std::string const &str, return ContainsMatcher(CasedString("abstract " + str, caseSensitivity)); } +ContainsMatcher IsAbstractClassTemplate(std::string const &str, + std::string const &tmplt, + CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) +{ + return ContainsMatcher(CasedString( + fmt::format("abstract \"{}<{}>\"", str, tmplt), caseSensitivity)); +} + ContainsMatcher IsBaseClass(std::string const &base, std::string const &sub, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) { @@ -376,7 +384,7 @@ ContainsMatcher HasLink(std::string const &alias, std::string const &link, template ContainsMatcher IsMethod(std::string const &name, - std::string const &type = "void", + std::string const &type = "void", std::string const ¶ms = "", CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) { std::string pattern; @@ -395,7 +403,7 @@ ContainsMatcher IsMethod(std::string const &name, pattern += name; - pattern += "()"; + pattern += "(" + params + ")"; if constexpr (has_type()) pattern += " const";