Merge pull request #56 from bkryza/add-structured-comment-parsing

Add structured comment parsing
This commit is contained in:
Bartek Kryza
2022-09-22 00:37:07 +02:00
committed by GitHub
42 changed files with 882 additions and 166 deletions

View File

@@ -2,7 +2,7 @@ compilation_database_dir: debug
output_directory: docs/diagrams
generate_links:
link: 'https://github.com/bkryza/clang-uml/blob/{{ git.commit }}/{{ element.source.path }}#L{{ element.source.line }}'
tooltip: '{% if "comment" in element %}{{ abbrv(trim(replace(element.comment, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %}'
tooltip: '{% if existsIn(element, "comment") and existsIn(element.comment, "brief") %}{{ abbrv(trim(replace(element.comment.brief.0, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %}'
diagrams:
main_package:
include!: uml/main_package_diagram.yml

View File

@@ -60,6 +60,68 @@ The following, are the `clang-uml` additional template functions:
* `alias(string)` - returns a PlantUML alias of an C++ entity represented by string name
* `comment(string)` - returns a comment of an C++ entity represented by string name
Templates allow complex postprocessing of the diagrams, for instance creation of customized PlantUML
notes in the diagrams from comments in the code. Below is an example of using the above commands to
generate notes in the PlantUML diagram from code comments (see also test case [t00050](./test_cases/t00050.md)):
```yaml
plantuml:
after:
# Add a note left of the `A` class with the entire clas comment as content
- >
note left of {{ alias("A") }}
{{ comment("clanguml::t00050::A").formatted }}
end note
# Same as above
- >
note right of {{ element("clanguml::t00050::A").alias }}
{% set e=element("clanguml::t00050::A") %} {{ e.comment.formatted }}
end note
# Add a note left of class 'C' using trimmed text content from the class comment
- >
note left of {{ alias("C") }} #AABBCC
{{ trim(comment("clanguml::t00050::C").text) }}
end note
# For each element in the diagram (class, template, enum):
# - Add a note with \brief comment if exists
# - Add a note with \todo for each element which has it
# - Add a note with template parameter descriptions based on \tparam comment
- >
{# Render brief comments and todos, if any were written for an element #}
{% for e in diagram.elements %}
{% if existsIn(e, "comment") and existsIn(e.comment, "brief") %}
note top of {{ e.alias }} {% if e.type == "class" %} #22AA22 {% else %} #2222AA {% endif %}
{% set c=e.comment %} {{ c.brief.0 }}
end note
{% endif %}
{% if existsIn(e, "comment") and existsIn(e.comment, "todo") %}
{% set c=e.comment %}
{% for t in c.todo %}
note top of {{ e.alias }} #882222
**TODO**
{{ t }}
end note
{% endfor %}
{% endif %}
{# Render template paramete if any #}
{% if existsIn(e, "comment") and existsIn(e.comment, "tparam") %}
{% set c=e.comment %}
note top of {{ e.alias }} #AAAAFF
**Template parameters**
{% for tp in c.tparam %}
//{{ tp.name }}// {{ trim(tp.description) }}
{% endfor %}
end note
{% endif %}
{% endfor %}
```
## Example complete config
```yaml

View File

@@ -66,7 +66,7 @@ access_t access_specifier_to_access_t(clang::AccessSpecifier access_specifier)
translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm,
clanguml::class_diagram::model::diagram &diagram,
const clanguml::config::class_diagram &config)
: source_manager_{sm}
: common::visitor::translation_unit_visitor{sm, config}
, diagram_{diagram}
, config_{config}
{
@@ -81,7 +81,7 @@ bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns)
LOG_DBG("= Visiting namespace declaration {} at {}",
ns->getQualifiedNameAsString(),
ns->getLocation().printToString(source_manager_));
ns->getLocation().printToString(source_manager()));
auto package_path = namespace_{common::get_qualified_name(*ns)};
auto package_parent = package_path;
@@ -138,7 +138,7 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm)
LOG_DBG("= Visiting enum declaration {} at {}",
enm->getQualifiedNameAsString(),
enm->getLocation().printToString(source_manager_));
enm->getLocation().printToString(source_manager()));
auto e_ptr = std::make_unique<enum_>(config_.using_namespace());
auto &e = *e_ptr;
@@ -217,7 +217,7 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm)
bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(
clang::ClassTemplateSpecializationDecl *cls)
{
if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin()))
if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin()))
return true;
if (!diagram().should_include(cls->getQualifiedNameAsString()))
@@ -225,7 +225,7 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(
LOG_DBG("= Visiting template specialization declaration {} at {}",
cls->getQualifiedNameAsString(),
cls->getLocation().printToString(source_manager_));
cls->getLocation().printToString(source_manager()));
// TODO: Add support for classes defined in function/method bodies
if (cls->isLocalClass())
@@ -264,7 +264,7 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(
bool translation_unit_visitor::VisitTypeAliasTemplateDecl(
clang::TypeAliasTemplateDecl *cls)
{
if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin()))
if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin()))
return true;
if (!diagram().should_include(cls->getQualifiedNameAsString()))
@@ -272,7 +272,7 @@ bool translation_unit_visitor::VisitTypeAliasTemplateDecl(
LOG_DBG("= Visiting template type alias declaration {} at {}",
cls->getQualifiedNameAsString(),
cls->getLocation().printToString(source_manager_));
cls->getLocation().printToString(source_manager()));
auto *template_type_specialization_ptr =
cls->getTemplatedDecl()
@@ -303,7 +303,7 @@ bool translation_unit_visitor::VisitTypeAliasTemplateDecl(
bool translation_unit_visitor::VisitClassTemplateDecl(
clang::ClassTemplateDecl *cls)
{
if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin()))
if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin()))
return true;
if (!diagram().should_include(cls->getQualifiedNameAsString()))
@@ -311,7 +311,7 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
LOG_DBG("= Visiting class template declaration {} at {}",
cls->getQualifiedNameAsString(),
cls->getLocation().printToString(source_manager_));
cls->getLocation().printToString(source_manager()));
auto c_ptr = create_class_declaration(cls->getTemplatedDecl());
@@ -352,7 +352,7 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
{
// Skip system headers
if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin()))
if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin()))
return true;
if (!diagram().should_include(cls->getQualifiedNameAsString()))
@@ -360,7 +360,7 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
LOG_DBG("= Visiting class declaration {} at {}",
cls->getQualifiedNameAsString(),
cls->getLocation().printToString(source_manager_));
cls->getLocation().printToString(source_manager()));
LOG_DBG(
"== getQualifiedNameAsString() = {}", cls->getQualifiedNameAsString());
@@ -1004,7 +1004,7 @@ void translation_unit_visitor::process_function_parameter(
const auto *default_arg = p.getDefaultArg();
if (default_arg != nullptr) {
auto default_arg_str = common::get_source_text(
default_arg->getSourceRange(), source_manager_);
default_arg->getSourceRange(), source_manager());
parameter.set_default_value(default_arg_str);
}
}
@@ -1241,7 +1241,7 @@ void translation_unit_visitor::process_template_specialization_argument(
// them from raw source code...
if (type_name.find("type-parameter-") == 0) {
auto declaration_text = common::get_source_text_raw(
cls->getSourceRange(), source_manager_);
cls->getSourceRange(), source_manager());
declaration_text = declaration_text.substr(
declaration_text.find(cls->getNameAsString()) +
@@ -1282,7 +1282,7 @@ void translation_unit_visitor::process_template_specialization_argument(
}
else if (type_name.find("type-parameter-") == 0) {
auto declaration_text = common::get_source_text_raw(
cls->getSourceRange(), source_manager_);
cls->getSourceRange(), source_manager());
declaration_text = declaration_text.substr(
declaration_text.find(cls->getNameAsString()) +
@@ -1327,14 +1327,14 @@ void translation_unit_visitor::process_template_specialization_argument(
template_parameter argument;
argument.is_template_parameter(false);
argument.set_type(common::get_source_text(
arg.getAsExpr()->getSourceRange(), source_manager_));
arg.getAsExpr()->getSourceRange(), source_manager()));
template_instantiation.add_template(std::move(argument));
}
else if (argument_kind == clang::TemplateArgument::TemplateExpansion) {
template_parameter argument;
argument.is_template_parameter(true);
cls->getLocation().dump(source_manager_);
cls->getLocation().dump(source_manager());
}
else if (argument_kind == clang::TemplateArgument::Pack) {
// This will only work for now if pack is at the end
@@ -1347,7 +1347,7 @@ void translation_unit_visitor::process_template_specialization_argument(
}
else {
LOG_ERROR("Unsupported template argument kind {} [{}]", arg.getKind(),
cls->getLocation().printToString(source_manager_));
cls->getLocation().printToString(source_manager()));
}
}
@@ -1888,7 +1888,7 @@ void translation_unit_visitor::
argument.is_template_parameter(false);
argument.set_type(common::get_source_text(
arg.getAsExpr()->getSourceRange(), source_manager_));
arg.getAsExpr()->getSourceRange(), source_manager()));
}
void translation_unit_visitor::
@@ -2128,16 +2128,6 @@ void translation_unit_visitor::process_field(
c.add_member(std::move(field));
}
void translation_unit_visitor::set_source_location(
const clang::Decl &decl, clanguml::common::model::source_location &element)
{
if (decl.getLocation().isValid()) {
element.set_file(source_manager_.getFilename(decl.getLocation()).str());
element.set_line(
source_manager_.getSpellingLineNumber(decl.getLocation()));
}
}
void translation_unit_visitor::add_incomplete_forward_declarations()
{
for (auto &[id, c] : forward_declarations_) {

View File

@@ -20,6 +20,7 @@
#include "class_diagram/model/class.h"
#include "class_diagram/model/diagram.h"
#include "common/model/enums.h"
#include "common/visitor/translation_unit_visitor.h"
#include "config/config.h"
#include <clang/AST/RecursiveASTVisitor.h>
@@ -38,7 +39,8 @@ using found_relationships_t =
common::model::relationship_t>>;
class translation_unit_visitor
: public clang::RecursiveASTVisitor<translation_unit_visitor> {
: public clang::RecursiveASTVisitor<translation_unit_visitor>,
public common::visitor::translation_unit_visitor {
public:
explicit translation_unit_visitor(clang::SourceManager &sm,
clanguml::class_diagram::model::diagram &diagram,
@@ -130,9 +132,6 @@ private:
const found_relationships_t &relationships,
bool break_on_first_aggregation = false);
void set_source_location(const clang::Decl &decl,
clanguml::common::model::source_location &element);
std::unique_ptr<clanguml::class_diagram::model::class_>
build_template_instantiation(
const clang::TemplateSpecializationType &template_type,
@@ -199,20 +198,6 @@ private:
const clanguml::class_diagram::model::template_parameter &ct,
found_relationships_t &relationships);
template <typename ClangDecl>
void process_comment(
const ClangDecl &decl, clanguml::common::model::decorated_element &e)
{
const auto *comment =
decl.getASTContext().getRawCommentForDeclNoCache(&decl);
if (comment != nullptr) {
e.set_comment(comment->getFormattedText(
source_manager_, decl.getASTContext().getDiagnostics()));
e.add_decorators(decorators::parse(e.comment().value()));
}
}
void add_incomplete_forward_declarations();
void resolve_local_to_global_ids();
@@ -229,8 +214,6 @@ private:
std::optional<common::model::diagram_element::id_t> get_ast_local_id(
int64_t local_id) const;
clang::SourceManager &source_manager_;
// Reference to the output diagram model
clanguml::class_diagram::model::diagram &diagram_;

View File

@@ -133,10 +133,7 @@ inja::json generator<C, D>::element_context(const E &e) const
}
if (e.comment().has_value()) {
std::string c = e.comment().value();
if (!c.empty()) {
ctx["element"]["comment"] = util::trim(c);
}
ctx["element"]["comment"] = e.comment().value();
}
return ctx;
@@ -186,27 +183,38 @@ void generator<C, D>::generate_plantuml_directives(
using common::model::namespace_;
for (const auto &d : directives) {
// Render the directive with template engine first
std::string directive{env().render(std::string_view{d}, context())};
try {
// Render the directive with template engine first
std::string directive{env().render(std::string_view{d}, context())};
// Now search for alias @A() directives in the text
std::tuple<std::string, size_t, size_t> alias_match;
while (util::find_element_alias(directive, alias_match)) {
const auto full_name =
m_config.using_namespace() | std::get<0>(alias_match);
auto element_opt = m_model.get(full_name.to_string());
// Now search for alias @A() directives in the text
// (this is deprecated)
std::tuple<std::string, size_t, size_t> alias_match;
while (util::find_element_alias(directive, alias_match)) {
const auto full_name =
m_config.using_namespace() | std::get<0>(alias_match);
auto element_opt = m_model.get(full_name.to_string());
if (element_opt)
directive.replace(std::get<1>(alias_match),
std::get<2>(alias_match), element_opt.value().alias());
else {
LOG_ERROR("Cannot find clang-uml alias for element {}",
full_name.to_string());
directive.replace(std::get<1>(alias_match),
std::get<2>(alias_match), "UNKNOWN_ALIAS");
if (element_opt)
directive.replace(std::get<1>(alias_match),
std::get<2>(alias_match), element_opt.value().alias());
else {
LOG_ERROR("Cannot find clang-uml alias for element {}",
full_name.to_string());
directive.replace(std::get<1>(alias_match),
std::get<2>(alias_match), "UNKNOWN_ALIAS");
}
}
ostr << directive << '\n';
}
catch (const inja::json::parse_error &e) {
LOG_ERROR("Failed to parse Jinja template: {}", d);
}
catch (const inja::json::exception &e) {
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
d, e.what());
}
ostr << directive << '\n';
}
}
@@ -231,20 +239,42 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
if (e.file().empty())
return;
if (!m_config.generate_links().link.empty()) {
ostr << " [[";
ostr << env().render(std::string_view{m_config.generate_links().link},
element_context(e));
ostr << " [[";
try {
if (!m_config.generate_links().link.empty()) {
ostr << env().render(
std::string_view{m_config.generate_links().link},
element_context(e));
}
}
catch (const inja::json::parse_error &e) {
LOG_ERROR("Failed to parse Jinja template: {}",
m_config.generate_links().link);
}
catch (const inja::json::exception &e) {
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
m_config.generate_links().link, e.what());
}
if (!m_config.generate_links().tooltip.empty()) {
ostr << "{";
ostr << env().render(
std::string_view{m_config.generate_links().tooltip},
element_context(e));
ostr << "}";
ostr << "{";
try {
if (!m_config.generate_links().tooltip.empty()) {
ostr << env().render(
std::string_view{m_config.generate_links().tooltip},
element_context(e));
}
}
catch (const inja::json::parse_error &e) {
LOG_ERROR("Failed to parse Jinja template: {}",
m_config.generate_links().link);
}
catch (const inja::json::exception &e) {
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
m_config.generate_links().link, e.what());
}
ostr << "}";
ostr << "]]";
}
@@ -433,10 +463,14 @@ template <typename C, typename D> void generator<C, D>::init_env()
// {{ element("clanguml::t00050::A").comment }}
//
m_env.add_callback("element", 1, [this](inja::Arguments &args) {
inja::json res{};
auto element_opt = m_model.get_with_namespace(
args[0]->get<std::string>(), m_config.using_namespace());
return element_opt.value().context();
if (element_opt.has_value())
res = element_opt.value().context();
return res;
});
// Convert C++ entity to PlantUML alias, e.g.
@@ -457,15 +491,17 @@ template <typename C, typename D> void generator<C, D>::init_env()
// {{ element("A").comment }}
//
m_env.add_callback("comment", 1, [this](inja::Arguments &args) {
std::string res{};
inja::json res{};
auto element = m_model.get_with_namespace(
args[0]->get<std::string>(), m_config.using_namespace());
if (element.has_value()) {
auto comment = element.value().comment();
if (comment.has_value())
if (comment.has_value()) {
assert(comment.value().is_object());
res = comment.value();
}
}
return res;

View File

@@ -88,10 +88,7 @@ void decorated_element::append(const decorated_element &de)
}
}
std::optional<std::string> decorated_element::comment() const
{
return comment_;
}
std::optional<comment_t> decorated_element::comment() const { return comment_; }
void decorated_element::set_comment(const std::string &c) { comment_ = c; }
void decorated_element::set_comment(const comment_t &c) { comment_ = c; }
}

View File

@@ -20,14 +20,20 @@
#include "enums.h"
#include "decorators/decorators.h"
#include "inja/inja.hpp"
#include <any>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
namespace clanguml::common::model {
using comment_t = inja::json;
class decorated_element {
public:
bool skip() const;
@@ -46,13 +52,13 @@ public:
void append(const decorated_element &de);
std::optional<std::string> comment() const;
std::optional<comment_t> comment() const;
void set_comment(const std::string &c);
void set_comment(const comment_t &c);
private:
std::vector<std::shared_ptr<decorators::decorator>> decorators_;
std::optional<std::string> comment_;
std::optional<comment_t> comment_;
};
}

View File

@@ -96,7 +96,6 @@ void diagram_element::complete(bool completed) { complete_ = completed; }
bool operator==(const diagram_element &l, const diagram_element &r)
{
return l.id() == r.id();
// return l.full_name(false) == r.full_name(false);
}
std::ostream &operator<<(std::ostream &out, const diagram_element &rhs)

View File

@@ -19,6 +19,7 @@
#include "decorated_element.h"
#include "relationship.h"
#include "source_location.h"
#include "util/util.h"
#include <inja/inja.hpp>
@@ -30,7 +31,7 @@
namespace clanguml::common::model {
class diagram_element : public decorated_element {
class diagram_element : public decorated_element, public source_location {
public:
using id_t = int64_t;

View File

@@ -173,11 +173,23 @@ tvl::value_t namespace_filter::match(
const diagram & /*d*/, const element &e) const
{
if (dynamic_cast<const package *>(&e) != nullptr) {
return tvl::any_of(
namespaces_.begin(), namespaces_.end(), [&e](const auto &nsit) {
return (e.get_namespace() | e.name()).starts_with(nsit) ||
nsit.starts_with(e.get_namespace() | e.name()) ||
return tvl::any_of(namespaces_.begin(), namespaces_.end(),
[&e, is_inclusive = is_inclusive()](const auto &nsit) {
auto element_full_name_starts_with_namespace =
(e.get_namespace() | e.name()).starts_with(nsit);
auto element_full_name_equals_pattern =
(e.get_namespace() | e.name()) == nsit;
auto namespace_starts_with_element_qualified_name =
nsit.starts_with(e.get_namespace());
auto result = element_full_name_starts_with_namespace |
element_full_name_equals_pattern;
if (is_inclusive)
result =
result | namespace_starts_with_element_qualified_name;
return result;
});
}
else {

View File

@@ -44,8 +44,9 @@ inja::json element::context() const
ctx["alias"] = alias();
ctx["full_name"] = full_name(false);
ctx["namespace"] = get_namespace().to_string();
if (comment().has_value())
if (comment().has_value()) {
ctx["comment"] = comment().value();
}
return ctx;
}

View File

@@ -32,7 +32,7 @@
namespace clanguml::common::model {
class element : public diagram_element, public source_location {
class element : public diagram_element {
public:
element(const namespace_ &using_namespace);

View File

@@ -45,7 +45,6 @@ using filesystem_path = common::model::path<fs_path_sep>;
class source_file
: public common::model::diagram_element,
public common::model::stylable_element,
public source_location,
public common::model::nested_trait<common::model::source_file,
filesystem_path> {
public:

View File

@@ -0,0 +1,221 @@
/**
* src/common/visitor/comment/clang_visitor.cc
*
* Copyright (c) 2021-2022 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.
*/
#include "clang_visitor.h"
namespace clanguml::common::visitor::comment {
clang_visitor::clang_visitor(clang::SourceManager &source_manager)
: comment_visitor{source_manager}
{
}
void clang_visitor::visit(
const clang::NamedDecl &decl, common::model::decorated_element &e)
{
const auto *comment =
decl.getASTContext().getRawCommentForDeclNoCache(&decl);
if (comment == nullptr) {
return;
}
auto raw_comment = comment->getRawText(source_manager());
auto formatted_comment = comment->getFormattedText(
source_manager(), decl.getASTContext().getDiagnostics());
common::model::comment_t cmt = inja::json::object();
cmt["raw"] = raw_comment;
cmt["formatted"] = formatted_comment;
using clang::comments::BlockCommandComment;
using clang::comments::Comment;
using clang::comments::FullComment;
using clang::comments::ParagraphComment;
using clang::comments::ParamCommandComment;
using clang::comments::TextComment;
using clang::comments::TParamCommandComment;
FullComment *full_comment =
comment->parse(decl.getASTContext(), nullptr, &decl);
const auto &traits = decl.getASTContext().getCommentCommandTraits();
for (const auto *block : full_comment->getBlocks()) {
const auto block_kind = block->getCommentKind();
if (block_kind == Comment::ParagraphCommentKind) {
std::string paragraph_text;
visit_paragraph(clang::dyn_cast<ParagraphComment>(block), traits,
paragraph_text);
if (!cmt.contains("text"))
cmt["text"] = "";
cmt["text"] =
cmt["text"].get<std::string>() + "\n" + paragraph_text;
}
else if (block_kind == Comment::TextCommentKind) {
// TODO
}
else if (block_kind == Comment::ParamCommandCommentKind) {
visit_param_command(
clang::dyn_cast<ParamCommandComment>(block), traits, cmt);
}
else if (block_kind == Comment::TParamCommandCommentKind) {
visit_tparam_command(
clang::dyn_cast<TParamCommandComment>(block), traits, cmt);
}
else if (block_kind == Comment::BlockCommandCommentKind) {
auto *command = clang::dyn_cast<BlockCommandComment>(block);
auto command_info = traits.getCommandInfo(command->getCommandID());
if (command_info->IsBlockCommand && command_info->NumArgs == 0) {
// Visit block command with a single text argument, e.g.:
// \brief text
// \todo text
// ...
visit_block_command(command, traits, cmt);
}
else if (command_info->IsParamCommand) {
// Visit function param block:
// \param arg text
visit_param_command(
clang::dyn_cast<ParamCommandComment>(command), traits, cmt);
}
else if (command_info->IsTParamCommand) {
// Visit template param block:
// \tparam typename text
visit_tparam_command(
clang::dyn_cast<TParamCommandComment>(command), traits,
cmt);
}
}
}
e.set_comment(cmt);
}
void clang_visitor::visit_block_command(
const clang::comments::BlockCommandComment *command,
const clang::comments::CommandTraits &traits, common::model::comment_t &cmt)
{
using clang::comments::Comment;
using clang::comments::ParagraphComment;
using clang::comments::TextComment;
std::string command_text;
for (auto paragraph_it = command->child_begin();
paragraph_it != command->child_end(); ++paragraph_it) {
if ((*paragraph_it)->getCommentKind() ==
Comment::ParagraphCommentKind) {
visit_paragraph(clang::dyn_cast<ParagraphComment>(*paragraph_it),
traits, command_text);
}
}
const auto command_name = command->getCommandName(traits).str();
if (!command_text.empty()) {
if (!cmt.contains(command_name))
cmt[command_name] = inja::json::array();
cmt[command_name].push_back(command_text);
}
}
void clang_visitor::visit_param_command(
const clang::comments::ParamCommandComment *command,
const clang::comments::CommandTraits &traits, common::model::comment_t &cmt)
{
using clang::comments::Comment;
using clang::comments::ParagraphComment;
using clang::comments::TextComment;
std::string description;
const auto name = command->getParamNameAsWritten().str();
for (auto it = command->child_begin(); it != command->child_end(); ++it) {
if ((*it)->getCommentKind() == Comment::ParagraphCommentKind) {
visit_paragraph(
clang::dyn_cast<ParagraphComment>(*it), traits, description);
}
}
if (!name.empty()) {
if (!cmt.contains("param"))
cmt["param"] = inja::json::array();
inja::json param = inja::json::object();
param["name"] = name;
param["description"] = description;
cmt["param"].push_back(std::move(param));
}
}
void clang_visitor::visit_tparam_command(
const clang::comments::TParamCommandComment *command,
const clang::comments::CommandTraits &traits, common::model::comment_t &cmt)
{
using clang::comments::Comment;
using clang::comments::ParagraphComment;
using clang::comments::TextComment;
std::string description;
const auto name = command->getParamNameAsWritten().str();
for (auto it = command->child_begin(); it != command->child_end(); ++it) {
if ((*it)->getCommentKind() == Comment::ParagraphCommentKind) {
visit_paragraph(
clang::dyn_cast<ParagraphComment>(*it), traits, description);
}
}
if (!name.empty()) {
if (!cmt.contains("tparam"))
cmt["tparam"] = inja::json::array();
inja::json param = inja::json::object();
param["name"] = name;
param["description"] = description;
cmt["tparam"].push_back(std::move(param));
}
}
void clang_visitor::visit_paragraph(
const clang::comments::ParagraphComment *paragraph,
const clang::comments::CommandTraits &traits, std::string &text)
{
using clang::comments::Comment;
using clang::comments::TextComment;
for (auto text_it = paragraph->child_begin();
text_it != paragraph->child_end(); ++text_it) {
if ((*text_it)->getCommentKind() == Comment::TextCommentKind) {
// Merge paragraph lines into a single string
text += clang::dyn_cast<TextComment>((*text_it))->getText();
text += "\n";
}
}
}
}

View File

@@ -0,0 +1,54 @@
/**
* src/common/visitor/comment/clang_visitor.h
*
* Copyright (c) 2021-2022 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.
*/
#pragma once
#include <clang/AST/ASTContext.h>
#include <clang/AST/Comment.h>
#include <clang/Basic/SourceManager.h>
#include "comment_visitor.h"
namespace clanguml::common::visitor::comment {
class clang_visitor : public comment_visitor {
public:
clang_visitor(clang::SourceManager &source_manager);
void visit(const clang::NamedDecl &decl,
common::model::decorated_element &e) override;
private:
void visit_block_command(
const clang::comments::BlockCommandComment *command,
const clang::comments::CommandTraits &traits,
common::model::comment_t &cmt);
void visit_param_command(
const clang::comments::ParamCommandComment *command,
const clang::comments::CommandTraits &traits,
common::model::comment_t &cmt);
void visit_tparam_command(
const clang::comments::TParamCommandComment *command,
const clang::comments::CommandTraits &traits,
common::model::comment_t &cmt);
void visit_paragraph(const clang::comments::ParagraphComment *paragraph,
const clang::comments::CommandTraits &traits, std::string &text);
};
}

View File

@@ -0,0 +1,33 @@
/**
* src/common/visitor/comment/comment_visitor.cc
*
* Copyright (c) 2021-2022 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.
*/
#include "comment_visitor.h"
namespace clanguml::common::visitor::comment {
comment_visitor::comment_visitor(clang::SourceManager &source_manager)
: source_manager_{source_manager}
{
}
clang::SourceManager &comment_visitor::source_manager()
{
return source_manager_;
}
}

View File

@@ -0,0 +1,41 @@
/**
* src/common/visitor/comment/comment_visitor.h
*
* Copyright (c) 2021-2022 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.
*/
#pragma once
#include <clang/AST/Comment.h>
#include <clang/Basic/SourceManager.h>
#include "common/model/decorated_element.h"
namespace clanguml::common::visitor::comment {
class comment_visitor {
public:
comment_visitor(clang::SourceManager &source_manager);
virtual ~comment_visitor() = default;
virtual void visit(
const clang::NamedDecl &decl, common::model::decorated_element &e) = 0;
clang::SourceManager &source_manager();
private:
clang::SourceManager &source_manager_;
};
}

View File

@@ -0,0 +1,50 @@
/**
* src/common/visitor/comment/plain_visitor.cc
*
* Copyright (c) 2021-2022 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.
*/
#include "plain_visitor.h"
namespace clanguml::common::visitor::comment {
plain_visitor::plain_visitor(clang::SourceManager &source_manager)
: comment_visitor{source_manager}
{
}
void plain_visitor::visit(
const clang::NamedDecl &decl, common::model::decorated_element &e)
{
const auto *comment =
decl.getASTContext().getRawCommentForDeclNoCache(&decl);
if (comment == nullptr) {
return;
}
auto raw_comment = comment->getRawText(source_manager());
auto formatted_comment = comment->getFormattedText(
source_manager(), decl.getASTContext().getDiagnostics());
common::model::comment_t cmt = inja::json::object();
cmt["raw"] = raw_comment;
cmt["formatted"] = formatted_comment;
e.set_comment(cmt);
}
}

View File

@@ -0,0 +1,35 @@
/**
* src/common/visitor/comment/plain_visitor.h
*
* Copyright (c) 2021-2022 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.
*/
#pragma once
#include <clang/AST/ASTContext.h>
#include <clang/AST/Comment.h>
#include <clang/Basic/SourceManager.h>
#include "comment_visitor.h"
namespace clanguml::common::visitor::comment {
class plain_visitor : public comment_visitor {
public:
plain_visitor(clang::SourceManager &source_manager);
void visit(const clang::NamedDecl &decl,
common::model::decorated_element &e) override;
};
}

View File

@@ -0,0 +1,76 @@
/**
* src/common/visitor/translation_unit_visitor.cc
*
* Copyright (c) 2021-2022 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.
*/
#include "translation_unit_visitor.h"
#include "comment/clang_visitor.h"
#include "comment/plain_visitor.h"
namespace clanguml::common::visitor {
translation_unit_visitor::translation_unit_visitor(
clang::SourceManager &sm, const clanguml::config::diagram &config)
: source_manager_{sm}
, config_{config}
{
if (config.comment_parser() == config::comment_parser_t::plain) {
comment_visitor_ =
std::make_unique<comment::plain_visitor>(source_manager_);
}
else if (config.comment_parser() == config::comment_parser_t::clang) {
comment_visitor_ =
std::make_unique<comment::clang_visitor>(source_manager_);
}
}
clang::SourceManager &translation_unit_visitor::source_manager() const
{
return source_manager_;
}
void translation_unit_visitor::process_comment(
const clang::NamedDecl &decl, clanguml::common::model::decorated_element &e)
{
assert(comment_visitor_.get() != nullptr);
comment_visitor_->visit(decl, e);
const auto *comment =
decl.getASTContext().getRawCommentForDeclNoCache(&decl);
if (comment != nullptr) {
// Process clang-uml decorators in the comments
// TODO: Refactor to use standard block comments processable by clang
// comments
e.add_decorators(decorators::parse(comment->getFormattedText(
source_manager_, decl.getASTContext().getDiagnostics())));
}
}
void translation_unit_visitor::set_source_location(
const clang::Decl &decl, clanguml::common::model::source_location &element)
{
if (decl.getLocation().isValid()) {
element.set_file(source_manager_.getFilename(decl.getLocation()).str());
element.set_line(
source_manager_.getSpellingLineNumber(decl.getLocation()));
}
}
}

View File

@@ -0,0 +1,62 @@
/**
* src/common/visitor/translation_unit_visitor.h
*
* Copyright (c) 2021-2022 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.
*/
#pragma once
#include "comment/comment_visitor.h"
#include "config/config.h"
#include <clang/AST/Comment.h>
#include <clang/Basic/SourceManager.h>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <string>
namespace clanguml::common::visitor {
using found_relationships_t =
std::vector<std::pair<clanguml::common::model::diagram_element::id_t,
common::model::relationship_t>>;
class translation_unit_visitor {
public:
explicit translation_unit_visitor(
clang::SourceManager &sm, const clanguml::config::diagram &config);
clang::SourceManager &source_manager() const;
void finalize();
protected:
void set_source_location(const clang::Decl &decl,
clanguml::common::model::source_location &element);
void process_comment(const clang::NamedDecl &decl,
clanguml::common::model::decorated_element &e);
private:
clang::SourceManager &source_manager_;
// Reference to diagram config
const clanguml::config::diagram &config_;
std::unique_ptr<comment::comment_visitor> comment_visitor_;
};
}

View File

@@ -100,6 +100,7 @@ void inheritable_diagram_options::inherit(
git.override(parent.git);
base_directory.override(parent.base_directory);
relative_to.override(parent.relative_to);
comment_parser.override(parent.comment_parser);
}
std::string inheritable_diagram_options::simplify_template_type(
@@ -284,6 +285,21 @@ void get_option<method_arguments>(
}
}
template <>
void get_option<clanguml::config::comment_parser_t>(const Node &node,
clanguml::config::option<clanguml::config::comment_parser_t> &option)
{
if (node[option.name]) {
const auto &val = node[option.name].as<std::string>();
if (val == "plain")
option.set(clanguml::config::comment_parser_t::plain);
else if (val == "clang")
option.set(clanguml::config::comment_parser_t::clang);
else
throw std::runtime_error("Invalid comment_parser value: " + val);
}
}
std::shared_ptr<clanguml::config::diagram> parse_diagram_config(const Node &d)
{
const auto diagram_type = d["type"].as<std::string>();
@@ -532,6 +548,8 @@ template <typename T> bool decode_diagram(const Node &node, T &rhs)
get_option(node, rhs.puml);
get_option(node, rhs.git);
get_option(node, rhs.generate_links);
get_option(node, rhs.type_aliases);
get_option(node, rhs.comment_parser);
return true;
}
@@ -552,6 +570,7 @@ template <> struct convert<class_diagram> {
get_option(node, rhs.generate_packages);
get_option(node, rhs.relationship_hints);
get_option(node, rhs.type_aliases);
// get_option(node, rhs.comment_parser);
rhs.initialize_relationship_hints();
rhs.initialize_type_aliases();

View File

@@ -37,6 +37,8 @@ namespace config {
enum class method_arguments { full, abbreviated, none };
enum class comment_parser_t { plain, clang };
struct plantuml {
std::vector<std::string> before;
std::vector<std::string> after;
@@ -138,6 +140,8 @@ struct inheritable_diagram_options {
option<bool> generate_system_headers{"generate_system_headers", false};
option<relationship_hints_t> relationship_hints{"relationship_hints"};
option<type_aliases_t> type_aliases{"type_aliases"};
option<comment_parser_t> comment_parser{
"comment_parser", comment_parser_t::plain};
void inherit(const inheritable_diagram_options &parent);

View File

@@ -37,7 +37,7 @@ translation_unit_visitor::include_visitor::include_visitor(
clang::SourceManager &sm,
clanguml::include_diagram::model::diagram &diagram,
const clanguml::config::include_diagram &config)
: source_manager_{sm}
: common::visitor::translation_unit_visitor{sm, config}
, diagram_{diagram}
, config_{config}
{
@@ -55,7 +55,7 @@ void translation_unit_visitor::include_visitor::InclusionDirective(
using common::model::source_file_t;
auto current_file =
std::filesystem::path{source_manager_.getFilename(hash_loc).str()};
std::filesystem::path{source_manager().getFilename(hash_loc).str()};
current_file = std::filesystem::absolute(current_file);
current_file = current_file.lexically_normal();

View File

@@ -19,6 +19,7 @@
#include "common/model/enums.h"
#include "common/model/package.h"
#include "common/visitor/translation_unit_visitor.h"
#include "config/config.h"
#include "include_diagram/model/diagram.h"
@@ -39,7 +40,8 @@ class translation_unit_visitor
public:
// This is an internal class for convenience to be able to access the
// include_visitor type from translation_unit_visitor type
class include_visitor : public clang::PPCallbacks {
class include_visitor : public clang::PPCallbacks,
public common::visitor::translation_unit_visitor {
public:
include_visitor(clang::SourceManager &sm,
clanguml::include_diagram::model::diagram &diagram,
@@ -74,8 +76,6 @@ public:
}
private:
clang::SourceManager &source_manager_;
// Reference to the output diagram model
clanguml::include_diagram::model::diagram &diagram_;

View File

@@ -39,7 +39,7 @@ using clanguml::package_diagram::model::diagram;
translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm,
clanguml::package_diagram::model::diagram &diagram,
const clanguml::config::package_diagram &config)
: source_manager_{sm}
: common::visitor::translation_unit_visitor{sm, config}
, diagram_{diagram}
, config_{config}
{
@@ -107,7 +107,7 @@ bool translation_unit_visitor::VisitFunctionDecl(
assert(function_declaration != nullptr);
// Skip system headers
if (source_manager_.isInSystemHeader(
if (source_manager().isInSystemHeader(
function_declaration->getSourceRange().getBegin()))
return true;
@@ -130,7 +130,7 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
assert(cls != nullptr);
// Skip system headers
if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin()))
if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin()))
return true;
// Templated records are handled by VisitClassTemplateDecl()

View File

@@ -17,6 +17,7 @@
*/
#pragma once
#include "common/visitor/translation_unit_visitor.h"
#include "config/config.h"
#include "package_diagram/model/diagram.h"
@@ -37,7 +38,8 @@ using found_relationships_t =
common::model::relationship_t>>;
class translation_unit_visitor
: public clang::RecursiveASTVisitor<translation_unit_visitor> {
: public clang::RecursiveASTVisitor<translation_unit_visitor>,
public common::visitor::translation_unit_visitor {
public:
translation_unit_visitor(clang::SourceManager &sm,
clanguml::package_diagram::model::diagram &diagram,
@@ -88,33 +90,6 @@ private:
void add_relationships(
clang::DeclContext *cls, found_relationships_t &relationships);
template <typename ClangDecl>
void process_comment(
const ClangDecl &decl, clanguml::common::model::decorated_element &e)
{
const auto *comment =
decl.getASTContext().getRawCommentForDeclNoCache(&decl);
if (comment != nullptr) {
e.set_comment(comment->getFormattedText(
source_manager_, decl.getASTContext().getDiagnostics()));
e.add_decorators(decorators::parse(e.comment().value()));
}
}
void set_source_location(const clang::Decl &decl,
clanguml::common::model::source_location &element)
{
if (decl.getLocation().isValid()) {
element.set_file(
source_manager_.getFilename(decl.getLocation()).str());
element.set_line(
source_manager_.getSpellingLineNumber(decl.getLocation()));
}
}
clang::SourceManager &source_manager_;
// Reference to the output diagram model
clanguml::package_diagram::model::diagram &diagram_;

View File

@@ -5,6 +5,7 @@ diagrams:
type: class
glob:
- ../../tests/t00002/t00002.cc
comment_parser: clang
using_namespace:
- clanguml::t00002
include:

View File

@@ -3,7 +3,7 @@
namespace clanguml {
namespace t00002 {
/// This is class A
/// \brief This is class A
class A {
public:
/// Abstract foo_a
@@ -12,7 +12,7 @@ public:
virtual void foo_c() = 0;
};
/// This is class B
/// \brief This is class B
class B : public A {
public:
virtual void foo_a() override { }

View File

@@ -10,10 +10,6 @@ diagrams:
include:
namespaces:
- clanguml::t00014
exclude:
namespaces:
- std
- clanguml::t00014::std
plantuml:
before:
- left to right direction

View File

@@ -19,4 +19,5 @@ diagrams:
- inheritance
exclude:
namespaces:
- std
- clanguml::t00039::detail
- clanguml::t00039::ns3::detail

View File

@@ -42,6 +42,11 @@ struct AAAA : public virtual AAA {
};
} // namespace ns2
namespace detail {
struct AA : public A {
};
} // namespace detail
namespace ns3 {
template <typename T> struct F {
T *t;

View File

@@ -42,6 +42,7 @@ TEST_CASE("t00039", "[test-case][class]")
REQUIRE_THAT(puml, IsBaseClass(_A("A"), _A("AA")));
REQUIRE_THAT(puml, IsBaseClass(_A("AA"), _A("AAA")));
REQUIRE_THAT(puml, IsBaseClass(_A("AAA"), _A("ns2::AAAA")));
REQUIRE_THAT(puml, !IsClass(_A("detail::AA")));
REQUIRE_THAT(puml, !IsClass(_A("B")));
REQUIRE_THAT(puml, !IsClass(_A("ns1::BB")));

View File

@@ -14,4 +14,7 @@ diagrams:
context:
- clanguml::t00041::RR
subclasses:
- clanguml::t00041::ns1::N
- clanguml::t00041::ns1::N
exclude:
namespaces:
- clanguml::t00041::detail

View File

@@ -24,9 +24,15 @@ struct E {
struct F {
};
namespace detail {
struct G {
};
} // namespace detail
struct RR : public R {
E *e;
F *f;
detail::G *g;
};
struct RRR : public RR {

View File

@@ -47,6 +47,7 @@ TEST_CASE("t00041", "[test-case][class]")
REQUIRE_THAT(puml, IsClass(_A("R")));
REQUIRE_THAT(puml, IsClass(_A("RR")));
REQUIRE_THAT(puml, IsClass(_A("RRR")));
REQUIRE_THAT(puml, !IsClass(_A("detail::G")));
REQUIRE_THAT(puml, IsBaseClass(_A("R"), _A("RR")));
REQUIRE_THAT(puml, IsBaseClass(_A("RR"), _A("RRR")));

View File

@@ -5,6 +5,7 @@ diagrams:
type: class
glob:
- ../../tests/t00050/t00050.cc
comment_parser: clang
include:
namespaces:
- clanguml::t00050
@@ -12,32 +13,49 @@ diagrams:
plantuml:
after:
- >
note left of {{ alias("A") }}
{{ comment("clanguml::t00050::A") }}
note left of {{ alias("A") }}
{{ comment("clanguml::t00050::A").formatted }}
end note
- >
note right of {{ element("clanguml::t00050::A").alias }}
{{ element("A").comment }}
{% set e=element("clanguml::t00050::A") %} {{ e.comment.formatted }}
end note
- >
note left of {{ alias("C") }} #AABBCC
{{ trim(comment("clanguml::t00050::C").text) }}
end note
- >
{% for element in diagram.elements %}
{% if element.type == "class" and existsIn(element, "comment") %}
note top of {{ element.alias }}
{{ element.comment }}
{# Render brief comments and todos, if any were written for an element #}
{% for e in diagram.elements %}
{% if existsIn(e, "comment") and existsIn(e.comment, "brief") %}
note top of {{ e.alias }} {% if e.type == "class" %} #22AA22 {% else %} #2222AA {% endif %}
{% set c=e.comment %} {{ c.brief.0 }}
end note
{% endif %}
{% if existsIn(e, "comment") and existsIn(e.comment, "todo") %}
{% set c=e.comment %}
{% for t in c.todo %}
note top of {{ e.alias }} #882222
**TODO**
{{ t }}
end note
{% endfor %}
{% endif %}
{# Render template paramete if any #}
{% if existsIn(e, "comment") and existsIn(e.comment, "tparam") %}
{% set c=e.comment %}
note top of {{ e.alias }} #AAAAFF
**Template parameters**
{% for tp in c.tparam %}
//{{ tp.name }}// {{ trim(tp.description) }}
{% endfor %}
end note
{% endif %}
{% endfor %}
- >
{% for element in diagram.elements %}
{% if element.type == "enum" and existsIn(element, "comment") %}
note bottom of {{ element.alias }}
{{ element.comment }}
end note
{% endif %}
{% endfor %}

View File

@@ -11,15 +11,23 @@ class A {
};
/**
* \brief Lorem ipsum
*
* Lorem ipsum dolor sit amet consectetur adipiscing elit, urna consequat felis
* vehicula class ultricies mollis dictumst, aenean non a in donec nulla.
* Phasellus ante pellentesque erat cum risus consequat imperdiet aliquam,
* integer placerat et turpis mi eros nec lobortis taciti, vehicula nisl litora
* tellus ligula porttitor metus.
*
* \todo 1. Write meaningful comment
* \todo 2. Write tests
* \todo 3. Implement
*/
class B {
};
/// \brief Long comment example
///
/// Lorem ipsum dolor sit amet consectetur adipiscing elit, urna consequat felis
/// vehicula class ultricies mollis dictumst, aenean non a in donec nulla.
/// Phasellus ante pellentesque erat cum risus consequat imperdiet aliquam,
@@ -56,6 +64,8 @@ namespace utils {
/// Phasellus ante pellentesque erat cum risus consequat imperdiet aliquam,
/// integer placerat et turpis mi eros nec lobortis taciti, vehicula nisl litora
/// tellus ligula porttitor metus.
///
/// \todo Implement...
class D {
};
@@ -64,6 +74,20 @@ class D {
/// Mollis pretium lorem primis
enum class E { E1, E2, E3 };
/// \brief Simple array wrapper.
///
/// This class is just for testing tparam parsing, it serves no other
/// purpose.
///
/// \tparam T Type of array elements.
/// \tparam V Type of regular element.
/// \tparam N Size of T array.
///
template <typename T, typename V, int N> class F {
T t[N];
V v;
};
class NoComment {
};

View File

@@ -42,13 +42,13 @@ TEST_CASE("t00050", "[test-case][class]")
REQUIRE_THAT(puml, IsEnum(_A("E")));
REQUIRE_THAT(puml, HasNote(_A("A"), "left"));
REQUIRE_THAT(puml, HasNote(_A("A"), "top"));
REQUIRE_THAT(puml, HasNote(_A("A"), "right"));
REQUIRE_THAT(puml, HasNote(_A("B"), "top"));
REQUIRE_THAT(puml, HasNote(_A("C"), "top"));
REQUIRE_THAT(puml, HasNote(_A("utils::D"), "top"));
REQUIRE_THAT(puml, HasNote(_A("E"), "bottom"));
REQUIRE_THAT(puml, !HasNote(_A("E"), "bottom"));
REQUIRE_THAT(puml, !HasNote(_A("NoComment"), "top"));
REQUIRE_THAT(puml, HasNote(_A("F<T,V,int N>"), "top"));
save_puml(
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);

View File

@@ -25,7 +25,7 @@ void inject_diagram_options(std::shared_ptr<clanguml::config::diagram> diagram)
// Inject links config to all test cases
clanguml::config::generate_links_config links_config{
R"(https://github.com/bkryza/clang-uml/blob/{{ git.commit }}/{{ element.source.path }}#L{{ element.source.line }})",
R"({% if "comment" in element %}{{ abbrv(trim(replace(element.comment, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %})"};
R"({% if existsIn(element, "comment") and existsIn(element.comment, "brief") %}{{ abbrv(trim(replace(element.comment.brief.0, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %})"};
diagram->generate_links.set(links_config);
}

View File

@@ -44,6 +44,9 @@ TEST_CASE("Test config simple", "[unit-test]")
"element.source.file }}#L{{ element.source.line }}");
CHECK(diagram.generate_links().tooltip == "{{ element.comment }}");
CHECK(
diagram.comment_parser() == clanguml::config::comment_parser_t::clang);
CHECK(contains(diagram.include().access, access_t::kPublic));
CHECK(contains(diagram.include().access, access_t::kProtected));
CHECK(contains(diagram.include().access, access_t::kPrivate));

View File

@@ -7,6 +7,7 @@ diagrams:
glob:
- src/**/*.cc
- src/**/*.h
comment_parser: clang
using_namespace: clanguml
generate_method_arguments: full
generate_packages: true