Skip generation of empty or invalid relationships in PlantUML output

This commit is contained in:
Bartek Kryza
2021-05-22 14:09:39 +02:00
parent 6cc49c2660
commit 08f00889e2
6 changed files with 145 additions and 63 deletions

View File

@@ -13,6 +13,15 @@ TODO
## Usage ## Usage
### Generating compile commands database
`clang-uml` requires an up-to-date
[compile-commands.json](https://clang.llvm.org/docs/JSONCompilationDatabase.html)
file, containing the list of commands used for compiling the source code.
Nowadays, this file can be generated rather easily using multiple methods:
* For [CMake](https://cmake.org/) projects, simply invoke the `cmake` command
as `cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...`
* For Make projects checkout [compiledb](https://github.com/nickdiego/compiledb) or [Bear](https://github.com/rizsotto/Bear)
### Invocation ### Invocation
### Configuration file format and examples ### Configuration file format and examples

View File

@@ -199,47 +199,68 @@ public:
if (m_config.should_include_relationship("inheritance")) if (m_config.should_include_relationship("inheritance"))
for (const auto &b : c.bases) { for (const auto &b : c.bases) {
ostr << m_model.to_alias(m_config.using_namespace, std::stringstream relstr;
ns_relative(m_config.using_namespace, b.name)) try {
<< " <|-- " relstr << m_model.to_alias(m_config.using_namespace,
<< m_model.to_alias(m_config.using_namespace, ns_relative(m_config.using_namespace, b.name))
ns_relative(m_config.using_namespace, c.name)) << " <|-- "
<< std::endl; << m_model.to_alias(m_config.using_namespace,
ns_relative(m_config.using_namespace, c.name))
<< std::endl;
ostr << relstr.str();
}
catch (error::uml_alias_missing &e) {
LOG_ERROR("Skipping inheritance relation from {} to {} due "
"to: {}",
b.name, c.name, e.what());
}
} }
for (const auto &r : c.relationships) { for (const auto &r : c.relationships) {
if (!m_config.should_include_relationship(name(r.type))) if (!m_config.should_include_relationship(name(r.type)))
continue; continue;
std::string destination; std::stringstream relstr;
if (r.destination.find("#") != std::string::npos ||
r.destination.find("@") != std::string::npos) {
destination = m_model.usr_to_name(
m_config.using_namespace, r.destination);
// If something went wrong and we have an empty destination std::string destination;
// generate the relationship but comment it out for try {
// debugging if (r.destination.find("#") != std::string::npos ||
if (destination.empty()) { r.destination.find("@") != std::string::npos) {
ostr << "' "; destination = m_model.usr_to_name(
m_config.using_namespace, r.destination);
// If something went wrong and we have an empty destination
// generate the relationship but comment it out for
// debugging
if (destination.empty()) {
relstr << "' ";
destination = r.destination;
}
}
else {
destination = r.destination; destination = r.destination;
} }
relstr << m_model.to_alias(m_config.using_namespace,
ns_relative(m_config.using_namespace,
c.full_name(m_config.using_namespace)))
<< " " << to_string(r.type) << " "
<< m_model.to_alias(m_config.using_namespace,
ns_relative(
m_config.using_namespace, destination));
if (!r.label.empty())
relstr << " : " << r.label;
relstr << std::endl;
ostr << relstr.str();
} }
else { catch (error::uml_alias_missing &e) {
destination = r.destination; LOG_ERROR("Skipping {} relation from {} to {} due "
"to: {}",
to_string(r.type), c.full_name(m_config.using_namespace),
destination, e.what());
} }
ostr << m_model.to_alias(m_config.using_namespace,
ns_relative(m_config.using_namespace,
c.full_name(m_config.using_namespace)))
<< " " << to_string(r.type) << " "
<< m_model.to_alias(m_config.using_namespace,
ns_relative(m_config.using_namespace, destination));
if (!r.label.empty())
ostr << " : " << r.label;
ostr << std::endl;
} }
} }
@@ -259,29 +280,39 @@ public:
continue; continue;
std::string destination; std::string destination;
if (r.destination.find("#") != std::string::npos || std::stringstream relstr;
r.destination.find("@") != std::string::npos) { try {
destination = m_model.usr_to_name( if (r.destination.find("#") != std::string::npos ||
m_config.using_namespace, r.destination); r.destination.find("@") != std::string::npos) {
if (destination.empty()) { destination = m_model.usr_to_name(
ostr << "' "; m_config.using_namespace, r.destination);
if (destination.empty()) {
relstr << "' ";
destination = r.destination;
}
}
else {
destination = r.destination; destination = r.destination;
} }
relstr << m_model.to_alias(m_config.using_namespace,
ns_relative(m_config.using_namespace, e.name))
<< " " << to_string(r.type) << " "
<< m_model.to_alias(m_config.using_namespace,
ns_relative(
m_config.using_namespace, destination));
if (!r.label.empty())
relstr << " : " << r.label;
relstr << std::endl;
ostr << relstr.str();
} }
else { catch (error::uml_alias_missing &ex) {
destination = r.destination; LOG_ERROR("Skipping {} relation from {} to {} due "
"to: {}",
to_string(r.type), e.name, destination, ex.what());
} }
ostr << m_model.to_alias(m_config.using_namespace,
ns_relative(m_config.using_namespace, e.name))
<< " " << to_string(r.type) << " "
<< m_model.to_alias(m_config.using_namespace,
ns_relative(m_config.using_namespace, destination));
if (!r.label.empty())
ostr << " : " << r.label;
ostr << std::endl;
} }
} }

View File

@@ -17,6 +17,7 @@
*/ */
#pragma once #pragma once
#include "util/error.h"
#include "util/util.h" #include "util/util.h"
#include <clang-c/CXCompilationDatabase.h> #include <clang-c/CXCompilationDatabase.h>
@@ -174,6 +175,13 @@ public:
void add_relationship(class_relationship &&cr) void add_relationship(class_relationship &&cr)
{ {
if (cr.destination.empty() || type_aliases.count(cr.destination) == 0) {
LOG_WARN(
"Skipping relationship '{}' - {} - '{}' due to missing alias",
cr.destination, to_string(cr.type), usr);
return;
}
auto it = std::find(relationships.begin(), relationships.end(), cr); auto it = std::find(relationships.begin(), relationships.end(), cr);
if (it == relationships.end()) if (it == relationships.end())
relationships.emplace_back(std::move(cr)); relationships.emplace_back(std::move(cr));
@@ -281,7 +289,8 @@ struct diagram {
} }
} }
return full_name; throw error::uml_alias_missing(
fmt::format("Missing alias for {}", full_name));
} }
std::string usr_to_name(const std::vector<std::string> &using_namespaces, std::string usr_to_name(const std::vector<std::string> &using_namespaces,

View File

@@ -659,9 +659,12 @@ void tu_visitor::process_template_method(
{ {
class_method m; class_method m;
m.name = util::trim(mf.name()); m.name = util::trim(mf.name());
m.type = cppast::to_string( if (mf.function().kind() == cppast::cpp_entity_kind::constructor_t)
static_cast<const cppast::cpp_member_function &>(mf.function()) m.type = "void";
.return_type()); else
m.type = cppast::to_string(
static_cast<const cppast::cpp_member_function &>(mf.function())
.return_type());
m.is_pure_virtual = false; m.is_pure_virtual = false;
m.is_virtual = false; m.is_virtual = false;
m.is_const = cppast::is_const( m.is_const = cppast::is_const(
@@ -747,16 +750,6 @@ void tu_visitor::process_function_parameter(
// so we have to deduce the correct namespace prefix of the // so we have to deduce the correct namespace prefix of the
// template which is being instantiated // template which is being instantiated
mp.type = cppast::to_string(param.type()); mp.type = cppast::to_string(param.type());
auto &template_instantiation_type =
static_cast<const cppast::cpp_template_instantiation_type &>(
param_type);
auto &primary_template_entity =
template_instantiation_type.primary_template();
auto trawname = cppast::to_string(template_instantiation_type);
auto pte = cx::util::fully_prefixed(ctx.namespace_,
primary_template_entity.get(ctx.entity_index)[0].get());
} }
else { else {
mp.type = cppast::to_string(param.type()); mp.type = cppast::to_string(param.type());
@@ -885,6 +878,13 @@ void tu_visitor::process_template_template_parameter(
void tu_visitor::process_friend(const cppast::cpp_friend &f, class_ &parent) void tu_visitor::process_friend(const cppast::cpp_friend &f, class_ &parent)
{ {
// Only process friends to other classes or class templates
if (!f.entity() ||
(f.entity().value().kind() != cppast::cpp_entity_kind::class_t) &&
(f.entity().value().kind() !=
cppast::cpp_entity_kind::class_template_t))
return;
class_relationship r; class_relationship r;
r.type = relationship_t::kFriendship; r.type = relationship_t::kFriendship;
r.label = "<<friend>>"; r.label = "<<friend>>";

29
src/util/error.h Normal file
View File

@@ -0,0 +1,29 @@
/**
* src/util/error.h
*
* Copyright (c) 2021 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 <stdexcept>
namespace clanguml::error {
struct uml_alias_missing : public virtual std::runtime_error {
uml_alias_missing(const std::string &message)
: std::runtime_error(message)
{
}
};
}

View File

@@ -34,6 +34,10 @@ std::string trim(const std::string &s);
#define __FILENAME__ \ #define __FILENAME__ \
(strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define LOG_ERROR(fmt__, ...) \
spdlog::error(std::string("[{}:{}] ") + fmt__, __FILENAME__, __LINE__, \
##__VA_ARGS__)
#define LOG_WARN(fmt__, ...) \ #define LOG_WARN(fmt__, ...) \
spdlog::warn(std::string("[{}:{}] ") + fmt__, __FILENAME__, __LINE__, \ spdlog::warn(std::string("[{}:{}] ") + fmt__, __FILENAME__, __LINE__, \
##__VA_ARGS__) ##__VA_ARGS__)