aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/cplusplus/declarationcomments.cpp
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2023-08-08 12:50:01 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2023-08-10 14:26:27 +0000
commit74a0313fcf0aeffdaad9583db2ca8dc5f78e99a7 (patch)
tree0ffb5a82fa86452cfb7dfe6e1f82f672321a85a7 /src/libs/cplusplus/declarationcomments.cpp
parentb128c585d951082f23faf609f1599944812dcdfa (diff)
CPlusPlus: Support associating comments with a declaration
This will serve as the basic building block for several comment-related features. Task-number: QTCREATORBUG-6934 Task-number: QTCREATORBUG-12051 Task-number: QTCREATORBUG-13877 Change-Id: Ic68587c0d7985dc731da9f539884590fcec764de Reviewed-by: David Schulz <david.schulz@qt.io>
Diffstat (limited to 'src/libs/cplusplus/declarationcomments.cpp')
-rw-r--r--src/libs/cplusplus/declarationcomments.cpp145
1 files changed, 145 insertions, 0 deletions
diff --git a/src/libs/cplusplus/declarationcomments.cpp b/src/libs/cplusplus/declarationcomments.cpp
new file mode 100644
index 00000000000..2283fa847ff
--- /dev/null
+++ b/src/libs/cplusplus/declarationcomments.cpp
@@ -0,0 +1,145 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "declarationcomments.h"
+
+#include <cplusplus/ASTPath.h>
+#include <cplusplus/CppDocument.h>
+#include <cplusplus/Overview.h>
+
+#include <utils/algorithm.h>
+
+#include <QRegularExpression>
+#include <QTextBlock>
+#include <QTextDocument>
+
+namespace CPlusPlus {
+
+QList<Token> commentsForDeclaration(const Symbol *symbol, const Snapshot &snapshot,
+ const QTextDocument &textDoc)
+{
+ // Set up cpp document.
+ const Document::Ptr cppDoc = snapshot.preprocessedDocument(textDoc.toPlainText().toUtf8(),
+ symbol->filePath());
+ cppDoc->parse();
+ TranslationUnit * const tu = cppDoc->translationUnit();
+ if (!tu || !tu->isParsed())
+ return {};
+
+ // Find the symbol declaration's AST node.
+ // We stop at the last declaration node that precedes the symbol, except:
+ // - For parameter declarations, we just continue, because we are interested in the function.
+ // - If the declaration node is preceded directly by another one, we choose that one instead,
+ // because with nested declarations we want the outer one (e.g. templates).
+ int line, column;
+ tu->getTokenPosition(symbol->sourceLocation(), &line, &column);
+ const QList<AST *> astPath = ASTPath(cppDoc)(line, column);
+ if (astPath.isEmpty())
+ return {};
+ if (astPath.last()->firstToken() != symbol->sourceLocation())
+ return {};
+ const AST *declAst = nullptr;
+ bool needsSymbolReference = false;
+ bool isParameter = false;
+ for (auto it = std::next(std::rbegin(astPath)); it != std::rend(astPath); ++it) {
+ AST * const node = *it;
+ if (node->asParameterDeclaration()) {
+ needsSymbolReference = true;
+ isParameter = true;
+ continue;
+ }
+ if (node->asDeclaration()) {
+ declAst = node;
+ continue;
+ }
+ if (declAst)
+ break;
+ }
+ if (!declAst)
+ return {};
+
+ // Get the list of all tokens (including comments) and find the declaration start token there.
+ const Token &declToken = tu->tokenAt(declAst->firstToken());
+ std::vector<Token> allTokens = tu->allTokens();
+ QTC_ASSERT(!allTokens.empty(), return {});
+ int tokenPos = -1;
+ for (int i = 0; i < int(allTokens.size()); ++i) {
+ if (allTokens.at(i).byteOffset == declToken.byteOffset) {
+ tokenPos = i;
+ break;
+ }
+ }
+ if (tokenPos == -1)
+ return {};
+
+ // Go backwards in the token list and collect all associated comments.
+ struct Comment {
+ Token token;
+ QTextBlock startBlock;
+ QTextBlock endBlock;
+ };
+ QList<Comment> comments;
+ Kind commentKind = T_EOF_SYMBOL;
+ const auto blockForTokenStart = [&](const Token &tok) {
+ return textDoc.findBlock(tu->getTokenPositionInDocument(tok, &textDoc));
+ };
+ const auto blockForTokenEnd = [&](const Token &tok) {
+ return textDoc.findBlock(tu->getTokenEndPositionInDocument(tok, &textDoc));
+ };
+ for (int i = tokenPos - 1; i >= 0; --i) {
+ const Token &tok = allTokens.at(i);
+ if (!tok.isComment())
+ break;
+ const QTextBlock tokenEndBlock = blockForTokenEnd(tok);
+ if (commentKind == T_EOF_SYMBOL) {
+ if (tokenEndBlock.next() != blockForTokenStart(declToken))
+ needsSymbolReference = true;
+ commentKind = tok.kind();
+ } else {
+ // If it's not the same kind of comment, it's not part of our comment block.
+ if (tok.kind() != commentKind)
+ break;
+
+ // If there are empty lines between the comments, we don't consider them as
+ // belonging together.
+ if (tokenEndBlock.next() != comments.first().startBlock)
+ break;
+ }
+
+ comments.push_front({tok, blockForTokenStart(tok), tokenEndBlock});
+ }
+
+ if (comments.isEmpty())
+ return {};
+
+ const auto tokenList = [&] {
+ return Utils::transform<QList<Token>>(comments, &Comment::token);
+ };
+
+ // We consider the comment block as associated with the symbol if it
+ // a) precedes it directly, without any empty lines in between or
+ // b) the symbol name occurs in it.
+ // Obviously, this heuristic can yield false positives in the case of very short names,
+ // but if a symbol is important enough to get documented, it should also have a proper name.
+ // Note that for function parameters, we always require the name to occur in the comment.
+
+ if (!needsSymbolReference) // a)
+ return tokenList();
+
+ // b)
+ const QString symbolName = Overview().prettyName(symbol->name());
+ const Kind tokenKind = comments.first().token.kind();
+ const bool isDoxygenComment = tokenKind == T_DOXY_COMMENT || tokenKind == T_CPP_DOXY_COMMENT;
+ const QRegularExpression symbolRegExp(QString("%1\\b%2\\b").arg(
+ isParameter && isDoxygenComment ? "[\\@]param\\s+" : QString(), symbolName));
+ for (const Comment &c : std::as_const(comments)) {
+ for (QTextBlock b = c.startBlock; b.blockNumber() <= c.endBlock.blockNumber();
+ b = b.next()) {
+ if (b.text().contains(symbolRegExp))
+ return tokenList();
+ }
+ }
+ return {};
+}
+
+} // namespace CPlusPlus