Skip to content

Commit

Permalink
Merge pull request #330 from bkryza/extend-element-filter-with-types
Browse files Browse the repository at this point in the history
Extend element filter with types
  • Loading branch information
bkryza authored Oct 31, 2024
2 parents e6c5693 + 6b37602 commit daf1b3b
Show file tree
Hide file tree
Showing 34 changed files with 628 additions and 29 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
39 changes: 38 additions & 1 deletion docs/diagram_filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion src/class_diagram/model/class_element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand All @@ -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
16 changes: 15 additions & 1 deletion src/class_diagram/model/class_element.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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_;
};

Expand Down
12 changes: 11 additions & 1 deletion src/class_diagram/visitor/translation_unit_visitor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
106 changes: 95 additions & 11 deletions src/common/model/filters/diagram_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ tvl::value_t modules_filter::match(
}

element_filter::element_filter(
filter_t type, std::vector<common::string_or_regex> elements)
filter_t type, std::vector<config::element_filter_t> elements)
: filter_visitor{type}
, elements_{std::move(elements)}
{
Expand All @@ -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();
});
}

Expand All @@ -481,19 +543,39 @@ tvl::value_t element_filter::match(
dynamic_cast<const sequence_diagram::model::diagram &>(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<const method &>(p);
const auto class_id = m.class_id();
const auto &class_participant =
sequence_model.get_participant<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<const sequence_diagram::model::objc_method &>(
p);
const auto class_id = m.class_id();
const auto &class_participant =
sequence_model.get_participant<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);
});
}

Expand All @@ -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) {
Expand Down Expand Up @@ -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<const class_diagram::model::diagram &>(d)
Expand Down Expand Up @@ -1084,4 +1168,4 @@ bool diagram_filter::should_include<std::string>(const std::string &name) const

return should_include(ns, n);
}
} // namespace clanguml::common::model
} // namespace clanguml::common::model
16 changes: 14 additions & 2 deletions src/common/model/filters/diagram_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,17 +265,29 @@ struct modules_filter : public filter_visitor {
*/
struct element_filter : public filter_visitor {
element_filter(
filter_t type, std::vector<common::string_or_regex> elements);
filter_t type, std::vector<config::element_filter_t> 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<common::string_or_regex> elements_;
std::vector<config::element_filter_t> elements_;
};

/**
Expand Down
Loading

0 comments on commit daf1b3b

Please sign in to comment.