diff --git a/CMakeLists.txt b/CMakeLists.txt index d64cdc28f..2d6fd3a22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ endif(CODE_COVERAGE) # option(ADDRESS_SANITIZER "" OFF) if(ADDRESS_SANITIZER) + message(STATUS "Enabling address sanitizer") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ -fno-omit-frame-pointer \ diff --git a/docs/diagram_filters.md b/docs/diagram_filters.md index 8f9f8704a..76f7d28df 100644 --- a/docs/diagram_filters.md +++ b/docs/diagram_filters.md @@ -113,7 +113,7 @@ Allows to include or exclude entities from specific C++20 module. ## elements Allows to directly include or exclude specific entities from the diagrams, for instance to exclude a specific class -from an included namespace: +from an included namespace simply add the following exclude block: ```yaml include: @@ -124,6 +124,43 @@ from an included namespace: - ns1::ns2::MyClass ``` +`elements` filter allows also for more fine-grained control over the diagram +contents. Instead of a literal value in the filter like above, the +filter can also specify to what type of element the filter applies. +For instance the following filter: + +```yaml + include: + namespaces: + - ns1::ns2 + exclude: + elements: + - ns1::ns2::MyClass + - type: method + name: ns1::ns2::OtherClass::log +``` + +will, in addition to excluding `ns1::ns2::MyClass`, also exclude `ns1::ns2::OtherClass::log` method. + +Another useful example, for instance to ignore all setters and getters in the +code base, the method elements filter can be specified with a regex e.g.: + +```yaml + include: + namespaces: + - ns1::ns2 + exclude: + elements: + - type: method + name: + r: '.*::(get|set).*' +``` + +The `type` in this filter can be one of the following: +`any`, `function`, `function_template`, `class`, `enum`, `method`, `member`, +`concept`, `package`, `objc_method`, `objc_member`, `objc_protocol`, +`objc_category`, `objc_interface`. + ## element_types Allows to include or exclude elements of specific type from the diagram, for instance diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index d4bb11085..f91809def 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -13,6 +13,7 @@ * [Schema validation error is thrown, but the configuration file is correct](#schema-validation-error-is-thrown-but-the-configuration-file-is-correct) * ["fatal error: 'stddef.h' file not found"](#fatal-error-stddefh-file-not-found) * ["error: unknown pragma ignored"](#error-unknown-pragma-ignored) + * ["bus error" on Apple Silicon macos](#bus-error-on-apple-silicon-macos) * [Class diagrams](#class-diagrams) * [How can I generate class diagram of my entire project](#how-can-i-generate-class-diagram-of-my-entire-project) * [Cannot generate classes for 'std' namespace](#cannot-generate-classes-for-std-namespace) @@ -306,6 +307,17 @@ add_compile_flags: - -Wno-unknown-pragmas ``` +### "bus error" on Apple Silicon macos +On Apple Silicon macos, `clang-uml` must be linked with LLVM libunwind libraries +for proper exception handling, otherwise whenever an exception is thrown +somewhere within `clang-uml` the application is terminated with `bus error`. + +In order to mitigate this error when building `clang-uml` from sources, the +following CMake option should be enabled during build: +```bash +-DCMAKE_EXE_LINKER_FLAGS="-L/opt/homebrew/opt/llvm/lib/c++ -Wl,-rpath,/opt/homebrew/opt/llvm/lib/c++" +``` + ## Class diagrams ### How can I generate class diagram of my entire project diff --git a/src/class_diagram/model/class_element.cc b/src/class_diagram/model/class_element.cc index 3a6385897..ce516cb7a 100644 --- a/src/class_diagram/model/class_element.cc +++ b/src/class_diagram/model/class_element.cc @@ -23,7 +23,7 @@ namespace clanguml::class_diagram::model { class_element::class_element( - common::model::access_t access, std::string name, std::string type) + const common::model::access_t access, std::string name, std::string type) : access_{access} , name_{std::move(name)} , type_{std::move(type)} @@ -48,4 +48,12 @@ inja::json class_element::context() const ctx["access"] = to_string(access()); return ctx; } + +void class_element::set_qualified_name(const std::string &qn) +{ + qualified_name_ = qn; +} + +std::string class_element::qualified_name() const { return qualified_name_; } + } // namespace clanguml::class_diagram::model diff --git a/src/class_diagram/model/class_element.h b/src/class_diagram/model/class_element.h index 1bc4fa820..c0f53e1f3 100644 --- a/src/class_diagram/model/class_element.h +++ b/src/class_diagram/model/class_element.h @@ -33,7 +33,7 @@ class class_element : public common::model::decorated_element, public common::model::source_location { public: class_element( - common::model::access_t scope, std::string name, std::string type); + common::model::access_t access, std::string name, std::string type); ~class_element() override = default; @@ -79,9 +79,23 @@ class class_element : public common::model::decorated_element, */ virtual inja::json context() const; + /** + * @brief Set class elements qualified name. + * + * This method sets the fully qualified path of the class element, + * including namespace and parent class name. + */ + void set_qualified_name(const std::string &qn); + + /** + * @brief get fully qualified name of the class element. + */ + std::string qualified_name() const; + private: common::model::access_t access_; std::string name_; + std::string qualified_name_; std::string type_; }; diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 87066af6e..059b44bf9 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -1269,6 +1269,8 @@ void translation_unit_visitor::process_objc_ivar( common::access_specifier_to_access_t(ivar.getAccessControl()), field_name, field_type_str}; + field.set_qualified_name(ivar.getQualifiedNameAsString()); + process_comment(ivar, field); set_source_location(ivar, field); @@ -1518,6 +1520,8 @@ void translation_unit_visitor::process_method( util::trim(method_name), config().simplify_template_type(method_return_type)}; + method.set_qualified_name(mf.getQualifiedNameAsString()); + process_method_properties(mf, c, method_name, method); process_comment(mf, method); @@ -1613,6 +1617,8 @@ void translation_unit_visitor::process_objc_method( objc_method method{common::access_specifier_to_access_t(mf.getAccess()), util::trim(mf.getNameAsString()), method_return_type}; + method.set_qualified_name(mf.getQualifiedNameAsString()); + process_comment(mf, method); // Register the source location of the field declaration @@ -2214,6 +2220,8 @@ void translation_unit_visitor::process_field( common::access_specifier_to_access_t(field_declaration.getAccess()), field_name, config().simplify_template_type(field_type_str)}; + field.set_qualified_name(field_declaration.getQualifiedNameAsString()); + // Parse the field comment process_comment(field_declaration, field); // Register the source location of the field declaration @@ -2391,7 +2399,9 @@ void translation_unit_visitor::process_field( } } - c.add_member(std::move(field)); + if (diagram().should_include(field)) { + c.add_member(std::move(field)); + } } void translation_unit_visitor::add_incomplete_forward_declarations() diff --git a/src/common/model/filters/diagram_filter.cc b/src/common/model/filters/diagram_filter.cc index 6df729589..50aaa2f21 100644 --- a/src/common/model/filters/diagram_filter.cc +++ b/src/common/model/filters/diagram_filter.cc @@ -449,7 +449,7 @@ tvl::value_t modules_filter::match( } element_filter::element_filter( - filter_t type, std::vector elements) + filter_t type, std::vector elements) : filter_visitor{type} , elements_{std::move(elements)} { @@ -463,8 +463,70 @@ tvl::value_t element_filter::match(const diagram &d, const element &e) const return tvl::any_of( elements_.begin(), elements_.end(), [&e](const auto &el) { - return ((el == e.full_name(false)) || - (el == fmt::format("::{}", e.full_name(false)))); + // First check if elements type matches the filter + if ((el.type != config::element_filter_t::filtered_type::any) && + (config::to_string(el.type) != e.type_name())) { + return false; + } + + return ((el.name == e.full_name(false)) || + (el.name == fmt::format("::{}", e.full_name(false)))); + }); +} + +tvl::value_t element_filter::match( + const diagram & /*d*/, const class_diagram::model::class_method &m) const +{ + return tvl::any_of(elements_.begin(), elements_.end(), + [&m](const auto &ef) -> tvl::value_t { + // Apply this filter only if it had `method` type, do not apply + // `any` filters to methods for backward compatibility + if (ef.type != config::element_filter_t::filtered_type::method) + return {}; + + return ef.name == m.qualified_name(); + }); +} + +tvl::value_t element_filter::match( + const diagram & /*d*/, const class_diagram::model::class_member &m) const +{ + return tvl::any_of(elements_.begin(), elements_.end(), + [&m](const auto &ef) -> tvl::value_t { + // Apply this filter only if it had `member` type, do not apply + // `any` filters to methods for backward compatibility + if (ef.type != config::element_filter_t::filtered_type::member) + return {}; + + return ef.name == m.qualified_name(); + }); +} + +tvl::value_t element_filter::match( + const diagram & /*d*/, const class_diagram::model::objc_method &m) const +{ + return tvl::any_of(elements_.begin(), elements_.end(), + [&m](const auto &ef) -> tvl::value_t { + // Apply this filter only if it had `objc_method` type, do not apply + // `any` filters to methods for backward compatibility + if (ef.type != config::element_filter_t::filtered_type::objc_method) + return {}; + + return ef.name == m.qualified_name(); + }); +} + +tvl::value_t element_filter::match( + const diagram & /*d*/, const class_diagram::model::objc_member &m) const +{ + return tvl::any_of(elements_.begin(), elements_.end(), + [&m](const auto &ef) -> tvl::value_t { + // Apply this filter only if it had `method` type, do not apply + // `any` filters to methods for backward compatibility + if (ef.type != config::element_filter_t::filtered_type::objc_member) + return {}; + + return ef.name == m.qualified_name(); }); } @@ -481,19 +543,39 @@ tvl::value_t element_filter::match( dynamic_cast(d); return tvl::any_of(elements_.begin(), elements_.end(), [&sequence_model, &p](const auto &el) { - if (p.type_name() == "method") { + // First check if elements type matches the filter + if (el.type != config::element_filter_t::filtered_type::any && + config::to_string(el.type) != p.type_name()) { + return false; + } + if (p.type_name() == "method") { const auto &m = dynamic_cast(p); const auto class_id = m.class_id(); const auto &class_participant = sequence_model.get_participant(class_id) .value(); - return el == p.full_name(false) || - el == class_participant.full_name(false); + return (el.name == p.name_and_ns()) || + (el.name == p.full_name(false)) || + (el.name == class_participant.full_name(false)); + } + + if (p.type_name() == "objc_method") { + const auto &m = + dynamic_cast( + p); + const auto class_id = m.class_id(); + const auto &class_participant = + sequence_model.get_participant(class_id) + .value(); + + return (el.name == p.name_and_ns()) || + (el.name == p.full_name(false)) || + (el.name == class_participant.full_name(false)); } - return el == p.full_name(false); + return el.name == p.full_name(false); }); } @@ -504,8 +586,9 @@ element_type_filter::element_type_filter( { } -tvl::value_t element_type_filter::match( - const diagram & /*d*/, const element &e) const +tvl::value_t element_type_filter::match(const diagram & /*d*/ + , + const element &e) const { return tvl::any_of(element_types_.begin(), element_types_.end(), [&e](const auto &element_type) { @@ -780,7 +863,8 @@ void context_filter::initialize_effective_context( auto &effective_context = effective_contexts_[idx]; - // First add to effective context all elements matching context_ patterns + // First add to effective context all elements matching context_ + // patterns const auto &context_cfg = context_.at(idx); const auto &context_matches = dynamic_cast(d) @@ -1084,4 +1168,4 @@ bool diagram_filter::should_include(const std::string &name) const return should_include(ns, n); } -} // namespace clanguml::common::model +} // namespace clanguml::common::model \ No newline at end of file diff --git a/src/common/model/filters/diagram_filter.h b/src/common/model/filters/diagram_filter.h index b13dea876..e9d494f38 100644 --- a/src/common/model/filters/diagram_filter.h +++ b/src/common/model/filters/diagram_filter.h @@ -265,17 +265,29 @@ struct modules_filter : public filter_visitor { */ struct element_filter : public filter_visitor { element_filter( - filter_t type, std::vector elements); + filter_t type, std::vector elements); ~element_filter() override = default; tvl::value_t match(const diagram &d, const element &e) const override; + tvl::value_t match(const diagram &d, + const class_diagram::model::class_method &m) const override; + + tvl::value_t match(const diagram &d, + const class_diagram::model::class_member &m) const override; + + tvl::value_t match(const diagram &d, + const class_diagram::model::objc_method &m) const override; + + tvl::value_t match(const diagram &d, + const class_diagram::model::objc_member &m) const override; + tvl::value_t match(const diagram &d, const sequence_diagram::model::participant &p) const override; private: - std::vector elements_; + std::vector elements_; }; /** diff --git a/src/config/config.cc b/src/config/config.cc index d20a96441..74c070c43 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -47,6 +47,43 @@ std::string to_string(const hint_t t) } } +std::string to_string(element_filter_t::filtered_type ft) +{ + switch (ft) { + case element_filter_t::filtered_type::any: + return "any"; + case element_filter_t::filtered_type::class_: + return "class"; + case element_filter_t::filtered_type::function: + return "function"; + case element_filter_t::filtered_type::method: + return "method"; + case element_filter_t::filtered_type::member: + return "member"; + case element_filter_t::filtered_type::enum_: + return "enum"; + case element_filter_t::filtered_type::concept_: + return "concept"; + case element_filter_t::filtered_type::package: + return "package"; + case element_filter_t::filtered_type::function_template: + return "function_template"; + case element_filter_t::filtered_type::objc_method: + return "objc_method"; + case element_filter_t::filtered_type::objc_member: + return "objc_member"; + case element_filter_t::filtered_type::objc_protocol: + return "objc_protocol"; + case element_filter_t::filtered_type::objc_category: + return "objc_category"; + case element_filter_t::filtered_type::objc_interface: + return "objc_interface"; + default: + assert(false); + return ""; + } +} + std::string to_string(const method_arguments ma) { switch (ma) { diff --git a/src/config/config.h b/src/config/config.h index 0e15ce19a..347a0769c 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -46,6 +46,30 @@ struct runtime_config; */ namespace config { +struct element_filter_t { + enum class filtered_type { + any, + function, + function_template, + class_, + enum_, + method, + member, + concept_, + package, + objc_method, + objc_member, + objc_protocol, + objc_category, + objc_interface + }; + + filtered_type type{filtered_type::any}; + common::string_or_regex name; +}; + +std::string to_string(element_filter_t::filtered_type ft); + /*! Select how the method arguments should be rendered */ enum class method_arguments { full, /*! Full */ @@ -250,7 +274,7 @@ struct filter { * - r: ".*Enum.*" * ``` */ - std::vector elements; + std::vector elements; /*! @brief Element types filter * @@ -850,6 +874,8 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const package_diagram &c); YAML::Emitter &operator<<(YAML::Emitter &out, const layout_hint &c); +YAML::Emitter &operator<<(YAML::Emitter &out, const element_filter_t &ef); + #ifdef _MSC_VER YAML::Emitter &operator<<(YAML::Emitter &out, const std::filesystem::path &p); @@ -886,5 +912,4 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const access_t &r); YAML::Emitter &operator<<(YAML::Emitter &out, const diagram_t &d); /** @} */ // end of yaml_emitters } // namespace common::model - } // namespace clanguml diff --git a/src/config/schema.h b/src/config/schema.h index 995810c3e..f389ab7ba 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -139,10 +139,16 @@ const std::string schema_str = R"( context_filter_t: - regex_or_string_t - context_filter_match_t + element_typed_filter_t: + type: string + name: regex_or_string_t + element_filter_t: + - regex_or_string_t + - element_typed_filter_t filter_t: namespaces: !optional [regex_or_string_t] modules: !optional [regex_or_string_t] - elements: !optional [regex_or_string_t] + elements: !optional [element_filter_t] element_types: !optional [element_types_filter_t] relationships: !optional [relationship_filter_t] access: !optional [access_filter_t] diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index e86feb7d0..1810834db 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -37,6 +37,7 @@ using clanguml::config::config; using clanguml::config::context_config; using clanguml::config::context_direction_t; using clanguml::config::diagram_template; +using clanguml::config::element_filter_t; using clanguml::config::filter; using clanguml::config::generate_links_config; using clanguml::config::git_config; @@ -530,6 +531,66 @@ template <> struct convert { } }; +template <> struct convert { + static bool decode(const Node &node, element_filter_t &rhs) + { + using namespace std::string_literals; + if (node.IsMap()) { + if (has_key(node, "r")) { + rhs.type = element_filter_t::filtered_type::any; + auto pattern = node["r"].as(); + auto rx = std::regex(pattern); + rhs.name = string_or_regex(std::move(rx), std::move(pattern)); + } + else if (has_key(node, "type")) { + rhs.type = element_filter_t::filtered_type::any; + if (node["type"].as() == "class") + rhs.type = element_filter_t::filtered_type::class_; + else if (node["type"].as() == "enum") + rhs.type = element_filter_t::filtered_type::enum_; + else if (node["type"].as() == "function") + rhs.type = element_filter_t::filtered_type::function; + else if (node["type"].as() == "method") + rhs.type = element_filter_t::filtered_type::method; + else if (node["type"].as() == "member") + rhs.type = element_filter_t::filtered_type::member; + else if (node["type"].as() == "concept") + rhs.type = element_filter_t::filtered_type::concept_; + else if (node["type"].as() == "package") + rhs.type = element_filter_t::filtered_type::package; + else if (node["type"].as() == "function_template") + rhs.type = + element_filter_t::filtered_type::function_template; + else if (node["type"].as() == "objc_method") + rhs.type = element_filter_t::filtered_type::objc_method; + else if (node["type"].as() == "objc_member") + rhs.type = element_filter_t::filtered_type::objc_member; + else if (node["type"].as() == "objc_protocol") + rhs.type = element_filter_t::filtered_type::objc_protocol; + else if (node["type"].as() == "objc_category") + rhs.type = element_filter_t::filtered_type::objc_category; + else if (node["type"].as() == "objc_interface") + rhs.type = element_filter_t::filtered_type::objc_interface; + auto name = node["name"]; + if (name.IsMap() && has_key(name, "r")) { + auto pattern = name["r"].as(); + auto rx = std::regex(pattern); + rhs.name = + string_or_regex(std::move(rx), std::move(pattern)); + } + else + rhs.name = name.as(); + } + } + else { + rhs.type = element_filter_t::filtered_type::any; + rhs.name = string_or_regex{node.as()}; + } + + return true; + } +}; + // // filter Yaml decoder // diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index 0e0e66de7..1ae1779e3 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -413,4 +413,13 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const package_diagram &c) return out; } +YAML::Emitter &operator<<(YAML::Emitter &out, const element_filter_t &ef) +{ + out << YAML::BeginMap; + out << YAML::Key << "type" << YAML::Value << to_string(ef.type); + out << YAML::Key << "name" << YAML::Value << ef.name; + out << YAML::EndMap; + + return out; +} } // namespace clanguml::config \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 292a6f7ec..ce88ebb92 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,6 +26,7 @@ set(TEST_CASES_REQUIRING_OBJC t00084 t00085 t00086 + t00088 t20057 t20058 t30016 diff --git a/tests/t00087/.clang-uml b/tests/t00087/.clang-uml new file mode 100644 index 000000000..0d8d85308 --- /dev/null +++ b/tests/t00087/.clang-uml @@ -0,0 +1,22 @@ +diagrams: + t00087_class: + type: class + glob: + - t00087.cc + using_namespace: clanguml::t00087 + include: + namespaces: + - clanguml::t00087 + elements: + - type: class + name: + r: '.*Foo.*' + - "clanguml::t00087::Bar" + exclude: + elements: + - type: method + name: + r: '.*FooClass::(get|set).*' + - type: member + name: 'clanguml::t00087::FooClass::pImpl_' + - bar \ No newline at end of file diff --git a/tests/t00087/t00087.cc b/tests/t00087/t00087.cc new file mode 100644 index 000000000..a0dd67115 --- /dev/null +++ b/tests/t00087/t00087.cc @@ -0,0 +1,27 @@ +namespace clanguml { +namespace t00087 { + +class FooClass { + int foo_; + + void *pImpl_; + +public: + FooClass() { } + + int getFoo() const { return foo_; } + + void setFoo(int f) { foo_ = f; } + + void foo() { } + + void bar() { } +}; + +enum FooEnum {}; + +template class Bar { + T bar; +}; +} +} \ No newline at end of file diff --git a/tests/t00087/test_case.h b/tests/t00087/test_case.h new file mode 100644 index 000000000..c3956119e --- /dev/null +++ b/tests/t00087/test_case.h @@ -0,0 +1,40 @@ +/** + * tests/t00087/test_case.h + * + * Copyright (c) 2021-2024 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("t00087") +{ + using namespace clanguml::test; + using namespace std::string_literals; + + auto [config, db, diagram, model] = + CHECK_CLASS_MODEL("t00087", "t00087_class"); + + CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) { + REQUIRE(IsClass(src, "FooClass")); + REQUIRE(!IsEnum(src, "FooEnum")); + REQUIRE(IsClassTemplate(src, "Bar")); + + REQUIRE(!IsMethod(src, "FooClass", "getFoo")); + REQUIRE(!IsMethod(src, "FooClass", "setFoo", "int")); + REQUIRE(IsMethod(src, "FooClass", "foo")); + REQUIRE(IsMethod(src, "FooClass", "bar")); + + REQUIRE(IsField(src, "FooClass", "foo_", "int")); + REQUIRE(!IsField(src, "FooClass", "pImpl_", "void *")); + }); +} \ No newline at end of file diff --git a/tests/t00088/.clang-uml b/tests/t00088/.clang-uml new file mode 100644 index 000000000..fb2ee727d --- /dev/null +++ b/tests/t00088/.clang-uml @@ -0,0 +1,22 @@ +diagrams: + t00088_class: + type: class + glob: + - t00088.m + include: + paths: + - . + elements: + - type: objc_interface + name: It00088_Foo + exclude: + elements: + - type: objc_method + name: + r: 'It00088_Foo::bar.*' + - type: objc_method + name: 'It00088_Foo::baz:with:' + - type: objc_member + name: 'It00088_Foo::_barMember' + - type: objc_protocol + name: Pr00088 \ No newline at end of file diff --git a/tests/t00088/t00088.h b/tests/t00088/t00088.h new file mode 100644 index 000000000..cbc756073 --- /dev/null +++ b/tests/t00088/t00088.h @@ -0,0 +1,23 @@ +#import + +struct It00088_Foo { }; + +@protocol Pr00088 + +@end + +@interface It00088_Foo : NSObject { + int _fooMember; + int _barMember; +} + ++ (void)foo; ++ (void)bar; ++ (void)baz:(int)b with:(int)c; + +@end + +@interface It00088_Bar : NSObject { +} + +@end \ No newline at end of file diff --git a/tests/t00088/t00088.m b/tests/t00088/t00088.m new file mode 100644 index 000000000..8659c0bd7 --- /dev/null +++ b/tests/t00088/t00088.m @@ -0,0 +1,16 @@ +#include "t00088.h" + +@implementation It00088_Foo + ++ (void)foo +{ +} + ++ (void)bar +{ +} + ++ (void)baz:(int)b with:(int)c +{ +} +@end \ No newline at end of file diff --git a/tests/t00088/test_case.h b/tests/t00088/test_case.h new file mode 100644 index 000000000..5c96781d8 --- /dev/null +++ b/tests/t00088/test_case.h @@ -0,0 +1,36 @@ +/** + * tests/t00088/test_case.h + * + * Copyright (c) 2021-2024 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("t00088") +{ + using namespace clanguml::test; + using namespace std::string_literals; + + auto [config, db, diagram, model] = + CHECK_CLASS_MODEL("t00088", "t00088_class"); + + CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) { + REQUIRE(IsObjCInterface(src, "It00088_Foo")); + REQUIRE(!IsObjCInterface(src, "It00088_Bar")); + REQUIRE(!IsObjCProtocol(src, "Pr00088")); + + REQUIRE(IsMethod(src, "It00088_Foo", "foo")); + REQUIRE(!IsMethod(src, "It00088_Foo", "bar")); + REQUIRE(!IsMethod(src, "It00088_Foo", "baz:with:")); + }); +} \ No newline at end of file diff --git a/tests/t20001/.clang-uml b/tests/t20001/.clang-uml index 078067e90..070ada347 100644 --- a/tests/t20001/.clang-uml +++ b/tests/t20001/.clang-uml @@ -19,9 +19,9 @@ diagrams: before: - "' t20001 test diagram of type {{ diagram.type }}" after: - - '{% set e=element("clanguml::t20001::tmain()") %} note over {{ e.alias) }}: Main test function' + - '{% set e=element("clanguml::t20001::tmain()") %} note over {{ e.alias }}: Main test function' mermaid: before: - "%% t20001 test diagram of type {{ diagram.type }}" after: - - '{% set e=element("clanguml::t20001::tmain()") %} Note over {{ e.alias) }}: Main test function' + - '{% set e=element("clanguml::t20001::tmain()") %} Note over {{ e.alias }}: Main test function' diff --git a/tests/t20043/.clang-uml b/tests/t20043/.clang-uml index 7b22cb2e2..d36a66461 100644 --- a/tests/t20043/.clang-uml +++ b/tests/t20043/.clang-uml @@ -10,6 +10,8 @@ diagrams: elements: - clanguml::t20043::B - clanguml::t20043::F + - type: method + name: clanguml::t20043::C::log_c namespaces: - clanguml::t20043::detail using_namespace: clanguml::t20043 diff --git a/tests/t20043/t20043.cc b/tests/t20043/t20043.cc index ad1baa161..80214b5b6 100644 --- a/tests/t20043/t20043.cc +++ b/tests/t20043/t20043.cc @@ -1,7 +1,9 @@ namespace clanguml { namespace t20043 { struct A { - int a() { return 1; } + int a() { return a_impl(); } + + int a_impl() { return 1; } }; struct B { @@ -11,7 +13,13 @@ struct B { struct C { B b; - int c() { return b.b(); } + int c() + { + log_c(); + return b.b(); + } + + void log_c() { } }; namespace detail { diff --git a/tests/t20043/test_case.h b/tests/t20043/test_case.h index c6ea8d18c..e41b0feef 100644 --- a/tests/t20043/test_case.h +++ b/tests/t20043/test_case.h @@ -28,13 +28,14 @@ TEST_CASE("t20043") { // {"tmain()", "D", "d()"}, // - {"D", "C", "c()"}, // + {"D", "C", "c()"} // })); REQUIRE(!HasMessage(src, {"tmain()", "F", "f()"})); REQUIRE(!HasMessage(src, {"D", {"detail", "E"}, "e()"})); REQUIRE(!HasMessage(src, {"C", "B", "b()"})); + REQUIRE(!HasMessage(src, {"C", "C", "log_c()"})); REQUIRE(!HasMessage(src, {"B", "A", "a()"})); }); } \ No newline at end of file diff --git a/tests/t20059/.clang-uml b/tests/t20059/.clang-uml index d46f5a175..90b7a675f 100644 --- a/tests/t20059/.clang-uml +++ b/tests/t20059/.clang-uml @@ -6,5 +6,9 @@ diagrams: include: paths: - . + exclude: + elements: + - type: objc_method + name: t20059_A::logA from: - function: "t20059_tmain()" \ No newline at end of file diff --git a/tests/t20059/t20059.m b/tests/t20059/t20059.m index 5d6e23cba..f57bc2fc8 100644 --- a/tests/t20059/t20059.m +++ b/tests/t20059/t20059.m @@ -14,8 +14,12 @@ - (void)printImpl NSLog(@"t20059_A: Called print_impl method"); } +- (void)logA { +} + - (void)print { + [self logA]; [self printImpl]; } diff --git a/tests/t20059/test_case.h b/tests/t20059/test_case.h index 3ebdb428c..710f3072c 100644 --- a/tests/t20059/test_case.h +++ b/tests/t20059/test_case.h @@ -34,5 +34,9 @@ TEST_CASE("t20059") {"t20059_tmain()", "t20059_C", "print()"}, // {"t20059_tmain()", "t20059_D", "print()"}, // })); + + REQUIRE(HasMessage(src, {"t20059_tmain()", "t20059_A", "print()"})); + + REQUIRE(!HasMessage(src, {"t20059_A", "t20059_A", "logA()"})); }); } \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index b7fa4f65a..4d21370da 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -573,6 +573,12 @@ void CHECK_INCLUDE_DIAGRAM(const clanguml::config::config &config, #include "t00086/test_case.h" #endif +#include "t00087/test_case.h" + +#if defined(ENABLE_OBJECTIVE_C_TEST_CASES) +#include "t00088/test_case.h" +#endif + /// /// Sequence diagram tests /// diff --git a/tests/test_cases.h b/tests/test_cases.h index 5035b6ee7..406a112af 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -2565,8 +2565,8 @@ int64_t FindMessage( const json_t &d, const Message &msg, int64_t offset, bool fail) { if (msg.is_response) { - // TODO: Currently response are not generated as separate messages in - // JSON format + // TODO: Currently response are not generated as separate messages + // in JSON format return offset; } @@ -2591,8 +2591,8 @@ int64_t find_message_in_chain(const json_t &d, const Message &msg, int64_t offset, bool fail, uint32_t chain_index) { if (msg.is_response) { - // TODO: Currently response are not generated as separate messages in - // JSON format + // TODO: Currently response are not generated as separate messages + // in JSON format return offset; } diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index d272d358c..60a75f9c1 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -255,6 +255,12 @@ test_cases: - name: t00086 title: Objective-C nested structs and enums test case description: + - name: t00087 + title: Typed element class diagram filter test case + description: + - name: t00088 + title: Typed element Objective-C class diagram filter test case + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram test case diff --git a/tests/test_config.cc b/tests/test_config.cc index 5b5132cee..473eb319b 100644 --- a/tests/test_config.cc +++ b/tests/test_config.cc @@ -454,6 +454,22 @@ TEST_CASE("Test config type aliases") "std::vector"); } +TEST_CASE("Test config element_filter_t::filtered_type to_string") +{ + using clanguml::config::element_filter_t; + CHECK(to_string(element_filter_t::filtered_type::any) == "any"); + CHECK(to_string(element_filter_t::filtered_type::class_) == "class"); + CHECK(to_string(element_filter_t::filtered_type::function) == "function"); + CHECK(to_string(element_filter_t::filtered_type::method) == "method"); + CHECK(to_string(element_filter_t::filtered_type::enum_) == "enum"); + CHECK(to_string(element_filter_t::filtered_type::concept_) == "concept"); + CHECK(to_string(element_filter_t::filtered_type::package) == "package"); + CHECK(to_string(element_filter_t::filtered_type::function_template) == + "function_template"); + CHECK(to_string(element_filter_t::filtered_type::objc_method) == + "objc_method"); +} + /// /// Main test function /// diff --git a/tests/test_config_data/filters.yml b/tests/test_config_data/filters.yml index 9549357ff..76ed5f4c0 100644 --- a/tests/test_config_data/filters.yml +++ b/tests/test_config_data/filters.yml @@ -48,6 +48,18 @@ diagrams: exclude: elements: - ns1::ns2::ClassZ + regex_typed_elements_test: + type: class + include: + elements: + - type: class + name: 'MyTypeClass' + - type: class + name: + r: 'MyType.+' + exclude: + elements: + - ns1::ns2::ClassZ regex_namespace_test: type: class include: diff --git a/tests/test_filters.cc b/tests/test_filters.cc index 5f6bf5411..37a7ce0ea 100644 --- a/tests/test_filters.cc +++ b/tests/test_filters.cc @@ -20,6 +20,7 @@ #include "doctest/doctest.h" #include "class_diagram/model/class.h" +#include "class_diagram/model/enum.h" #include "cli/cli_handler.h" #include "common/model/filters/diagram_filter_factory.h" #include "common/model/source_file.h" @@ -218,6 +219,38 @@ TEST_CASE("Test elements regexp filter") CHECK(filter.should_include(c)); } +TEST_CASE("Test typed elements filter") +{ + using clanguml::class_diagram::model::class_; + using clanguml::class_diagram::model::class_method; + using clanguml::class_diagram::model::enum_; + using clanguml::common::model::access_t; + using clanguml::common::model::diagram_filter; + using clanguml::common::model::diagram_filter_factory; + using clanguml::common::model::namespace_; + using clanguml::common::model::package; + using clanguml::common::model::source_file; + + auto cfg = clanguml::config::load("./test_config_data/filters.yml"); + + auto &config = *cfg.diagrams["regex_typed_elements_test"]; + clanguml::class_diagram::model::diagram diagram; + + auto filter_ptr = diagram_filter_factory::create(diagram, config); + diagram_filter &filter = *filter_ptr; + + class_ c{{}}; + + c.set_name("MyTypeClass"); + + CHECK(filter.should_include(c)); + + enum_ e{{}}; + e.set_name("MyTypeClass"); + + CHECK(!filter.should_include(e)); +} + TEST_CASE("Test namespaces regexp filter") { using clanguml::class_diagram::model::class_;