From 78d8655c6deef4c6bd5e9614ab0c947c8c109f2f Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Tue, 23 Feb 2021 23:31:35 +0100 Subject: [PATCH] Added basic class access and type specifiers generation --- src/cx/cursor.h | 2 ++ src/puml/class_diagram_generator.h | 23 ++++++++++++++- src/uml/class_diagram_model.h | 1 + src/uml/class_diagram_visitor.h | 38 ++++++++++++++++++------- tests/CMakeLists.txt | 2 ++ tests/t00003/.clanguml | 17 +++++++++++ tests/t00003/t00003.cc | 28 +++++++++++++++++++ tests/test_cases.cc | 45 ++++++++++++++++++++++++++++++ 8 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 tests/t00003/.clanguml create mode 100644 tests/t00003/t00003.cc diff --git a/src/cx/cursor.h b/src/cx/cursor.h index 33f3f3c8..46b9f772 100644 --- a/src/cx/cursor.h +++ b/src/cx/cursor.h @@ -137,6 +137,8 @@ public: return clang_CXXMethod_isVirtual(m_cursor); } + bool is_method_static() const { return clang_CXXMethod_isStatic(m_cursor); } + bool is_method_const() const { return clang_CXXMethod_isConst(m_cursor); } bool is_method_pure_virtual() const diff --git a/src/puml/class_diagram_generator.h b/src/puml/class_diagram_generator.h index 8d0e82d2..94eda9a2 100644 --- a/src/puml/class_diagram_generator.h +++ b/src/puml/class_diagram_generator.h @@ -101,18 +101,39 @@ public: ostr << c.name << " {" << std::endl; + // + // Process methods + // for (const auto &m : c.methods) { if (m.is_pure_virtual) ostr << "{abstract} "; - ostr << to_string(m.scope) << m.type << " " << m.name + "()"; + if (m.is_static) + ostr << "{static} "; + + std::string type{}; + if (m.type != "void") + type = m.type; + + ostr << to_string(m.scope) << type << " " << m.name + "()"; + + if (m.is_const) + ostr << " const"; + + assert(!(m.is_pure_virtual && m.is_defaulted)); if (m.is_pure_virtual) ostr << " = 0"; + if (m.is_defaulted) + ostr << " = default"; + ostr << std::endl; } + // + // Process members + // for (const auto &m : c.members) { ostr << to_string(m.scope) << m.type << " " << m.name << std::endl; } diff --git a/src/uml/class_diagram_model.h b/src/uml/class_diagram_model.h index d55b6d42..1431a01c 100644 --- a/src/uml/class_diagram_model.h +++ b/src/uml/class_diagram_model.h @@ -67,6 +67,7 @@ struct class_method : public class_element { bool is_virtual{false}; bool is_const{false}; bool is_defaulted{false}; + bool is_static{false}; }; struct class_parent { diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index da9f3419..4d9ea354 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -75,7 +75,7 @@ enum CXChildVisitResult visit_if_cursor_valid( cx::cursor cursor, std::function f) { enum CXChildVisitResult ret = CXChildVisit_Break; - if (cursor.is_definition()) { + if (cursor.is_definition() || cursor.is_declaration()) { if (!cursor.spelling().empty()) { f(cursor); ret = CXChildVisit_Continue; @@ -84,21 +84,32 @@ enum CXChildVisitResult visit_if_cursor_valid( ret = CXChildVisit_Recurse; } } - else if (cursor.is_declaration()) { - if (cursor.is_method_pure_virtual()) { - f(cursor); - ret = CXChildVisit_Continue; - } - else { - ret = CXChildVisit_Recurse; - } - } else { ret = CXChildVisit_Continue; } return ret; } +scope_t cx_access_specifier_to_scope(CX_CXXAccessSpecifier as) +{ + scope_t res = scope_t::kPublic; + switch (as) { + case CX_CXXAccessSpecifier::CX_CXXPublic: + res = scope_t::kPublic; + break; + case CX_CXXAccessSpecifier::CX_CXXPrivate: + res = scope_t::kPrivate; + break; + case CX_CXXAccessSpecifier::CX_CXXProtected: + res = scope_t::kProtected; + break; + default: + break; + } + + return res; +} + static enum CXChildVisitResult enum_visitor( CXCursor cx_cursor, CXCursor cx_parent, CXClientData client_data) { @@ -144,6 +155,8 @@ static enum CXChildVisitResult class_visitor( c->name, cursor_name_str, cursor.kind()); enum CXChildVisitResult ret = CXChildVisit_Break; + bool is_constructor{false}; + bool is_destructor{false}; switch (cursor.kind()) { case CXCursor_CXXMethod: case CXCursor_Constructor: @@ -157,6 +170,9 @@ static enum CXChildVisitResult class_visitor( m.is_virtual = cursor.is_method_virtual(); m.is_const = cursor.is_method_const(); m.is_defaulted = cursor.is_method_defaulted(); + m.is_static = cursor.is_method_static(); + m.scope = + cx_access_specifier_to_scope(cursor.cxxaccess_specifier()); spdlog::info("Adding method {} {}::{}()", m.type, c->name, cursor.spelling()); @@ -172,6 +188,8 @@ static enum CXChildVisitResult class_visitor( class_member m; m.name = cursor.spelling(); m.type = cursor.type().spelling(); + m.scope = + cx_access_specifier_to_scope(cursor.cxxaccess_specifier()); spdlog::info("Adding member {} {}::{}", m.type, c->name, cursor.spelling()); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6a13a1df..5ec49275 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,7 @@ set(CLANG_UML_TEST_CASES_SRC test_cases.cc t00001/t00001.cc t00002/t00002.cc + t00003/t00003.cc ) set(CLANG_UML_TEST_CASES_HEADER catch.h @@ -25,5 +26,6 @@ target_link_libraries(test_cases configure_file(t00001/.clanguml t00001/.clanguml COPYONLY) configure_file(t00002/.clanguml t00002/.clanguml COPYONLY) +configure_file(t00003/.clanguml t00003/.clanguml COPYONLY) add_test(NAME test_cases COMMAND test_cases) diff --git a/tests/t00003/.clanguml b/tests/t00003/.clanguml new file mode 100644 index 00000000..6bd41e8d --- /dev/null +++ b/tests/t00003/.clanguml @@ -0,0 +1,17 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00003_class: + type: class + glob: + - ../../tests/t00003/t00003.cc + using_namespace: + - clanguml::t00003 + include: + namespaces: + - clanguml::t00003 + plantuml: + before: + - "' t00003 test class members" + after: + - 'note over "A": A class' diff --git a/tests/t00003/t00003.cc b/tests/t00003/t00003.cc new file mode 100644 index 00000000..a30f6aab --- /dev/null +++ b/tests/t00003/t00003.cc @@ -0,0 +1,28 @@ +namespace clanguml { +namespace t00003 { + +class A { +public: + A() = default; + A(A &&) = default; + A(const A &) = default; + virtual ~A() = default; + + void basic_method() {} + static int static_method() { return 0; } + void const_method() const {} + + int public_member; + +protected: + void protected_method() {} + + int protected_member; + +private: + void private_method() {} + + int private_member; +}; +} +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 0489bae8..a43798b0 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -160,3 +160,48 @@ TEST_CASE("Test t00002", "[unit-test]") save_puml( "./" + config.output_directory + "/" + diagram->name + ".puml", puml); } + +TEST_CASE("Test t00003", "[unit-test]") +{ + spdlog::set_level(spdlog::level::debug); + + auto [config, db] = load_config("t00003"); + + auto diagram = config.diagrams["t00003_class"]; + + REQUIRE(diagram->name == "t00003_class"); + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t00003"})); + + REQUIRE(diagram->exclude.namespaces.size() == 0); + + REQUIRE(diagram->should_include("clanguml::t00003::A")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name == "t00003_class"); + + auto puml = generate_class_puml(diagram, model); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + REQUIRE_THAT(puml, Contains("class A")); + + REQUIRE_THAT(puml, Contains("+ A() = default")); + REQUIRE_THAT(puml, Contains("+ A() = default")); + REQUIRE_THAT(puml, Contains("+ A() = default")); + REQUIRE_THAT(puml, Contains("+ ~A() = default")); + REQUIRE_THAT(puml, Contains("+ basic_method()")); + REQUIRE_THAT(puml, Contains("{static} +int static_method()")); + REQUIRE_THAT(puml, Contains("+ const_method() const")); + REQUIRE_THAT(puml, Contains("# protected_method()")); + REQUIRE_THAT(puml, Contains("- private_method()")); + REQUIRE_THAT(puml, Contains("+int public_member")); + REQUIRE_THAT(puml, Contains("#int protected_member")); + REQUIRE_THAT(puml, Contains("-int private_member")); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +}