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