0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
clang_visitor.cc
Go to the documentation of this file.
1/**
2 * @file src/common/visitor/comment/clang_visitor.cc
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
19#include "clang_visitor.h"
20#include "util/util.h"
21
22#if LLVM_VERSION_MAJOR > 17
23#define CLANG_UML_LLVM_COMMENT_KIND(COMMENT_KIND) \
24 clang::comments::CommentKind::COMMENT_KIND
25#else
26#define CLANG_UML_LLVM_COMMENT_KIND(COMMENT_KIND) \
27 clang::comments::Comment::COMMENT_KIND##Kind
28#endif
29
31
32clang_visitor::clang_visitor(clang::SourceManager &source_manager)
33 : comment_visitor{source_manager}
34{
35}
36
38 const clang::NamedDecl &decl, common::model::decorated_element &e)
39{
40 const auto *comment =
41 decl.getASTContext().getRawCommentForDeclNoCache(&decl);
42
43 if (comment == nullptr) {
44 return;
45 }
46
47 auto raw_comment = comment->getRawText(source_manager());
48
49 auto formatted_comment = comment->getFormattedText(
50 source_manager(), decl.getASTContext().getDiagnostics());
51
52 common::model::comment_t cmt = inja::json::object();
53 cmt["raw"] = raw_comment;
54 cmt["formatted"] = formatted_comment;
55
56 using clang::comments::BlockCommandComment;
57 using clang::comments::FullComment;
58 using clang::comments::ParagraphComment;
59 using clang::comments::ParamCommandComment;
60 using clang::comments::TextComment;
61 using clang::comments::TParamCommandComment;
62
63 FullComment *full_comment =
64 comment->parse(decl.getASTContext(), nullptr, &decl);
65
66 const auto &traits = decl.getASTContext().getCommentCommandTraits();
67
68 for (const auto *block : full_comment->getBlocks()) {
69 const auto block_kind = block->getCommentKind();
70 if (block_kind == CLANG_UML_LLVM_COMMENT_KIND(ParagraphComment)) {
71 std::string paragraph_text;
72
73 visit_paragraph(clang::dyn_cast<ParagraphComment>(block), traits,
74 paragraph_text);
75 if (!cmt.contains("text"))
76 cmt["text"] = "";
77
78 cmt["text"] =
79 cmt["text"].get<std::string>() + "\n" + paragraph_text;
80
81 if (!cmt.contains("paragraph"))
82 cmt["paragraph"] = inja::json::array();
83
84 cmt["paragraph"].push_back(paragraph_text);
85 }
86 else if (block_kind == CLANG_UML_LLVM_COMMENT_KIND(TextComment)) {
87 // TODO
88 }
89 else if (block_kind ==
90 CLANG_UML_LLVM_COMMENT_KIND(ParamCommandComment)) {
92 clang::dyn_cast<ParamCommandComment>(block), traits, cmt);
93 }
94 else if (block_kind ==
95 CLANG_UML_LLVM_COMMENT_KIND(TParamCommandComment)) {
97 clang::dyn_cast<TParamCommandComment>(block), traits, cmt);
98 }
99 else if (block_kind ==
100 CLANG_UML_LLVM_COMMENT_KIND(BlockCommandComment)) {
101 if (const auto *command =
102 clang::dyn_cast<BlockCommandComment>(block);
103 command != nullptr) {
104 const auto *command_info =
105 traits.getCommandInfo(command->getCommandID());
106
107 if (command_info->IsBlockCommand &&
108 command_info->NumArgs == 0U) {
109 // Visit block command with a single text argument, e.g.:
110 // \brief text
111 // \todo text
112 // ...
113 visit_block_command(command, traits, cmt);
114 }
115 else if (command_info->IsParamCommand) {
116 // Visit function param block:
117 // \param arg text
119 clang::dyn_cast<ParamCommandComment>(command), traits,
120 cmt);
121 }
122 else if (command_info->IsTParamCommand) {
123 // Visit template param block:
124 // \tparam typename text
126 clang::dyn_cast<TParamCommandComment>(command), traits,
127 cmt);
128 }
129 }
130 }
131 }
132 e.set_comment(cmt);
133}
134
136 const clang::comments::BlockCommandComment *command,
137 const clang::comments::CommandTraits &traits, common::model::comment_t &cmt)
138{
139 using clang::comments::Comment;
140 using clang::comments::ParagraphComment;
141 using clang::comments::TextComment;
142
143 std::string command_text;
144
145 for (const auto *paragraph_it = command->child_begin();
146 paragraph_it != command->child_end(); ++paragraph_it) {
147
148 if ((*paragraph_it)->getCommentKind() ==
149 CLANG_UML_LLVM_COMMENT_KIND(ParagraphComment)) {
150 visit_paragraph(clang::dyn_cast<ParagraphComment>(*paragraph_it),
151 traits, command_text);
152 }
153 }
154
155 const auto command_name = command->getCommandName(traits).str();
156 if (!command_text.empty()) {
157 if (!cmt.contains(command_name))
158 cmt[command_name] = inja::json::array();
159
160 cmt[command_name].push_back(command_text);
161 }
162}
163
165 const clang::comments::ParamCommandComment *command,
166 const clang::comments::CommandTraits &traits, common::model::comment_t &cmt)
167{
168 using clang::comments::Comment;
169 using clang::comments::ParagraphComment;
170 using clang::comments::TextComment;
171
172 std::string description;
173
174 if (command == nullptr)
175 return;
176
177 const auto name = command->getParamNameAsWritten().str();
178
179 for (const auto *it = command->child_begin(); it != command->child_end();
180 ++it) {
181
182 if ((*it)->getCommentKind() ==
183 CLANG_UML_LLVM_COMMENT_KIND(ParagraphComment)) {
185 clang::dyn_cast<ParagraphComment>(*it), traits, description);
186 }
187 }
188
189 if (!name.empty()) {
190 if (!cmt.contains("param"))
191 cmt["param"] = inja::json::array();
192
193 inja::json param = inja::json::object();
194 param["name"] = name;
195 param["description"] = util::trim(description);
196 cmt["param"].push_back(std::move(param));
197 }
198}
199
201 const clang::comments::TParamCommandComment *command,
202 const clang::comments::CommandTraits &traits, common::model::comment_t &cmt)
203{
204 using clang::comments::Comment;
205 using clang::comments::ParagraphComment;
206 using clang::comments::TextComment;
207
208 std::string description;
209
210 if (command == nullptr)
211 return;
212
213 const auto name = command->getParamNameAsWritten().str();
214
215 for (const auto *it = command->child_begin(); it != command->child_end();
216 ++it) {
217 if ((*it)->getCommentKind() ==
218 CLANG_UML_LLVM_COMMENT_KIND(ParagraphComment)) {
220 clang::dyn_cast<ParagraphComment>(*it), traits, description);
221 }
222 }
223
224 if (!name.empty()) {
225 if (!cmt.contains("tparam"))
226 cmt["tparam"] = inja::json::array();
227
228 inja::json param = inja::json::object();
229 param["name"] = name;
230 param["description"] = util::trim(description);
231 cmt["tparam"].push_back(std::move(param));
232 }
233}
234
236 const clang::comments::ParagraphComment *paragraph,
237 const clang::comments::CommandTraits & /*traits*/, std::string &text)
238{
239 using clang::comments::Comment;
240 using clang::comments::TextComment;
241
242 if (paragraph == nullptr)
243 return;
244
245 for (const auto *text_it = paragraph->child_begin();
246 text_it != paragraph->child_end(); ++text_it) {
247
248 if ((*text_it)->getCommentKind() ==
249 CLANG_UML_LLVM_COMMENT_KIND(TextComment) &&
250 clang::dyn_cast<TextComment>(*text_it) != nullptr) {
251 // Merge paragraph lines into a single string
252 text += clang::dyn_cast<TextComment>(*text_it)->getText();
253 text += "\n";
254 }
255 }
256}
257
258} // namespace clanguml::common::visitor::comment