forked from qt-creator/qt-creator
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>
This commit is contained in:
@@ -32,6 +32,7 @@ add_qtc_library(CPlusPlus
|
||||
TypeOfExpression.cpp TypeOfExpression.h
|
||||
TypePrettyPrinter.cpp TypePrettyPrinter.h
|
||||
cppmodelmanagerbase.cpp cppmodelmanagerbase.h
|
||||
declarationcomments.cpp declarationcomments.h
|
||||
findcdbbreakpoint.cpp findcdbbreakpoint.h
|
||||
pp-cctype.h pp-engine.cpp
|
||||
pp-engine.h pp-scanner.cpp
|
||||
|
||||
@@ -96,6 +96,7 @@ Project {
|
||||
"CppDocument.cpp", "CppDocument.h",
|
||||
"CppRewriter.cpp", "CppRewriter.h",
|
||||
"cppmodelmanagerbase.cpp", "cppmodelmanagerbase.h",
|
||||
"declarationcomments.cpp", "declarationcomments.h",
|
||||
"DependencyTable.cpp", "DependencyTable.h",
|
||||
"DeprecatedGenTemplateInstance.cpp", "DeprecatedGenTemplateInstance.h",
|
||||
"ExpressionUnderCursor.cpp", "ExpressionUnderCursor.h",
|
||||
|
||||
145
src/libs/cplusplus/declarationcomments.cpp
Normal file
145
src/libs/cplusplus/declarationcomments.cpp
Normal file
@@ -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
|
||||
21
src/libs/cplusplus/declarationcomments.h
Normal file
21
src/libs/cplusplus/declarationcomments.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cplusplus/Token.h>
|
||||
|
||||
#include <QList>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextDocument;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace CPlusPlus {
|
||||
class Snapshot;
|
||||
|
||||
QList<Token> CPLUSPLUS_EXPORT commentsForDeclaration(const Symbol *symbol,
|
||||
const Snapshot &snapshot,
|
||||
const QTextDocument &textDoc);
|
||||
|
||||
} // namespace CPlusPlus
|
||||
Reference in New Issue
Block a user