0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
translation_unit_visitor.h
Go to the documentation of this file.
1/**
2 * @file src/common/visitor/translation_unit_visitor.h
3 *
4 * Copyright (c) 2021-2025 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18#pragma once
19
23#include "common/clang_utils.h"
29#include "config/config.h"
30
31#include <clang/AST/Comment.h>
32#include <clang/AST/Expr.h>
33#include <clang/AST/RawCommentList.h>
34#include <clang/Basic/Module.h>
35#include <clang/Basic/SourceManager.h>
36
37#include <deque>
38#include <functional>
39#include <map>
40#include <memory>
41#include <string>
42
44
45using found_relationships_t = std::vector<
46 std::tuple<eid_t, common::model::relationship_t, const clang::Decl *>>;
47
48/**
49 * @brief Diagram translation unit visitor base class
50 *
51 * This class provides common interface for diagram translation unit
52 * visitors.
53 */
54template <typename ConfigT, typename DiagramT> class translation_unit_visitor {
55public:
56 using config_t = ConfigT;
57 using diagram_t = DiagramT;
58
59 /**
60 * @brief Constructor
61 *
62 * @param sm Reference to @ref clang::SourceManager instance
63 * @param config Reference to @ref clanguml::config::diagram configuration
64 * instance
65 */
67 clang::SourceManager &sm, DiagramT &diagram, const ConfigT &config)
70 , source_manager_{sm}
71 , relative_to_path_{config.root_directory()}
72 {
73 if (config.comment_parser() == config::comment_parser_t::plain) {
75 std::make_unique<comment::plain_visitor>(source_manager_);
76 }
77 else if (config.comment_parser() == config::comment_parser_t::clang) {
79 std::make_unique<comment::clang_visitor>(source_manager_);
80 }
81 }
82
83 virtual ~translation_unit_visitor() = default;
84
85 void set_tu_path(const std::string &translation_unit_path)
86 {
87 translation_unit_path_ = relative(
88 std::filesystem::path{translation_unit_path}, relative_to_path_);
89 translation_unit_path_.make_preferred();
90 }
91
92 /**
93 * @brief Return relative path to current translation unit
94 * @return Current translation unit path
95 */
96 const std::filesystem::path &tu_path() const
97 {
99 }
100
101 /**
102 * @brief Get reference to Clang AST to clang-uml id mapper
103 *
104 * @return Reference to Clang AST to clang-uml id mapper
105 */
107
108 /**
109 * @brief Get clang::SourceManager
110 * @return Reference to @ref clang::SourceManager used by this translation
111 * unit visitor
112 */
113 clang::SourceManager &source_manager() const { return source_manager_; }
114
115 /**
116 * @brief Set source location in diagram element
117 *
118 * @param decl Reference to @ref clang::Decl
119 * @param element Reference to element to be updated
120 */
121 void set_source_location(const clang::Decl &decl,
123 {
124 set_source_location(decl.getLocation(), element);
125 }
126
127 /**
128 * @brief Set source location in diagram element
129 *
130 * @param expr Reference to @ref clang::Expr
131 * @param element Reference to element to be updated
132 */
133 void set_source_location(const clang::Expr &expr,
135 {
136 set_source_location(expr.getBeginLoc(), element);
137 }
138
139 void set_source_location(const clang::Stmt &stmt,
141 {
142 set_source_location(stmt.getBeginLoc(), element);
143 }
144
146 const clang::NamedDecl &decl, clanguml::common::model::element &element)
147 {
148 common::model::namespace_ ns{decl.getQualifiedNameAsString()};
149 element.set_name(ns.name());
150 ns.pop_back();
151 element.set_namespace(ns);
152 }
153
154 /**
155 * @brief Set source location in diagram element
156 *
157 * @param location Reference to @ref clang::SourceLocation
158 * @param element Reference to element to be updated
159 */
160 void set_source_location(const clang::SourceLocation &location,
162 {
164 source_manager(), location, element, tu_path(), relative_to_path_);
165 }
166
168 const clang::Decl &decl, clanguml::common::model::element &element)
169 {
170 if (const clang::Module *module = decl.getOwningModule();
171 module != nullptr) {
172 std::string module_name = module->Name;
173 bool is_private{false};
174#if LLVM_VERSION_MAJOR < 15
175 is_private = module->Kind ==
176 clang::Module::ModuleKind::PrivateModuleFragment;
177#else
178 is_private = module->isPrivateModule();
179#endif
180 if (is_private) {
181 // Clang just maps private modules names to "<private>"
182 module_name = module->getTopLevelModule()->Name;
183 }
184 element.set_module(module_name);
185 element.set_module_private(is_private);
186 }
187 }
188
190 std::unique_ptr<common::model::template_element> element)
191 {
192 }
193
194 /**
195 * @brief Process comment directives in comment attached to a declaration
196 *
197 * @param decl Reference to @ref clang::NamedDecl
198 * @param element Reference to element to be updated
199 */
200 void process_comment(const clang::NamedDecl &decl,
202 {
203 assert(comment_visitor_.get() != nullptr);
204
205 comment_visitor_->visit(decl, e);
206
207 auto *comment = decl.getASTContext().getRawCommentForDeclNoCache(&decl);
208
209 process_comment(comment, decl.getASTContext().getDiagnostics(), e);
210 }
211
212 /**
213 * @brief Process comment directives in raw comment
214 *
215 * @param comment clang::RawComment pointer
216 * @param de Reference to clang::DiagnosticsEngine
217 * @param element Reference to element to be updated
218 * @return Comment with uml directives stripped from it
219 */
220 [[maybe_unused]] std::string process_comment(
221 const clang::RawComment *comment, clang::DiagnosticsEngine &de,
223 {
224 if (comment == nullptr)
225 return {};
226
227 auto [it, inserted] = processed_comments().emplace(comment);
228
229 if (!inserted)
230 return {};
231
232 // Process clang-uml decorators in the comments
233 // TODO: Refactor to use standard block comments processable by
234 // clang comments
235 const auto &[decorators, stripped_comment] =
236 decorators::parse(comment->getFormattedText(source_manager(), de));
237
238 e.add_decorators(decorators);
239
240 return stripped_comment;
241 }
242
243 bool skip_system_header_decl(const clang::NamedDecl *decl) const
244 {
245 return !config().include_system_headers() &&
246 source_manager().isInSystemHeader(
247 decl->getSourceRange().getBegin());
248 }
249
250 /**
251 * @brief Check if the diagram should include a declaration.
252 *
253 * @param decl Clang declaration.
254 * @return True, if the entity should be included in the diagram.
255 */
256 bool should_include(const clang::NamedDecl *decl) const
257 {
258 if (decl == nullptr)
259 return false;
260
261 if (skip_system_header_decl(decl))
262 return false;
263
264 if (config().filter_mode() == config::filter_mode_t::advanced)
265 return true;
266
267 auto should_include_namespace = diagram().should_include(
268 common::model::namespace_{decl->getQualifiedNameAsString()});
269
270 const auto decl_file =
271 decl->getLocation().printToString(source_manager());
272
273 const auto should_include_decl_file = diagram().should_include(
275
276 return should_include_namespace && should_include_decl_file;
277 }
278
279 /**
280 * @brief Get diagram model reference
281 *
282 * @return Reference to diagram model created by the visitor
283 */
284 DiagramT &diagram() { return diagram_; }
285
286 /**
287 * @brief Get diagram model reference
288 *
289 * @return Reference to diagram model created by the visitor
290 */
291 const DiagramT &diagram() const { return diagram_; }
292
293 /**
294 * @brief Get diagram config instance
295 *
296 * @return Reference to config instance
297 */
298 const ConfigT &config() const { return config_; }
299
300protected:
301 std::set<const clang::RawComment *> &processed_comments()
302 {
303 return processed_comments_;
304 }
305
306 std::string get_file_path(const std::string &file_location) const
307 {
308 std::string file_path;
309 unsigned line{};
310 unsigned column{};
311
313 file_location, file_path, line, column))
314 return file_path;
315
316 return file_location;
317 }
318
319private:
320 // Reference to the output diagram model
321 DiagramT &diagram_;
322
323 // Reference to class diagram config
324 const ConfigT &config_;
325
326 clang::SourceManager &source_manager_;
327
328 std::unique_ptr<comment::comment_visitor> comment_visitor_;
329
330 std::filesystem::path relative_to_path_;
331
332 std::filesystem::path translation_unit_path_;
333
334 std::set<const clang::RawComment *> processed_comments_;
335
337};
338} // namespace clanguml::common::visitor