diff options
| author | Christian Kandeler <christian.kandeler@qt.io> | 2023-08-08 12:50:01 +0200 |
|---|---|---|
| committer | Christian Kandeler <christian.kandeler@qt.io> | 2023-08-10 14:26:27 +0000 |
| commit | 74a0313fcf0aeffdaad9583db2ca8dc5f78e99a7 (patch) | |
| tree | 0ffb5a82fa86452cfb7dfe6e1f82f672321a85a7 /src/libs/cplusplus/declarationcomments.cpp | |
| parent | b128c585d951082f23faf609f1599944812dcdfa (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.cpp | 145 |
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 |
