From 023a4a0cc082777a5336890be455baca023d25a6 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 21 Jan 2023 23:23:13 +0100 Subject: [PATCH] Fixed generation of lambda names in class diagrams (#78) --- .../visitor/translation_unit_visitor.cc | 46 +++++++++++--- .../visitor/translation_unit_visitor.h | 1 + src/util/util.h | 3 +- tests/t00051/.clang-uml | 12 ++++ tests/t00051/t00051.cc | 63 +++++++++++++++++++ tests/t00051/test_case.h | 57 +++++++++++++++++ tests/test_cases.cc | 1 + tests/test_cases.yaml | 3 + util/templates/test_cases/test_case.h | 3 +- 9 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 tests/t00051/.clang-uml create mode 100644 tests/t00051/t00051.cc create mode 100644 tests/t00051/test_case.h diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index ea11207d..40ba3063 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -828,9 +828,13 @@ void translation_unit_visitor::process_method( if (mf.isDefaulted() && !mf.isExplicitlyDefaulted()) return; + auto method_return_type = + common::to_string(mf.getReturnType(), mf.getASTContext()); + + ensure_lambda_type_is_relative(method_return_type); + class_method method{common::access_specifier_to_access_t(mf.getAccess()), - util::trim(mf.getNameAsString()), - common::to_string(mf.getReturnType(), mf.getASTContext())}; + util::trim(mf.getNameAsString()), std::move(method_return_type)}; method.is_pure_virtual(mf.isPure()); method.is_virtual(mf.isVirtual()); @@ -923,13 +927,6 @@ bool translation_unit_visitor::find_relationships(const clang::QualType &type, const auto *type_instantiation_decl = type->getAs(); - // if (type_instantiation_decl != nullptr) { - // if (type_instantiation_decl->isTypeAlias()) - // type_instantiation_decl = - // type_instantiation_decl->getAliasedType() - // ->getAs(); - // } - if (type_instantiation_decl != nullptr) { for (const auto &template_argument : *type_instantiation_decl) { const auto template_argument_kind = template_argument.getKind(); @@ -996,7 +993,12 @@ void translation_unit_visitor::process_function_parameter( if (parameter.skip()) return; - parameter.set_type(common::to_string(p.getType(), p.getASTContext())); + auto parameter_type = common::to_string(p.getType(), p.getASTContext()); + + // Is there no better way to determine that 'type' is a lambda? + ensure_lambda_type_is_relative(parameter_type); + + parameter.set_type(parameter_type); if (p.hasDefaultArg()) { const auto *default_arg = p.getDefaultArg(); @@ -1048,6 +1050,30 @@ void translation_unit_visitor::process_function_parameter( method.add_parameter(std::move(parameter)); } +void translation_unit_visitor::ensure_lambda_type_is_relative( + std::string ¶meter_type) const +{ + std::string lambda_prefix{"(lambda at /"}; + + while (parameter_type.find(lambda_prefix) != std::string::npos) { + auto lambda_begin = parameter_type.find(lambda_prefix); + + auto absolute_lambda_path_end = parameter_type.find(":", lambda_begin); + auto absolute_lambda_path = + parameter_type.substr(lambda_begin + lambda_prefix.size() - 1, + absolute_lambda_path_end - + (lambda_begin + lambda_prefix.size() - 1)); + + auto relative_lambda_path = std::filesystem::relative( + absolute_lambda_path, config().relative_to()) + .string(); + + parameter_type = fmt::format("{}(lambda at {}{}", + parameter_type.substr(0, lambda_begin), relative_lambda_path, + parameter_type.substr(absolute_lambda_path_end)); + } +} + void translation_unit_visitor:: process_function_parameter_find_relationships_in_template(class_ &c, const std::set & /*template_parameter_names*/, diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index 02778238..b7c4bde4 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -254,5 +254,6 @@ private: std::tuple> anonymous_struct_relationships_; + void ensure_lambda_type_is_relative(std::string ¶meter_type) const; }; } // namespace clanguml::class_diagram::visitor diff --git a/src/util/util.h b/src/util/util.h index 96e39fbf..03c853f1 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -162,7 +162,8 @@ template <> bool starts_with( const std::filesystem::path &path, const std::filesystem::path &prefix); -template <> bool starts_with(const std::string &s, const std::string &prefix); +template <> +bool starts_with(const std::string &s, const std::string &prefix); template bool ends_with(const T &value, const T &suffix); diff --git a/tests/t00051/.clang-uml b/tests/t00051/.clang-uml new file mode 100644 index 00000000..4cb46059 --- /dev/null +++ b/tests/t00051/.clang-uml @@ -0,0 +1,12 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00051_class: + type: class + glob: + - ../../tests/t00051/t00051.cc + include: + namespaces: + - clanguml::t00051 + using_namespace: + - clanguml::t00051 \ No newline at end of file diff --git a/tests/t00051/t00051.cc b/tests/t00051/t00051.cc new file mode 100644 index 00000000..a9578fcb --- /dev/null +++ b/tests/t00051/t00051.cc @@ -0,0 +1,63 @@ +#include + +namespace clanguml { +namespace t00051 { + +template struct B : private std::thread { + B(F &&f, FF &&ff) + : f_{std::move(f)} + , ff_{std::move(ff)} + { + } + + void f() { f_(); } + void ff() { ff_(); } + + F f_; + FF ff_; +}; + +class A { +public: +private: + class custom_thread1 : private std::thread { + public: + template + explicit custom_thread1(Function &&f, Args &&...args) + : std::thread::thread( + std::forward(f), std::forward(args)...) + { + } + }; + + static custom_thread1 start_thread1(); + + class custom_thread2 : private std::thread { + using std::thread::thread; + }; + + static custom_thread2 start_thread2(); + + auto start_thread3() + { + return B{[]() {}, []() {}}; + } + + auto get_function() + { + return []() {}; + } +}; + +A::custom_thread1 A::start_thread1() +{ + return custom_thread1{[]() {}}; +} + +A::custom_thread2 A::start_thread2() +{ + return custom_thread2{[]() {}}; +} + +} +} diff --git a/tests/t00051/test_case.h b/tests/t00051/test_case.h new file mode 100644 index 00000000..54400afd --- /dev/null +++ b/tests/t00051/test_case.h @@ -0,0 +1,57 @@ +/** + * tests/t00051/test_case.h + * + * Copyright (c) 2021-2023 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("t00051", "[test-case][class]") +{ + auto [config, db] = load_config("t00051"); + + auto diagram = config.diagrams["t00051_class"]; + + REQUIRE(diagram->name == "t00051_class"); + + auto model = generate_class_diagram(*db, diagram); + + REQUIRE(model->name() == "t00051_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, IsClass(_A("A"))); + REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("A::custom_thread1"))); + REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("A::custom_thread2"))); + + REQUIRE_THAT(puml, + (IsMethod( + "custom_thread1", "void", "Function && f, Args &&... args"))); + REQUIRE_THAT(puml, + (IsMethod("thread", "void", + "(lambda at ../../tests/t00051/t00051.cc:59:27) && "))); + REQUIRE_THAT(puml, + (IsMethod("start_thread3", + "B<(lambda at ../../tests/t00051/t00051.cc:43:18),(lambda at " + "../../tests/t00051/t00051.cc:43:27)>"))); + REQUIRE_THAT(puml, + (IsMethod( + "get_function", "(lambda at ../../tests/t00051/t00051.cc:48:16)"))); + + 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 c267fdbd..3fd9bce1 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -244,6 +244,7 @@ using namespace clanguml::test::matchers; #include "t00048/test_case.h" #include "t00049/test_case.h" #include "t00050/test_case.h" +#include "t00051/test_case.h" /// /// Sequence diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 880825fd..105de191 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -147,6 +147,9 @@ test_cases: - name: t00050 title: Test case for generating notes from comments using jinja templates description: + - name: t00051 + title: Test case for relative paths in lambda names + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram test case diff --git a/util/templates/test_cases/test_case.h b/util/templates/test_cases/test_case.h index 34a5811e..83ed5856 100644 --- a/util/templates/test_cases/test_case.h +++ b/util/templates/test_cases/test_case.h @@ -36,6 +36,5 @@ TEST_CASE("{{ name }}", "[test-case][{{ type }}]") {{ examples }} - save_puml( - "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); + save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); }