Fixed generation of lambda names in class diagrams (#78)

This commit is contained in:
Bartek Kryza
2023-01-21 23:23:13 +01:00
parent b5cf78ce82
commit 023a4a0cc0
9 changed files with 176 additions and 13 deletions

View File

@@ -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<clang::TemplateSpecializationType>();
// if (type_instantiation_decl != nullptr) {
// if (type_instantiation_decl->isTypeAlias())
// type_instantiation_decl =
// type_instantiation_decl->getAliasedType()
// ->getAs<clang::TemplateSpecializationType>();
// }
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 &parameter_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<std::string> & /*template_parameter_names*/,

View File

@@ -254,5 +254,6 @@ private:
std::tuple<std::string /* field name */, common::model::relationship_t,
common::model::access_t>>
anonymous_struct_relationships_;
void ensure_lambda_type_is_relative(std::string &parameter_type) const;
};
} // namespace clanguml::class_diagram::visitor

View File

@@ -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 <typename T> bool ends_with(const T &value, const T &suffix);

12
tests/t00051/.clang-uml Normal file
View File

@@ -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

63
tests/t00051/t00051.cc Normal file
View File

@@ -0,0 +1,63 @@
#include <thread>
namespace clanguml {
namespace t00051 {
template <typename F, typename FF = F> 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 <class Function, class... Args>
explicit custom_thread1(Function &&f, Args &&...args)
: std::thread::thread(
std::forward<Function>(f), std::forward<Args>(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{[]() {}};
}
}
}

57
tests/t00051/test_case.h Normal file
View File

@@ -0,0 +1,57 @@
/**
* tests/t00051/test_case.h
*
* Copyright (c) 2021-2023 Bartek Kryza <bkryza@gmail.com>
*
* 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<Public>(
"custom_thread1", "void", "Function && f, Args &&... args")));
REQUIRE_THAT(puml,
(IsMethod<Public>("thread", "void",
"(lambda at ../../tests/t00051/t00051.cc:59:27) && ")));
REQUIRE_THAT(puml,
(IsMethod<Private>("start_thread3",
"B<(lambda at ../../tests/t00051/t00051.cc:43:18),(lambda at "
"../../tests/t00051/t00051.cc:43:27)>")));
REQUIRE_THAT(puml,
(IsMethod<Private>(
"get_function", "(lambda at ../../tests/t00051/t00051.cc:48:16)")));
save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}