0.5.4
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-2024 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
46 std::vector<std::pair<eid_t, common::model::relationship_t>>;
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 {
163 namespace fs = std::filesystem;
164
165 std::string file;
166 unsigned line{};
167 unsigned column{};
168
169 if (location.isValid()) {
170 file = source_manager_.getFilename(location).str();
171 line = source_manager_.getSpellingLineNumber(location);
172 column = source_manager_.getSpellingColumnNumber(location);
173
174 if (file.empty()) {
175 // Why do I have to do this?
176 parse_source_location(location.printToString(source_manager()),
177 file, line, column);
178 }
179 }
180 else {
181 auto success = parse_source_location(
182 location.printToString(source_manager()), file, line, column);
183 if (!success) {
184 LOG_DBG("Failed to extract source location for element from {}",
185 location.printToString(source_manager_));
186 return;
187 }
188 }
189
190 // ensure the path is absolute
191 fs::path file_path{file};
192 if (!file_path.is_absolute()) {
193 file_path = fs::absolute(file_path);
194 }
195
196 file_path = fs::weakly_canonical(file_path);
197
198 file = file_path.string();
199
200 element.set_file(file);
201
202 if (util::is_relative_to(file_path, relative_to_path_)) {
204 fs::relative(element.file(), relative_to_path_).string()));
205 }
206 else {
207 element.set_file_relative("");
208 }
209
210 element.set_translation_unit(tu_path().string());
211 element.set_line(line);
212 element.set_column(column);
213 element.set_location_id(location.getHashValue());
214 }
215
217 const clang::Decl &decl, clanguml::common::model::element &element)
218 {
219 if (const clang::Module *module = decl.getOwningModule();
220 module != nullptr) {
221 std::string module_name = module->Name;
222 bool is_private{false};
223#if LLVM_VERSION_MAJOR < 15
224 is_private = module->Kind ==
225 clang::Module::ModuleKind::PrivateModuleFragment;
226#else
227 is_private = module->isPrivateModule();
228#endif
229 if (is_private) {
230 // Clang just maps private modules names to "<private>"
231 module_name = module->getTopLevelModule()->Name;
232 }
233 element.set_module(module_name);
234 element.set_module_private(is_private);
235 }
236 }
237
239 std::unique_ptr<common::model::template_element> element)
240 {
241 }
242
243 /**
244 * @brief Process comment directives in comment attached to a declaration
245 *
246 * @param decl Reference to @ref clang::NamedDecl
247 * @param element Reference to element to be updated
248 */
249 void process_comment(const clang::NamedDecl &decl,
251 {
252 assert(comment_visitor_.get() != nullptr);
253
254 comment_visitor_->visit(decl, e);
255
256 auto *comment = decl.getASTContext().getRawCommentForDeclNoCache(&decl);
257
258 process_comment(comment, decl.getASTContext().getDiagnostics(), e);
259 }
260
261 /**
262 * @brief Process comment directives in raw comment
263 *
264 * @param comment clang::RawComment pointer
265 * @param de Reference to clang::DiagnosticsEngine
266 * @param element Reference to element to be updated
267 * @return Comment with uml directives stripped from it
268 */
269 [[maybe_unused]] std::string process_comment(
270 const clang::RawComment *comment, clang::DiagnosticsEngine &de,
272 {
273 if (comment == nullptr)
274 return {};
275
276 auto [it, inserted] = processed_comments().emplace(comment);
277
278 if (!inserted)
279 return {};
280
281 // Process clang-uml decorators in the comments
282 // TODO: Refactor to use standard block comments processable by
283 // clang comments
284 const auto &[decorators, stripped_comment] =
285 decorators::parse(comment->getFormattedText(source_manager(), de));
286
287 e.add_decorators(decorators);
288
289 return stripped_comment;
290 }
291
292 bool skip_system_header_decl(const clang::NamedDecl *decl) const
293 {
294 return !config().include_system_headers() &&
295 source_manager().isInSystemHeader(
296 decl->getSourceRange().getBegin());
297 }
298
299 /**
300 * @brief Check if the diagram should include a declaration.
301 *
302 * @param decl Clang declaration.
303 * @return True, if the entity should be included in the diagram.
304 */
305 bool should_include(const clang::NamedDecl *decl) const
306 {
307 if (decl == nullptr)
308 return false;
309
310 if (skip_system_header_decl(decl))
311 return false;
312
313 if (config().filter_mode() == config::filter_mode_t::advanced)
314 return true;
315
316 auto should_include_namespace = diagram().should_include(
317 common::model::namespace_{decl->getQualifiedNameAsString()});
318
319 const auto decl_file =
320 decl->getLocation().printToString(source_manager());
321
322 const auto should_include_decl_file =
323 diagram().should_include(common::model::source_file{decl_file});
324
325 return should_include_namespace && should_include_decl_file;
326 }
327
328 /**
329 * @brief Get diagram model reference
330 *
331 * @return Reference to diagram model created by the visitor
332 */
333 DiagramT &diagram() { return diagram_; }
334
335 /**
336 * @brief Get diagram model reference
337 *
338 * @return Reference to diagram model created by the visitor
339 */
340 const DiagramT &diagram() const { return diagram_; }
341
342 /**
343 * @brief Get diagram config instance
344 *
345 * @return Reference to config instance
346 */
347 const ConfigT &config() const { return config_; }
348
349protected:
350 std::set<const clang::RawComment *> &processed_comments()
351 {
352 return processed_comments_;
353 }
354
355private:
356 // Reference to the output diagram model
357 DiagramT &diagram_;
358
359 // Reference to class diagram config
360 const ConfigT &config_;
361
362 clang::SourceManager &source_manager_;
363
364 std::unique_ptr<comment::comment_visitor> comment_visitor_;
365
366 std::filesystem::path relative_to_path_;
367
368 std::filesystem::path translation_unit_path_;
369
370 std::set<const clang::RawComment *> processed_comments_;
371
373};
374} // namespace clanguml::common::visitor