Added support for 'class' diagrams from C99/C11 translation units (#97)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
* Added support for plain C11 translation units (#97)
|
||||||
* Added 'row' and 'column' layout hints for aligning elements (#90)
|
* Added 'row' and 'column' layout hints for aligning elements (#90)
|
||||||
* Added 'together' layout hint for grouping elements (#43)
|
* Added 'together' layout hint for grouping elements (#43)
|
||||||
* Enabled adding notes to class methods and members (#87)
|
* Enabled adding notes to class methods and members (#87)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ Main features supported so far include:
|
|||||||
* Diagram content filtering based on namespaces, elements and relationships
|
* Diagram content filtering based on namespaces, elements and relationships
|
||||||
* Optional package generation from namespaces
|
* Optional package generation from namespaces
|
||||||
* Interactive links to online code to classes, methods and class fields in SVG diagrams
|
* Interactive links to online code to classes, methods and class fields in SVG diagrams
|
||||||
|
* Support for plain C99/C11 code (struct and units relationships)
|
||||||
* **Sequence diagram generation**
|
* **Sequence diagram generation**
|
||||||
* Generation of sequence diagram from specific method or function
|
* Generation of sequence diagram from specific method or function
|
||||||
* Generation of loop and conditional statements
|
* Generation of loop and conditional statements
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
|
|||||||
|
|
||||||
ostr << class_type << " " << c.alias();
|
ostr << class_type << " " << c.alias();
|
||||||
|
|
||||||
|
if (c.is_union())
|
||||||
|
ostr << " "
|
||||||
|
<< "<<union>>";
|
||||||
|
|
||||||
if (m_config.generate_links) {
|
if (m_config.generate_links) {
|
||||||
common_generator<diagram_config, diagram_model>::generate_link(ostr, c);
|
common_generator<diagram_config, diagram_model>::generate_link(ostr, c);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ public:
|
|||||||
bool is_template_instantiation() const;
|
bool is_template_instantiation() const;
|
||||||
void is_template_instantiation(bool is_template_instantiation);
|
void is_template_instantiation(bool is_template_instantiation);
|
||||||
|
|
||||||
|
bool is_alias() const { return is_alias_; }
|
||||||
|
void is_alias(bool alias) { is_alias_ = alias; }
|
||||||
|
|
||||||
|
bool is_union() const { return is_union_; }
|
||||||
|
void is_union(bool u) { is_union_ = u; }
|
||||||
|
|
||||||
void add_member(class_member &&member);
|
void add_member(class_member &&member);
|
||||||
void add_method(class_method &&method);
|
void add_method(class_method &&method);
|
||||||
void add_parent(class_parent &&parent);
|
void add_parent(class_parent &&parent);
|
||||||
@@ -73,10 +79,6 @@ public:
|
|||||||
|
|
||||||
bool is_abstract() const;
|
bool is_abstract() const;
|
||||||
|
|
||||||
bool is_alias() const { return is_alias_; }
|
|
||||||
|
|
||||||
void is_alias(bool alias) { is_alias_ = alias; }
|
|
||||||
|
|
||||||
void find_relationships(
|
void find_relationships(
|
||||||
std::vector<std::pair<std::string, common::model::relationship_t>>
|
std::vector<std::pair<std::string, common::model::relationship_t>>
|
||||||
&nested_relationships);
|
&nested_relationships);
|
||||||
@@ -89,6 +91,7 @@ private:
|
|||||||
bool is_template_{false};
|
bool is_template_{false};
|
||||||
bool is_template_instantiation_{false};
|
bool is_template_instantiation_{false};
|
||||||
bool is_alias_{false};
|
bool is_alias_{false};
|
||||||
|
bool is_union_{false};
|
||||||
std::vector<class_member> members_;
|
std::vector<class_member> members_;
|
||||||
std::vector<class_method> methods_;
|
std::vector<class_method> methods_;
|
||||||
std::vector<class_parent> bases_;
|
std::vector<class_parent> bases_;
|
||||||
|
|||||||
@@ -310,6 +310,63 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool translation_unit_visitor::VisitRecordDecl(clang::RecordDecl *rec)
|
||||||
|
{
|
||||||
|
// Skip system headers
|
||||||
|
if (source_manager().isInSystemHeader(rec->getSourceRange().getBegin()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (clang::dyn_cast_or_null<clang::CXXRecordDecl>(rec))
|
||||||
|
// This is handled by VisitCXXRecordDecl()
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// It seems we are in a C (not C++) translation unit
|
||||||
|
if (!diagram().should_include(rec->getQualifiedNameAsString()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
LOG_DBG("= Visiting record declaration {} at {}",
|
||||||
|
rec->getQualifiedNameAsString(),
|
||||||
|
rec->getLocation().printToString(source_manager()));
|
||||||
|
|
||||||
|
auto record_ptr = create_record_declaration(rec);
|
||||||
|
|
||||||
|
if (!record_ptr)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const auto rec_id = record_ptr->id();
|
||||||
|
|
||||||
|
set_ast_local_id(rec->getID(), rec_id);
|
||||||
|
|
||||||
|
auto &record_model = diagram().get_class(rec_id).has_value()
|
||||||
|
? *diagram().get_class(rec_id).get()
|
||||||
|
: *record_ptr;
|
||||||
|
|
||||||
|
if (rec->isCompleteDefinition() && !record_model.complete()) {
|
||||||
|
process_record_members(rec, record_model);
|
||||||
|
record_model.complete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto id = record_model.id();
|
||||||
|
if (!rec->isCompleteDefinition()) {
|
||||||
|
forward_declarations_.emplace(id, std::move(record_ptr));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
forward_declarations_.erase(id);
|
||||||
|
|
||||||
|
if (diagram_.should_include(record_model)) {
|
||||||
|
LOG_DBG("Adding struct/union {} with id {}",
|
||||||
|
record_model.full_name(false), record_model.id());
|
||||||
|
|
||||||
|
diagram_.add_class(std::move(record_ptr));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG_DBG("Skipping struct/union {} with id {}", record_model.full_name(),
|
||||||
|
record_model.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
|
bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
|
||||||
{
|
{
|
||||||
// Skip system headers
|
// Skip system headers
|
||||||
@@ -383,6 +440,42 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<class_> translation_unit_visitor::create_record_declaration(
|
||||||
|
clang::RecordDecl *rec)
|
||||||
|
{
|
||||||
|
assert(rec != nullptr);
|
||||||
|
|
||||||
|
auto record_ptr{std::make_unique<class_>(config_.using_namespace())};
|
||||||
|
auto &record = *record_ptr;
|
||||||
|
|
||||||
|
auto qualified_name = rec->getQualifiedNameAsString();
|
||||||
|
|
||||||
|
if (!diagram().should_include(qualified_name))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
process_record_parent(rec, record, namespace_{});
|
||||||
|
|
||||||
|
if (!record.is_nested()) {
|
||||||
|
record.set_name(common::get_tag_name(*rec));
|
||||||
|
record.set_id(common::to_id(record.full_name(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
process_comment(*rec, record);
|
||||||
|
set_source_location(*rec, record);
|
||||||
|
|
||||||
|
const auto record_full_name = record_ptr->full_name(false);
|
||||||
|
|
||||||
|
record.is_struct(rec->isStruct());
|
||||||
|
record.is_union(rec->isUnion());
|
||||||
|
|
||||||
|
if (record.skip())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
record.set_style(record.style_spec());
|
||||||
|
|
||||||
|
return record_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<class_> translation_unit_visitor::create_class_declaration(
|
std::unique_ptr<class_> translation_unit_visitor::create_class_declaration(
|
||||||
clang::CXXRecordDecl *cls)
|
clang::CXXRecordDecl *cls)
|
||||||
{
|
{
|
||||||
@@ -392,14 +485,37 @@ std::unique_ptr<class_> translation_unit_visitor::create_class_declaration(
|
|||||||
auto &c = *c_ptr;
|
auto &c = *c_ptr;
|
||||||
|
|
||||||
// TODO: refactor to method get_qualified_name()
|
// TODO: refactor to method get_qualified_name()
|
||||||
auto qualified_name =
|
auto qualified_name = cls->getQualifiedNameAsString();
|
||||||
cls->getQualifiedNameAsString(); // common::get_qualified_name(*cls);
|
|
||||||
|
|
||||||
if (!diagram().should_include(qualified_name))
|
if (!diagram().should_include(qualified_name))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
auto ns = common::get_tag_namespace(*cls);
|
auto ns = common::get_tag_namespace(*cls);
|
||||||
|
|
||||||
|
process_record_parent(cls, c, ns);
|
||||||
|
|
||||||
|
if (!c.is_nested()) {
|
||||||
|
c.set_name(common::get_tag_name(*cls));
|
||||||
|
c.set_namespace(ns);
|
||||||
|
c.set_id(common::to_id(c.full_name(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
c.is_struct(cls->isStruct());
|
||||||
|
|
||||||
|
process_comment(*cls, c);
|
||||||
|
set_source_location(*cls, c);
|
||||||
|
|
||||||
|
if (c.skip())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
c.set_style(c.style_spec());
|
||||||
|
|
||||||
|
return c_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void translation_unit_visitor::process_record_parent(
|
||||||
|
clang::RecordDecl *cls, class_ &c, const namespace_ &ns)
|
||||||
|
{
|
||||||
const auto *parent = cls->getParent();
|
const auto *parent = cls->getParent();
|
||||||
|
|
||||||
std::optional<common::model::diagram_element::id_t> id_opt;
|
std::optional<common::model::diagram_element::id_t> id_opt;
|
||||||
@@ -435,7 +551,8 @@ std::unique_ptr<class_> translation_unit_visitor::create_class_declaration(
|
|||||||
assert(parent_class);
|
assert(parent_class);
|
||||||
|
|
||||||
c.set_namespace(ns);
|
c.set_namespace(ns);
|
||||||
if (cls->getNameAsString().empty()) {
|
const auto cls_name = cls->getNameAsString();
|
||||||
|
if (cls_name.empty()) {
|
||||||
// Nested structs can be anonymous
|
// Nested structs can be anonymous
|
||||||
if (anonymous_struct_relationships_.count(cls->getID()) > 0) {
|
if (anonymous_struct_relationships_.count(cls->getID()) > 0) {
|
||||||
const auto &[label, hint, access] =
|
const auto &[label, hint, access] =
|
||||||
@@ -467,23 +584,6 @@ std::unique_ptr<class_> translation_unit_visitor::create_class_declaration(
|
|||||||
|
|
||||||
c.nested(true);
|
c.nested(true);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
c.set_name(common::get_tag_name(*cls));
|
|
||||||
c.set_namespace(ns);
|
|
||||||
c.set_id(common::to_id(c.full_name(false)));
|
|
||||||
}
|
|
||||||
|
|
||||||
c.is_struct(cls->isStruct());
|
|
||||||
|
|
||||||
process_comment(*cls, c);
|
|
||||||
set_source_location(*cls, c);
|
|
||||||
|
|
||||||
if (c.skip())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
c.set_style(c.style_spec());
|
|
||||||
|
|
||||||
return c_ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void translation_unit_visitor::process_class_declaration(
|
void translation_unit_visitor::process_class_declaration(
|
||||||
@@ -715,6 +815,16 @@ void translation_unit_visitor::process_template_specialization_children(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void translation_unit_visitor::process_record_members(
|
||||||
|
const clang::RecordDecl *cls, class_ &c)
|
||||||
|
{
|
||||||
|
// Iterate over regular class fields
|
||||||
|
for (const auto *field : cls->fields()) {
|
||||||
|
if (field != nullptr)
|
||||||
|
process_field(*field, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void translation_unit_visitor::process_class_children(
|
void translation_unit_visitor::process_class_children(
|
||||||
const clang::CXXRecordDecl *cls, class_ &c)
|
const clang::CXXRecordDecl *cls, class_ &c)
|
||||||
{
|
{
|
||||||
@@ -1054,11 +1164,16 @@ bool translation_unit_visitor::find_relationships(const clang::QualType &type,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (type->getAsCXXRecordDecl()) {
|
||||||
const auto target_id = common::to_id(*type->getAsCXXRecordDecl());
|
const auto target_id = common::to_id(*type->getAsCXXRecordDecl());
|
||||||
relationships.emplace_back(target_id, relationship_hint);
|
relationships.emplace_back(target_id, relationship_hint);
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
const auto target_id = common::to_id(*type->getAsRecordDecl());
|
||||||
|
relationships.emplace_back(target_id, relationship_hint);
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -2092,6 +2207,9 @@ bool translation_unit_visitor::build_template_instantiation_add_base_classes(
|
|||||||
void translation_unit_visitor::process_field(
|
void translation_unit_visitor::process_field(
|
||||||
const clang::FieldDecl &field_declaration, class_ &c)
|
const clang::FieldDecl &field_declaration, class_ &c)
|
||||||
{
|
{
|
||||||
|
LOG_DBG(
|
||||||
|
"== Visiting record member {}", field_declaration.getNameAsString());
|
||||||
|
|
||||||
// Default hint for relationship is aggregation
|
// Default hint for relationship is aggregation
|
||||||
auto relationship_hint = relationship_t::kAggregation;
|
auto relationship_hint = relationship_t::kAggregation;
|
||||||
// If the first type of the template instantiation of this field type
|
// If the first type of the template instantiation of this field type
|
||||||
@@ -2108,6 +2226,7 @@ void translation_unit_visitor::process_field(
|
|||||||
|
|
||||||
auto field_type_str =
|
auto field_type_str =
|
||||||
common::to_string(field_type, field_declaration.getASTContext(), false);
|
common::to_string(field_type, field_declaration.getASTContext(), false);
|
||||||
|
|
||||||
ensure_lambda_type_is_relative(field_type_str);
|
ensure_lambda_type_is_relative(field_type_str);
|
||||||
|
|
||||||
class_member field{
|
class_member field{
|
||||||
@@ -2239,11 +2358,11 @@ void translation_unit_visitor::process_field(
|
|||||||
// Find relationship for the type if the type has not been added
|
// Find relationship for the type if the type has not been added
|
||||||
// as aggregation
|
// as aggregation
|
||||||
if (!template_instantiation_added_as_aggregation) {
|
if (!template_instantiation_added_as_aggregation) {
|
||||||
if ((field_type->getAsCXXRecordDecl() != nullptr) &&
|
if ((field_type->getAsRecordDecl() != nullptr) &&
|
||||||
field_type->getAsCXXRecordDecl()->getNameAsString().empty()) {
|
field_type->getAsRecordDecl()->getNameAsString().empty()) {
|
||||||
// Relationships to fields whose type is an anonymous nested
|
// Relationships to fields whose type is an anonymous nested
|
||||||
// struct have to be handled separately here
|
// struct have to be handled separately here
|
||||||
anonymous_struct_relationships_[field_type->getAsCXXRecordDecl()
|
anonymous_struct_relationships_[field_type->getAsRecordDecl()
|
||||||
->getID()] =
|
->getID()] =
|
||||||
std::make_tuple(
|
std::make_tuple(
|
||||||
field.name(), relationship_hint, field.access());
|
field.name(), relationship_hint, field.access());
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ public:
|
|||||||
|
|
||||||
virtual bool VisitNamespaceDecl(clang::NamespaceDecl *ns);
|
virtual bool VisitNamespaceDecl(clang::NamespaceDecl *ns);
|
||||||
|
|
||||||
|
virtual bool VisitRecordDecl(clang::RecordDecl *D);
|
||||||
|
|
||||||
virtual bool VisitCXXRecordDecl(clang::CXXRecordDecl *d);
|
virtual bool VisitCXXRecordDecl(clang::CXXRecordDecl *d);
|
||||||
|
|
||||||
virtual bool VisitEnumDecl(clang::EnumDecl *e);
|
virtual bool VisitEnumDecl(clang::EnumDecl *e);
|
||||||
@@ -109,6 +111,9 @@ private:
|
|||||||
std::unique_ptr<clanguml::class_diagram::model::class_>
|
std::unique_ptr<clanguml::class_diagram::model::class_>
|
||||||
create_class_declaration(clang::CXXRecordDecl *cls);
|
create_class_declaration(clang::CXXRecordDecl *cls);
|
||||||
|
|
||||||
|
std::unique_ptr<clanguml::class_diagram::model::class_>
|
||||||
|
create_record_declaration(clang::RecordDecl *rec);
|
||||||
|
|
||||||
void process_class_declaration(const clang::CXXRecordDecl &cls,
|
void process_class_declaration(const clang::CXXRecordDecl &cls,
|
||||||
clanguml::class_diagram::model::class_ &c);
|
clanguml::class_diagram::model::class_ &c);
|
||||||
|
|
||||||
@@ -118,6 +123,8 @@ private:
|
|||||||
void process_class_children(const clang::CXXRecordDecl *cls,
|
void process_class_children(const clang::CXXRecordDecl *cls,
|
||||||
clanguml::class_diagram::model::class_ &c);
|
clanguml::class_diagram::model::class_ &c);
|
||||||
|
|
||||||
|
void process_record_members(const clang::RecordDecl *cls, class_ &c);
|
||||||
|
|
||||||
std::unique_ptr<clanguml::class_diagram::model::class_>
|
std::unique_ptr<clanguml::class_diagram::model::class_>
|
||||||
process_template_specialization(
|
process_template_specialization(
|
||||||
clang::ClassTemplateSpecializationDecl *cls);
|
clang::ClassTemplateSpecializationDecl *cls);
|
||||||
@@ -224,6 +231,9 @@ private:
|
|||||||
|
|
||||||
void ensure_lambda_type_is_relative(std::string ¶meter_type) const;
|
void ensure_lambda_type_is_relative(std::string ¶meter_type) const;
|
||||||
|
|
||||||
|
void process_record_parent(
|
||||||
|
clang::RecordDecl *cls, class_ &c, const namespace_ &ns);
|
||||||
|
|
||||||
void process_function_parameter_find_relatinoships_in_autotype(
|
void process_function_parameter_find_relatinoships_in_autotype(
|
||||||
model::class_ &c, const clang::AutoType *atsp);
|
model::class_ &c, const clang::AutoType *atsp);
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,12 @@ std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx,
|
|||||||
// it has some default name
|
// it has some default name
|
||||||
if (result.empty())
|
if (result.empty())
|
||||||
result = "(anonymous)";
|
result = "(anonymous)";
|
||||||
else if (util::contains(result, "unnamed struct")) {
|
else if (util::contains(result, "unnamed struct") ||
|
||||||
|
util::contains(result, "unnamed union")) {
|
||||||
|
result = common::get_tag_name(*type->getAsTagDecl());
|
||||||
|
}
|
||||||
|
else if (util::contains(result, "anonymous struct") ||
|
||||||
|
util::contains(result, "anonymous union")) {
|
||||||
result = common::get_tag_name(*type->getAsTagDecl());
|
result = common::get_tag_name(*type->getAsTagDecl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ template <> id_t to_id(const clang::NamespaceDecl &declaration);
|
|||||||
|
|
||||||
template <> id_t to_id(const clang::CXXRecordDecl &declaration);
|
template <> id_t to_id(const clang::CXXRecordDecl &declaration);
|
||||||
|
|
||||||
|
template <> id_t to_id(const clang::RecordDecl &declaration);
|
||||||
|
|
||||||
template <> id_t to_id(const clang::EnumDecl &declaration);
|
template <> id_t to_id(const clang::EnumDecl &declaration);
|
||||||
|
|
||||||
template <> id_t to_id(const clang::TagDecl &declaration);
|
template <> id_t to_id(const clang::TagDecl &declaration);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
file(GLOB_RECURSE TEST_CASE_SOURCES t*/*.cc)
|
file(GLOB_RECURSE TEST_CASE_SOURCES t*/*.cc t*/*.c t*/src/*.c)
|
||||||
file(GLOB_RECURSE TEST_CASE_CONFIGS t*/.clang-uml)
|
file(GLOB_RECURSE TEST_CASE_CONFIGS t*/.clang-uml)
|
||||||
file(GLOB_RECURSE TEST_CONFIG_YMLS test_config_data/*.yml)
|
file(GLOB_RECURSE TEST_CONFIG_YMLS test_config_data/*.yml)
|
||||||
|
|
||||||
|
|||||||
8
tests/t00057/.clang-uml
Normal file
8
tests/t00057/.clang-uml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
compilation_database_dir: ..
|
||||||
|
output_directory: puml
|
||||||
|
diagrams:
|
||||||
|
t00057_class:
|
||||||
|
type: class
|
||||||
|
glob:
|
||||||
|
- ../../tests/t00057/t00057.c
|
||||||
|
- ../../tests/t00057/src/t00057_impl.c
|
||||||
3
tests/t00057/include/t00057.h
Normal file
3
tests/t00057/include/t00057.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct t00057_F;
|
||||||
5
tests/t00057/src/t00057_impl.c
Normal file
5
tests/t00057/src/t00057_impl.c
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#include "../include/t00057.h"
|
||||||
|
|
||||||
|
struct t00057_F {
|
||||||
|
int f1;
|
||||||
|
};
|
||||||
39
tests/t00057/t00057.c
Normal file
39
tests/t00057/t00057.c
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include "include/t00057.h"
|
||||||
|
|
||||||
|
struct t00057_A {
|
||||||
|
int a1;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct t00057_B {
|
||||||
|
int b1;
|
||||||
|
} t00057_B;
|
||||||
|
|
||||||
|
struct t00057_C {
|
||||||
|
int c1;
|
||||||
|
};
|
||||||
|
|
||||||
|
union t00057_D {
|
||||||
|
int d1;
|
||||||
|
float d2;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct t00057_E {
|
||||||
|
int e;
|
||||||
|
struct {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
} coordinates;
|
||||||
|
union {
|
||||||
|
int z;
|
||||||
|
double t;
|
||||||
|
} height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct t00057_R {
|
||||||
|
struct t00057_A a;
|
||||||
|
t00057_B b;
|
||||||
|
struct t00057_C *c;
|
||||||
|
union t00057_D d;
|
||||||
|
struct t00057_E *e;
|
||||||
|
struct t00057_F *f;
|
||||||
|
};
|
||||||
60
tests/t00057/test_case.h
Normal file
60
tests/t00057/test_case.h
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* tests/t00057/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("t00057", "[test-case][class]")
|
||||||
|
{
|
||||||
|
auto [config, db] = load_config("t00057");
|
||||||
|
|
||||||
|
auto diagram = config.diagrams["t00057_class"];
|
||||||
|
|
||||||
|
REQUIRE(diagram->name == "t00057_class");
|
||||||
|
|
||||||
|
auto model = generate_class_diagram(*db, diagram);
|
||||||
|
|
||||||
|
REQUIRE(model->name() == "t00057_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("t00057_A")));
|
||||||
|
REQUIRE_THAT(puml, IsClass(_A("t00057_B")));
|
||||||
|
REQUIRE_THAT(puml, IsClass(_A("t00057_C")));
|
||||||
|
REQUIRE_THAT(puml, IsUnion(_A("t00057_D")));
|
||||||
|
REQUIRE_THAT(puml, IsClass(_A("t00057_E")));
|
||||||
|
REQUIRE_THAT(puml, IsClass(_A("t00057_F")));
|
||||||
|
REQUIRE_THAT(puml, IsClass(_A("t00057_R")));
|
||||||
|
|
||||||
|
// Check if all relationships exist
|
||||||
|
REQUIRE_THAT(puml, IsAggregation(_A("t00057_R"), _A("t00057_A"), "+a"));
|
||||||
|
REQUIRE_THAT(puml, IsAggregation(_A("t00057_R"), _A("t00057_B"), "+b"));
|
||||||
|
REQUIRE_THAT(puml, IsAssociation(_A("t00057_R"), _A("t00057_C"), "+c"));
|
||||||
|
REQUIRE_THAT(puml, IsAggregation(_A("t00057_R"), _A("t00057_D"), "+d"));
|
||||||
|
REQUIRE_THAT(puml, IsAssociation(_A("t00057_R"), _A("t00057_E"), "+e"));
|
||||||
|
REQUIRE_THAT(puml, IsAssociation(_A("t00057_R"), _A("t00057_F"), "+f"));
|
||||||
|
REQUIRE_THAT(puml,
|
||||||
|
IsAggregation(
|
||||||
|
_A("t00057_E"), _A("t00057_E::(coordinates)"), "+coordinates"));
|
||||||
|
REQUIRE_THAT(puml,
|
||||||
|
IsAggregation(_A("t00057_E"), _A("t00057_E::(height)"), "+height"));
|
||||||
|
|
||||||
|
save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml);
|
||||||
|
}
|
||||||
@@ -255,6 +255,7 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t00053/test_case.h"
|
#include "t00053/test_case.h"
|
||||||
#include "t00054/test_case.h"
|
#include "t00054/test_case.h"
|
||||||
#include "t00055/test_case.h"
|
#include "t00055/test_case.h"
|
||||||
|
#include "t00057/test_case.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Sequence diagram tests
|
/// Sequence diagram tests
|
||||||
|
|||||||
@@ -266,6 +266,13 @@ ContainsMatcher IsClass(std::string const &str,
|
|||||||
return ContainsMatcher(CasedString("class " + str, caseSensitivity));
|
return ContainsMatcher(CasedString("class " + str, caseSensitivity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContainsMatcher IsUnion(std::string const &str,
|
||||||
|
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||||
|
{
|
||||||
|
return ContainsMatcher(
|
||||||
|
CasedString("class " + str + " <<union>>", caseSensitivity));
|
||||||
|
}
|
||||||
|
|
||||||
ContainsMatcher IsClassTemplate(std::string const &str,
|
ContainsMatcher IsClassTemplate(std::string const &str,
|
||||||
std::string const &tmplt,
|
std::string const &tmplt,
|
||||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||||
|
|||||||
@@ -162,6 +162,9 @@ test_cases:
|
|||||||
- name: t00055
|
- name: t00055
|
||||||
title: Test case for `row` and `column` layout hints
|
title: Test case for `row` and `column` layout hints
|
||||||
description:
|
description:
|
||||||
|
- name: t00057
|
||||||
|
title: Test case C99/C11 translation units with structs and unions
|
||||||
|
description:
|
||||||
Sequence diagrams:
|
Sequence diagrams:
|
||||||
- name: t20001
|
- name: t20001
|
||||||
title: Basic sequence diagram test case
|
title: Basic sequence diagram test case
|
||||||
|
|||||||
Reference in New Issue
Block a user