diff --git a/README.md b/README.md index 44eb556f..0fefa914 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,15 @@ TODO ## 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 ### Configuration file format and examples diff --git a/src/puml/class_diagram_generator.h b/src/puml/class_diagram_generator.h index bd1d9441..c274addd 100644 --- a/src/puml/class_diagram_generator.h +++ b/src/puml/class_diagram_generator.h @@ -199,47 +199,68 @@ public: if (m_config.should_include_relationship("inheritance")) for (const auto &b : c.bases) { - ostr << m_model.to_alias(m_config.using_namespace, - ns_relative(m_config.using_namespace, b.name)) - << " <|-- " - << m_model.to_alias(m_config.using_namespace, - ns_relative(m_config.using_namespace, c.name)) - << std::endl; + std::stringstream relstr; + try { + relstr << m_model.to_alias(m_config.using_namespace, + ns_relative(m_config.using_namespace, b.name)) + << " <|-- " + << 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) { if (!m_config.should_include_relationship(name(r.type))) continue; - std::string destination; - 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); + std::stringstream relstr; - // If something went wrong and we have an empty destination - // generate the relationship but comment it out for - // debugging - if (destination.empty()) { - ostr << "' "; + std::string destination; + try { + 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 + // generate the relationship but comment it out for + // debugging + if (destination.empty()) { + relstr << "' "; + destination = r.destination; + } + } + else { 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 { - destination = r.destination; + catch (error::uml_alias_missing &e) { + 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; std::string destination; - 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 (destination.empty()) { - ostr << "' "; + std::stringstream relstr; + try { + 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 (destination.empty()) { + relstr << "' "; + destination = r.destination; + } + } + else { 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 { - destination = r.destination; + catch (error::uml_alias_missing &ex) { + 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; } } diff --git a/src/uml/class_diagram_model.h b/src/uml/class_diagram_model.h index c381a372..c7a70c19 100644 --- a/src/uml/class_diagram_model.h +++ b/src/uml/class_diagram_model.h @@ -17,6 +17,7 @@ */ #pragma once +#include "util/error.h" #include "util/util.h" #include @@ -174,6 +175,13 @@ public: 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); if (it == relationships.end()) 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 &using_namespaces, diff --git a/src/uml/class_diagram_visitor.cc b/src/uml/class_diagram_visitor.cc index 7b7be600..731ab0dd 100644 --- a/src/uml/class_diagram_visitor.cc +++ b/src/uml/class_diagram_visitor.cc @@ -659,9 +659,12 @@ void tu_visitor::process_template_method( { class_method m; m.name = util::trim(mf.name()); - m.type = cppast::to_string( - static_cast(mf.function()) - .return_type()); + if (mf.function().kind() == cppast::cpp_entity_kind::constructor_t) + m.type = "void"; + else + m.type = cppast::to_string( + static_cast(mf.function()) + .return_type()); m.is_pure_virtual = false; m.is_virtual = false; 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 // template which is being instantiated mp.type = cppast::to_string(param.type()); - - auto &template_instantiation_type = - static_cast( - 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 { 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) { + // 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; r.type = relationship_t::kFriendship; r.label = "<>"; diff --git a/src/util/error.h b/src/util/error.h new file mode 100644 index 00000000..661b7967 --- /dev/null +++ b/src/util/error.h @@ -0,0 +1,29 @@ +/** + * src/util/error.h + * + * Copyright (c) 2021 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. + */ +#pragma once + +#include + +namespace clanguml::error { +struct uml_alias_missing : public virtual std::runtime_error { + uml_alias_missing(const std::string &message) + : std::runtime_error(message) + { + } +}; +} diff --git a/src/util/util.h b/src/util/util.h index 574a70e2..e8794f67 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -34,6 +34,10 @@ std::string trim(const std::string &s); #define __FILENAME__ \ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#define LOG_ERROR(fmt__, ...) \ + spdlog::error(std::string("[{}:{}] ") + fmt__, __FILENAME__, __LINE__, \ + ##__VA_ARGS__) + #define LOG_WARN(fmt__, ...) \ spdlog::warn(std::string("[{}:{}] ") + fmt__, __FILENAME__, __LINE__, \ ##__VA_ARGS__)