1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "doxygengenerator.h"
27
28 #include <cplusplus/CppDocument.h>
29 #include <cplusplus/SimpleLexer.h>
30
31 #include <utils/textutils.h>
32 #include <utils/fileutils.h>
33 #include <utils/qtcassert.h>
34
35 #include <QDebug>
36 #include <QRegularExpression>
37 #include <QTextBlock>
38 #include <QTextCursor>
39 #include <QTextDocument>
40
41 #include <limits>
42
43 using namespace CppTools;
44 using namespace CPlusPlus;
45
46 DoxygenGenerator::DoxygenGenerator() = default;
47
setStyle(DocumentationStyle style)48 void DoxygenGenerator::setStyle(DocumentationStyle style)
49 {
50 m_style = style;
51 }
52
setStartComment(bool start)53 void DoxygenGenerator::setStartComment(bool start)
54 {
55 m_startComment = start;
56 }
57
setGenerateBrief(bool get)58 void DoxygenGenerator::setGenerateBrief(bool get)
59 {
60 m_generateBrief = get;
61 }
62
setAddLeadingAsterisks(bool add)63 void DoxygenGenerator::setAddLeadingAsterisks(bool add)
64 {
65 m_addLeadingAsterisks = add;
66 }
67
lineBeforeCursor(const QTextCursor & cursor)68 static int lineBeforeCursor(const QTextCursor &cursor)
69 {
70 int line, column;
71 const bool converted = Utils::Text::convertPosition(cursor.document(), cursor.position(), &line,
72 &column);
73 QTC_ASSERT(converted, return std::numeric_limits<int>::max());
74
75 return line - 1;
76 }
77
generate(QTextCursor cursor,const CPlusPlus::Snapshot & snapshot,const Utils::FilePath & documentFilePath)78 QString DoxygenGenerator::generate(QTextCursor cursor,
79 const CPlusPlus::Snapshot &snapshot,
80 const Utils::FilePath &documentFilePath)
81 {
82 const QTextCursor initialCursor = cursor;
83
84 const QChar &c = cursor.document()->characterAt(cursor.position());
85 if (!c.isLetter() && c != QLatin1Char('_') && c != QLatin1Char('['))
86 return QString();
87
88 // Try to find what would be the declaration we are interested in.
89 SimpleLexer lexer;
90 QTextBlock block = cursor.block();
91 while (block.isValid()) {
92 const QString &text = block.text();
93 const Tokens &tks = lexer(text);
94 foreach (const Token &tk, tks) {
95 if (tk.is(T_SEMICOLON) || tk.is(T_LBRACE)) {
96 // No need to continue beyond this, we might already have something meaningful.
97 cursor.setPosition(block.position() + tk.utf16charsEnd(), QTextCursor::KeepAnchor);
98 break;
99 }
100 }
101
102 if (cursor.hasSelection())
103 break;
104
105 block = block.next();
106 }
107
108 if (!cursor.hasSelection())
109 return QString();
110
111 QString declCandidate = cursor.selectedText();
112
113 // remove attributes like [[nodiscard]] because
114 // Document::Ptr::parse(Document::ParseDeclaration) fails on attributes
115 static QRegularExpression attribute("\\[\\s*\\[.*\\]\\s*\\]");
116 declCandidate.replace(attribute, "");
117
118 declCandidate.replace("Q_INVOKABLE", "");
119 declCandidate.remove(QRegularExpression(R"(\s*(public|protected|private)\s*:\s*)"));
120 declCandidate.replace(QChar::ParagraphSeparator, QLatin1Char('\n'));
121
122 // Let's append a closing brace in the case we got content like 'class MyType {'
123 if (declCandidate.endsWith(QLatin1Char('{')))
124 declCandidate.append(QLatin1Char('}'));
125
126 Document::Ptr doc = snapshot.preprocessedDocument(declCandidate.toUtf8(),
127 documentFilePath,
128 lineBeforeCursor(initialCursor));
129 doc->parse(Document::ParseDeclaration);
130 doc->check(Document::FastCheck);
131
132 if (!doc->translationUnit()
133 || !doc->translationUnit()->ast()
134 || !doc->translationUnit()->ast()->asDeclaration()) {
135 return QString();
136 }
137
138 return generate(cursor, doc->translationUnit()->ast()->asDeclaration());
139 }
140
generate(QTextCursor cursor,DeclarationAST * decl)141 QString DoxygenGenerator::generate(QTextCursor cursor, DeclarationAST *decl)
142 {
143 SpecifierAST *spec = nullptr;
144 DeclaratorAST *decltr = nullptr;
145 if (SimpleDeclarationAST *simpleDecl = decl->asSimpleDeclaration()) {
146 if (simpleDecl->declarator_list
147 && simpleDecl->declarator_list->value) {
148 decltr = simpleDecl->declarator_list->value;
149 } else if (simpleDecl->decl_specifier_list
150 && simpleDecl->decl_specifier_list->value) {
151 spec = simpleDecl->decl_specifier_list->value;
152 }
153 } else if (FunctionDefinitionAST * defDecl = decl->asFunctionDefinition()) {
154 decltr = defDecl->declarator;
155 }
156
157 assignCommentOffset(cursor);
158
159 QString comment;
160 if (m_startComment)
161 writeStart(&comment);
162 writeNewLine(&comment);
163 writeContinuation(&comment);
164
165 if (decltr
166 && decltr->core_declarator
167 && decltr->core_declarator->asDeclaratorId()
168 && decltr->core_declarator->asDeclaratorId()->name) {
169 CoreDeclaratorAST *coreDecl = decltr->core_declarator;
170 if (m_generateBrief)
171 writeBrief(&comment, m_printer.prettyName(coreDecl->asDeclaratorId()->name->name));
172 else
173 writeNewLine(&comment);
174
175 if (decltr->postfix_declarator_list
176 && decltr->postfix_declarator_list->value
177 && decltr->postfix_declarator_list->value->asFunctionDeclarator()) {
178 FunctionDeclaratorAST *funcDecltr =
179 decltr->postfix_declarator_list->value->asFunctionDeclarator();
180 if (funcDecltr->parameter_declaration_clause
181 && funcDecltr->parameter_declaration_clause->parameter_declaration_list) {
182 for (ParameterDeclarationListAST *it =
183 funcDecltr->parameter_declaration_clause->parameter_declaration_list;
184 it;
185 it = it->next) {
186 ParameterDeclarationAST *paramDecl = it->value;
187 if (paramDecl->declarator
188 && paramDecl->declarator->core_declarator
189 && paramDecl->declarator->core_declarator->asDeclaratorId()
190 && paramDecl->declarator->core_declarator->asDeclaratorId()->name) {
191 DeclaratorIdAST *paramId =
192 paramDecl->declarator->core_declarator->asDeclaratorId();
193 writeContinuation(&comment);
194 writeCommand(&comment,
195 ParamCommand,
196 m_printer.prettyName(paramId->name->name));
197 }
198 }
199 }
200 if (funcDecltr->symbol
201 && funcDecltr->symbol->returnType().type()
202 && !funcDecltr->symbol->returnType()->isVoidType()
203 && !funcDecltr->symbol->returnType()->isUndefinedType()) {
204 writeContinuation(&comment);
205 writeCommand(&comment, ReturnCommand);
206 }
207 }
208 } else if (spec && m_generateBrief) {
209 bool briefWritten = false;
210 if (ClassSpecifierAST *classSpec = spec->asClassSpecifier()) {
211 if (classSpec->name) {
212 QString aggregate;
213 if (classSpec->symbol->isClass())
214 aggregate = QLatin1String("class");
215 else if (classSpec->symbol->isStruct())
216 aggregate = QLatin1String("struct");
217 else
218 aggregate = QLatin1String("union");
219 writeBrief(&comment,
220 m_printer.prettyName(classSpec->name->name),
221 QLatin1String("The"),
222 aggregate);
223 briefWritten = true;
224 }
225 } else if (EnumSpecifierAST *enumSpec = spec->asEnumSpecifier()) {
226 if (enumSpec->name) {
227 writeBrief(&comment,
228 m_printer.prettyName(enumSpec->name->name),
229 QLatin1String("The"),
230 QLatin1String("enum"));
231 briefWritten = true;
232 }
233 }
234 if (!briefWritten)
235 writeNewLine(&comment);
236 } else {
237 writeNewLine(&comment);
238 }
239
240 writeEnd(&comment);
241
242 return comment;
243 }
244
startMark() const245 QChar DoxygenGenerator::startMark() const
246 {
247 if (m_style == QtStyle)
248 return QLatin1Char('!');
249 return QLatin1Char('*');
250 }
251
styleMark() const252 QChar DoxygenGenerator::styleMark() const
253 {
254 if (m_style == QtStyle || m_style == CppStyleA || m_style == CppStyleB)
255 return QLatin1Char('\\');
256 return QLatin1Char('@');
257 }
258
commandSpelling(Command command)259 QString DoxygenGenerator::commandSpelling(Command command)
260 {
261 if (command == ParamCommand)
262 return QLatin1String("param ");
263 if (command == ReturnCommand)
264 return QLatin1String("return ");
265
266 QTC_ASSERT(command == BriefCommand, return QString());
267 return QLatin1String("brief ");
268 }
269
writeStart(QString * comment) const270 void DoxygenGenerator::writeStart(QString *comment) const
271 {
272 if (m_style == CppStyleA)
273 comment->append(QLatin1String("///"));
274 if (m_style == CppStyleB)
275 comment->append(QLatin1String("//!"));
276 else
277 comment->append(offsetString() + "/*" + startMark());
278 }
279
writeEnd(QString * comment) const280 void DoxygenGenerator::writeEnd(QString *comment) const
281 {
282 if (m_style == CppStyleA)
283 comment->append(QLatin1String("///"));
284 else if (m_style == CppStyleB)
285 comment->append(QLatin1String("//!"));
286 else
287 comment->append(offsetString() + " */");
288 }
289
writeContinuation(QString * comment) const290 void DoxygenGenerator::writeContinuation(QString *comment) const
291 {
292 if (m_style == CppStyleA)
293 comment->append(offsetString() + "///");
294 else if (m_style == CppStyleB)
295 comment->append(offsetString() + "//!");
296 else if (m_addLeadingAsterisks)
297 comment->append(offsetString() + " *");
298 else
299 comment->append(offsetString() + " ");
300 }
301
writeNewLine(QString * comment) const302 void DoxygenGenerator::writeNewLine(QString *comment) const
303 {
304 comment->append(QLatin1Char('\n'));
305 }
306
writeCommand(QString * comment,Command command,const QString & commandContent) const307 void DoxygenGenerator::writeCommand(QString *comment,
308 Command command,
309 const QString &commandContent) const
310 {
311 comment->append(' ' + styleMark() + commandSpelling(command) + commandContent + '\n');
312 }
313
writeBrief(QString * comment,const QString & brief,const QString & prefix,const QString & suffix)314 void DoxygenGenerator::writeBrief(QString *comment,
315 const QString &brief,
316 const QString &prefix,
317 const QString &suffix)
318 {
319 QString content = prefix + ' ' + brief + ' ' + suffix;
320 writeCommand(comment, BriefCommand, content.trimmed());
321 }
322
assignCommentOffset(QTextCursor cursor)323 void DoxygenGenerator::assignCommentOffset(QTextCursor cursor)
324 {
325 if (cursor.hasSelection()) {
326 if (cursor.anchor() < cursor.position())
327 cursor.setPosition(cursor.anchor());
328 }
329
330 cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
331 m_commentOffset = cursor.selectedText();
332 }
333
offsetString() const334 QString DoxygenGenerator::offsetString() const
335 {
336 return m_commentOffset;
337 }
338