forked from qt-creator/qt-creator
CppEditor: Let users check for unused functions in (sub-)projects
Note that especially in C++, there can be a lot of false positives, especially in template-heavy code bases. We filter out the most notorious offenders, namely: - templates themselves - constructors and destructors - *begin() and *end() - qHash() - main() Since the code model does not know about symbol visibility, the functionality is quite useless for libraries, unless you want to check your test coverage. The procedure is rather slow, but that shouldn't matter so much, as it's something you'll only run "once in a while". Fixes: QTCREATORBUG-6772 Change-Id: If00a537b760a9b0babdda6c848133715c3240155 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -118,14 +118,18 @@ void FindUsages::reportResult(unsigned tokenIndex, const QList<LookupItem> &cand
|
|||||||
lineText = matchingLine(tk);
|
lineText = matchingLine(tk);
|
||||||
const int len = tk.utf16chars();
|
const int len = tk.utf16chars();
|
||||||
|
|
||||||
const Usage u(_doc->filePath(), lineText,
|
QString callerName;
|
||||||
getContainingFunction(line, col), getTags(line, col, tokenIndex),
|
const Function * const caller = getContainingFunction(line, col);
|
||||||
line, col - 1, len);
|
if (caller)
|
||||||
|
callerName = Overview().prettyName(caller->name());
|
||||||
|
Usage u(_doc->filePath(), lineText, callerName, getTags(line, col, tokenIndex),
|
||||||
|
line, col - 1, len);
|
||||||
|
u.containingFunctionSymbol = caller;
|
||||||
_usages.append(u);
|
_usages.append(u);
|
||||||
_references.append(tokenIndex);
|
_references.append(tokenIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FindUsages::getContainingFunction(int line, int column)
|
Function *FindUsages::getContainingFunction(int line, int column)
|
||||||
{
|
{
|
||||||
const QList<AST *> astPath = ASTPath(_doc)(line, column);
|
const QList<AST *> astPath = ASTPath(_doc)(line, column);
|
||||||
bool hasBlock = false;
|
bool hasBlock = false;
|
||||||
@@ -135,9 +139,7 @@ QString FindUsages::getContainingFunction(int line, int column)
|
|||||||
if (const auto func = (*it)->asFunctionDefinition()) {
|
if (const auto func = (*it)->asFunctionDefinition()) {
|
||||||
if (!hasBlock)
|
if (!hasBlock)
|
||||||
return {};
|
return {};
|
||||||
if (!func->symbol)
|
return func->symbol;
|
||||||
return {};
|
|
||||||
return Overview().prettyName(func->symbol->name());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
@@ -222,7 +224,7 @@ public:
|
|||||||
// We don't want to classify constructors and destructors as declarations
|
// We don't want to classify constructors and destructors as declarations
|
||||||
// when listing class usages.
|
// when listing class usages.
|
||||||
if (m_findUsages->_declSymbol->asClass())
|
if (m_findUsages->_declSymbol->asClass())
|
||||||
return {};
|
return Usage::Tag::ConstructorDestructor;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (const auto declarator = (*it)->asDeclarator()) {
|
if (const auto declarator = (*it)->asDeclarator()) {
|
||||||
@@ -257,6 +259,10 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (const auto declId = declarator->core_declarator->asDeclaratorId()) {
|
||||||
|
if (declId->name && declId->name->asOperatorFunctionId())
|
||||||
|
tags |= Usage::Tag::Operator;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (const auto decl = (*(it + 1))->asSimpleDeclaration()) {
|
if (const auto decl = (*(it + 1))->asSimpleDeclaration()) {
|
||||||
if (tags.toInt() && decl->qt_invokable_token)
|
if (tags.toInt() && decl->qt_invokable_token)
|
||||||
@@ -268,6 +274,11 @@ public:
|
|||||||
declarator->initializer, it + 1),
|
declarator->initializer, it + 1),
|
||||||
it + 1);
|
it + 1);
|
||||||
}
|
}
|
||||||
|
Class *clazz = decl->symbols->value->enclosingClass();
|
||||||
|
if (clazz && clazz->name()
|
||||||
|
&& decl->symbols->value->name()->match(clazz->name())) {
|
||||||
|
return tags |= Usage::Tag::ConstructorDestructor;
|
||||||
|
}
|
||||||
if (const auto func = decl->symbols->value->type()->asFunctionType()) {
|
if (const auto func = decl->symbols->value->type()->asFunctionType()) {
|
||||||
if (func->isSignal() || func->isSlot() || func->isInvokable())
|
if (func->isSignal() || func->isSlot() || func->isInvokable())
|
||||||
return tags |= Usage::Tag::MocInvokable;
|
return tags |= Usage::Tag::MocInvokable;
|
||||||
|
@@ -25,6 +25,9 @@ public:
|
|||||||
Override = 1 << 4,
|
Override = 1 << 4,
|
||||||
MocInvokable = 1 << 5,
|
MocInvokable = 1 << 5,
|
||||||
Template = 1 << 6,
|
Template = 1 << 6,
|
||||||
|
ConstructorDestructor = 1 << 7,
|
||||||
|
Operator = 1 << 8,
|
||||||
|
Used = 1 << 9,
|
||||||
};
|
};
|
||||||
using Tags = QFlags<Tag>;
|
using Tags = QFlags<Tag>;
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ public:
|
|||||||
Utils::FilePath path;
|
Utils::FilePath path;
|
||||||
QString lineText;
|
QString lineText;
|
||||||
QString containingFunction;
|
QString containingFunction;
|
||||||
|
const Function *containingFunctionSymbol = nullptr;
|
||||||
Tags tags;
|
Tags tags;
|
||||||
int line = 0;
|
int line = 0;
|
||||||
int col = 0;
|
int col = 0;
|
||||||
@@ -65,7 +69,7 @@ protected:
|
|||||||
void reportResult(unsigned tokenIndex, const Name *name, Scope *scope = nullptr);
|
void reportResult(unsigned tokenIndex, const Name *name, Scope *scope = nullptr);
|
||||||
void reportResult(unsigned tokenIndex, const QList<LookupItem> &candidates);
|
void reportResult(unsigned tokenIndex, const QList<LookupItem> &candidates);
|
||||||
Usage::Tags getTags(int line, int column, int tokenIndex);
|
Usage::Tags getTags(int line, int column, int tokenIndex);
|
||||||
QString getContainingFunction(int line, int column);
|
Function *getContainingFunction(int line, int column);
|
||||||
|
|
||||||
bool checkCandidates(const QList<LookupItem> &candidates) const;
|
bool checkCandidates(const QList<LookupItem> &candidates) const;
|
||||||
void checkExpression(unsigned startToken, unsigned endToken, Scope *scope = nullptr);
|
void checkExpression(unsigned startToken, unsigned endToken, Scope *scope = nullptr);
|
||||||
|
@@ -519,6 +519,12 @@ void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor,
|
|||||||
requestSymbolInfo(document->filePath(), Range(adjustedCursor).start(), symbolInfoHandler);
|
requestSymbolInfo(document->filePath(), Range(adjustedCursor).start(), symbolInfoHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClangdClient::checkUnused(const Utils::Link &link, Core::SearchResult *search,
|
||||||
|
const Utils::LinkHandler &callback)
|
||||||
|
{
|
||||||
|
new ClangdFindReferences(this, link, search, callback);
|
||||||
|
}
|
||||||
|
|
||||||
void ClangdClient::handleDiagnostics(const PublishDiagnosticsParams ¶ms)
|
void ClangdClient::handleDiagnostics(const PublishDiagnosticsParams ¶ms)
|
||||||
{
|
{
|
||||||
const DocumentUri &uri = params.uri();
|
const DocumentUri &uri = params.uri();
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
namespace Core { class SearchResult; }
|
||||||
namespace CppEditor { class CppEditorWidget; }
|
namespace CppEditor { class CppEditorWidget; }
|
||||||
namespace LanguageServerProtocol { class Range; }
|
namespace LanguageServerProtocol { class Range; }
|
||||||
namespace ProjectExplorer {
|
namespace ProjectExplorer {
|
||||||
@@ -52,6 +53,8 @@ public:
|
|||||||
|
|
||||||
void findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor,
|
void findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor,
|
||||||
const std::optional<QString> &replacement);
|
const std::optional<QString> &replacement);
|
||||||
|
void checkUnused(const Utils::Link &link, Core::SearchResult *search,
|
||||||
|
const Utils::LinkHandler &callback);
|
||||||
void followSymbol(TextEditor::TextDocument *document,
|
void followSymbol(TextEditor::TextDocument *document,
|
||||||
const QTextCursor &cursor,
|
const QTextCursor &cursor,
|
||||||
CppEditor::CppEditorWidget *editorWidget,
|
CppEditor::CppEditorWidget *editorWidget,
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
#include <utils/filepath.h>
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QFile>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
|
||||||
@@ -51,6 +52,27 @@ public:
|
|||||||
QSet<Utils::FilePath> fileRenameCandidates;
|
QSet<Utils::FilePath> fileRenameCandidates;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ClangdFindReferences::CheckUnusedData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CheckUnusedData(ClangdFindReferences *q, const Link &link, SearchResult *search,
|
||||||
|
const LinkHandler &callback)
|
||||||
|
: q(q), link(link), linkAsPosition(link.targetLine, link.targetColumn), search(search),
|
||||||
|
callback(callback) {}
|
||||||
|
~CheckUnusedData();
|
||||||
|
|
||||||
|
ClangdFindReferences * const q;
|
||||||
|
const Link link;
|
||||||
|
const Position linkAsPosition;
|
||||||
|
const QPointer<SearchResult> search;
|
||||||
|
const LinkHandler callback;
|
||||||
|
QList<SearchResultItem> declDefItems;
|
||||||
|
bool openedExtraFileForLink = false;
|
||||||
|
bool declHasUsedTag = false;
|
||||||
|
bool recursiveCallDetected = false;
|
||||||
|
bool serverRestarted = false;
|
||||||
|
};
|
||||||
|
|
||||||
class ClangdFindReferences::Private
|
class ClangdFindReferences::Private
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -67,8 +89,7 @@ public:
|
|||||||
void finishSearch();
|
void finishSearch();
|
||||||
void reportAllSearchResultsAndFinish();
|
void reportAllSearchResultsAndFinish();
|
||||||
void addSearchResultsForFile(const FilePath &file, const ReferencesFileData &fileData);
|
void addSearchResultsForFile(const FilePath &file, const ReferencesFileData &fileData);
|
||||||
std::optional<QString> getContainingFunctionName(const ClangdAstPath &astPath,
|
ClangdAstNode getContainingFunction(const ClangdAstPath &astPath, const Range& range);
|
||||||
const Range& range);
|
|
||||||
|
|
||||||
ClangdFindReferences * const q;
|
ClangdFindReferences * const q;
|
||||||
QMap<DocumentUri, ReferencesFileData> fileData;
|
QMap<DocumentUri, ReferencesFileData> fileData;
|
||||||
@@ -76,6 +97,7 @@ public:
|
|||||||
QPointer<SearchResult> search;
|
QPointer<SearchResult> search;
|
||||||
std::optional<ReplacementData> replacementData;
|
std::optional<ReplacementData> replacementData;
|
||||||
QString searchTerm;
|
QString searchTerm;
|
||||||
|
std::optional<CheckUnusedData> checkUnusedData;
|
||||||
bool canceled = false;
|
bool canceled = false;
|
||||||
bool categorize = false;
|
bool categorize = false;
|
||||||
};
|
};
|
||||||
@@ -148,6 +170,60 @@ ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *d
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClangdFindReferences::ClangdFindReferences(ClangdClient *client, const Link &link,
|
||||||
|
SearchResult *search, const LinkHandler &callback)
|
||||||
|
: QObject(client), d(new Private(this))
|
||||||
|
{
|
||||||
|
d->checkUnusedData.emplace(this, link, search, callback);
|
||||||
|
d->categorize = true;
|
||||||
|
d->search = search;
|
||||||
|
|
||||||
|
if (!client->documentForFilePath(link.targetFilePath)) {
|
||||||
|
QFile f(link.targetFilePath.toString());
|
||||||
|
if (!f.open(QIODevice::ReadOnly)) {
|
||||||
|
d->finishSearch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QString contents = QString::fromUtf8(f.readAll());
|
||||||
|
QTextDocument doc(contents);
|
||||||
|
QTextCursor cursor(&doc);
|
||||||
|
cursor.setPosition(Text::positionInText(&doc, link.targetLine, link.targetColumn + 1));
|
||||||
|
cursor.select(QTextCursor::WordUnderCursor);
|
||||||
|
d->searchTerm = cursor.selectedText();
|
||||||
|
client->openExtraFile(link.targetFilePath, contents);
|
||||||
|
d->checkUnusedData->openedExtraFileForLink = true;
|
||||||
|
}
|
||||||
|
const TextDocumentIdentifier documentId(DocumentUri::fromFilePath(link.targetFilePath));
|
||||||
|
const Position pos(link.targetLine - 1, link.targetColumn);
|
||||||
|
ReferenceParams params(TextDocumentPositionParams(documentId, pos));
|
||||||
|
params.setContext(ReferenceParams::ReferenceContext(true));
|
||||||
|
FindReferencesRequest request(params);
|
||||||
|
request.setResponseCallback([self = QPointer(this)]
|
||||||
|
(const FindReferencesRequest::Response &response) {
|
||||||
|
if (self) {
|
||||||
|
const LanguageClientArray<Location> locations = response.result().value_or(nullptr);
|
||||||
|
self->d->handleFindUsagesResult(locations.isNull() ? QList<Location>()
|
||||||
|
: locations.toList());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client->sendMessage(request, ClangdClient::SendDocUpdates::Ignore);
|
||||||
|
QObject::connect(d->search, &SearchResult::canceled, this, [this, client, id = request.id()] {
|
||||||
|
client->cancelRequest(id);
|
||||||
|
d->canceled = true;
|
||||||
|
d->finishSearch();
|
||||||
|
});
|
||||||
|
QObject::connect(d->search, &SearchResult::destroyed, this, [this, client, id = request.id()] {
|
||||||
|
client->cancelRequest(id);
|
||||||
|
d->canceled = true;
|
||||||
|
d->finishSearch();
|
||||||
|
});
|
||||||
|
connect(client, &ClangdClient::initialized, this, [this] {
|
||||||
|
d->checkUnusedData->serverRestarted = true;
|
||||||
|
d->finishSearch();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ClangdFindReferences::~ClangdFindReferences()
|
ClangdFindReferences::~ClangdFindReferences()
|
||||||
{
|
{
|
||||||
delete d;
|
delete d;
|
||||||
@@ -227,8 +303,12 @@ void ClangdFindReferences::Private::handleFindUsagesResult(const QList<Location>
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = fileData.begin(); it != fileData.end(); ++it) {
|
for (auto it = fileData.begin(); it != fileData.end(); ++it) {
|
||||||
const TextDocument * const doc = client()->documentForFilePath(it.key().toFilePath());
|
const FilePath filePath = it.key().toFilePath();
|
||||||
if (!doc)
|
const TextDocument * const doc = client()->documentForFilePath(filePath);
|
||||||
|
const bool openExtraFile = !doc && (!checkUnusedData
|
||||||
|
|| !checkUnusedData->openedExtraFileForLink
|
||||||
|
|| checkUnusedData->link.targetFilePath != filePath);
|
||||||
|
if (openExtraFile)
|
||||||
client()->openExtraFile(it.key().toFilePath(), it->fileContent);
|
client()->openExtraFile(it.key().toFilePath(), it->fileContent);
|
||||||
it->fileContent.clear();
|
it->fileContent.clear();
|
||||||
const auto docVariant = doc ? ClangdClient::TextDocOrFile(doc)
|
const auto docVariant = doc ? ClangdClient::TextDocOrFile(doc)
|
||||||
@@ -246,21 +326,26 @@ void ClangdFindReferences::Private::handleFindUsagesResult(const QList<Location>
|
|||||||
qCDebug(clangdLog) << pendingAstRequests.size() << "AST requests still pending";
|
qCDebug(clangdLog) << pendingAstRequests.size() << "AST requests still pending";
|
||||||
addSearchResultsForFile(loc.toFilePath(), data);
|
addSearchResultsForFile(loc.toFilePath(), data);
|
||||||
fileData.remove(loc);
|
fileData.remove(loc);
|
||||||
if (pendingAstRequests.isEmpty()) {
|
if (pendingAstRequests.isEmpty() && !canceled) {
|
||||||
qDebug(clangdLog) << "retrieved all ASTs";
|
qCDebug(clangdLog) << "retrieved all ASTs";
|
||||||
finishSearch();
|
finishSearch();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const MessageId reqId = client()->getAndHandleAst(
|
const MessageId reqId = client()->getAndHandleAst(
|
||||||
docVariant, astHandler, ClangdClient::AstCallbackMode::AlwaysAsync, {});
|
docVariant, astHandler, ClangdClient::AstCallbackMode::AlwaysAsync, {});
|
||||||
pendingAstRequests << reqId;
|
pendingAstRequests << reqId;
|
||||||
if (!doc)
|
if (openExtraFile)
|
||||||
client()->closeExtraFile(it.key().toFilePath());
|
client()->closeExtraFile(it.key().toFilePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClangdFindReferences::Private::finishSearch()
|
void ClangdFindReferences::Private::finishSearch()
|
||||||
{
|
{
|
||||||
|
if (checkUnusedData) {
|
||||||
|
q->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!client()->testingEnabled() && search) {
|
if (!client()->testingEnabled() && search) {
|
||||||
search->finishSearch(canceled);
|
search->finishSearch(canceled);
|
||||||
search->disconnect(q);
|
search->disconnect(q);
|
||||||
@@ -283,24 +368,59 @@ void ClangdFindReferences::Private::finishSearch()
|
|||||||
|
|
||||||
void ClangdFindReferences::Private::reportAllSearchResultsAndFinish()
|
void ClangdFindReferences::Private::reportAllSearchResultsAndFinish()
|
||||||
{
|
{
|
||||||
for (auto it = fileData.begin(); it != fileData.end(); ++it)
|
if (!checkUnusedData) {
|
||||||
addSearchResultsForFile(it.key().toFilePath(), it.value());
|
for (auto it = fileData.begin(); it != fileData.end(); ++it)
|
||||||
|
addSearchResultsForFile(it.key().toFilePath(), it.value());
|
||||||
|
}
|
||||||
finishSearch();
|
finishSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &searchTerm);
|
static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &searchTerm,
|
||||||
|
const QStringList &expectedDeclTypes);
|
||||||
|
|
||||||
void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file,
|
void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file,
|
||||||
const ReferencesFileData &fileData)
|
const ReferencesFileData &fileData)
|
||||||
{
|
{
|
||||||
QList<SearchResultItem> items;
|
QList<SearchResultItem> items;
|
||||||
qCDebug(clangdLog) << file << "has valid AST:" << fileData.ast.isValid();
|
qCDebug(clangdLog) << file << "has valid AST:" << fileData.ast.isValid();
|
||||||
|
const auto expectedDeclTypes = [this]() -> QStringList {
|
||||||
|
if (checkUnusedData)
|
||||||
|
return {"Function", "CXXMethod"};
|
||||||
|
return {};
|
||||||
|
}();
|
||||||
for (const auto &rangeWithText : fileData.rangesAndLineText) {
|
for (const auto &rangeWithText : fileData.rangesAndLineText) {
|
||||||
const Range &range = rangeWithText.first;
|
const Range &range = rangeWithText.first;
|
||||||
const ClangdAstPath astPath = getAstPath(fileData.ast, range);
|
const ClangdAstPath astPath = getAstPath(fileData.ast, range);
|
||||||
const Usage::Tags usageType = fileData.ast.isValid() ? getUsageType(astPath, searchTerm)
|
const Usage::Tags usageType = fileData.ast.isValid()
|
||||||
: Usage::Tags();
|
? getUsageType(astPath, searchTerm, expectedDeclTypes)
|
||||||
|
: Usage::Tags();
|
||||||
|
if (checkUnusedData) {
|
||||||
|
bool isProperUsage = false;
|
||||||
|
if (usageType.testFlag(Usage::Tag::Declaration)) {
|
||||||
|
checkUnusedData->declHasUsedTag = checkUnusedData->declHasUsedTag
|
||||||
|
|| usageType.testFlag(Usage::Tag::Used);
|
||||||
|
isProperUsage = usageType.testAnyFlags({
|
||||||
|
Usage::Tag::Override, Usage::Tag::MocInvokable,
|
||||||
|
Usage::Tag::ConstructorDestructor, Usage::Tag::Template,
|
||||||
|
Usage::Tag::Operator});
|
||||||
|
} else {
|
||||||
|
bool isRecursiveCall = false;
|
||||||
|
if (checkUnusedData->link.targetFilePath == file) {
|
||||||
|
const ClangdAstNode containingFunction = getContainingFunction(astPath, range);
|
||||||
|
isRecursiveCall = containingFunction.hasRange()
|
||||||
|
&& containingFunction.range().contains(checkUnusedData->linkAsPosition);
|
||||||
|
}
|
||||||
|
checkUnusedData->recursiveCallDetected = checkUnusedData->recursiveCallDetected
|
||||||
|
|| isRecursiveCall;
|
||||||
|
isProperUsage = !isRecursiveCall;
|
||||||
|
}
|
||||||
|
if (isProperUsage) {
|
||||||
|
qCDebug(clangdLog) << "proper usage at" << rangeWithText.second;
|
||||||
|
canceled = true;
|
||||||
|
finishSearch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
SearchResultItem item;
|
SearchResultItem item;
|
||||||
item.setUserData(usageType.toInt());
|
item.setUserData(usageType.toInt());
|
||||||
item.setStyle(CppEditor::colorStyleForUsageType(usageType));
|
item.setStyle(CppEditor::colorStyleForUsageType(usageType));
|
||||||
@@ -308,7 +428,18 @@ void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file
|
|||||||
item.setMainRange(SymbolSupport::convertRange(range));
|
item.setMainRange(SymbolSupport::convertRange(range));
|
||||||
item.setUseTextEditorFont(true);
|
item.setUseTextEditorFont(true);
|
||||||
item.setLineText(rangeWithText.second);
|
item.setLineText(rangeWithText.second);
|
||||||
item.setContainingFunctionName(getContainingFunctionName(astPath, range));
|
if (checkUnusedData) {
|
||||||
|
if (rangeWithText.second.contains("template<>")) {
|
||||||
|
// Hack: Function specializations are not detectable in the AST.
|
||||||
|
canceled = true;
|
||||||
|
finishSearch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qCDebug(clangdLog) << "collecting decl/def" << rangeWithText.second;
|
||||||
|
checkUnusedData->declDefItems << item;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
item.setContainingFunctionName(getContainingFunction(astPath, range).detail());
|
||||||
|
|
||||||
if (search->supportsReplace()) {
|
if (search->supportsReplace()) {
|
||||||
const bool fileInSession = SessionManager::projectForFile(file);
|
const bool fileInSession = SessionManager::projectForFile(file);
|
||||||
@@ -322,12 +453,12 @@ void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file
|
|||||||
}
|
}
|
||||||
if (client()->testingEnabled())
|
if (client()->testingEnabled())
|
||||||
emit q->foundReferences(items);
|
emit q->foundReferences(items);
|
||||||
else
|
else if (!checkUnusedData)
|
||||||
search->addResults(items, SearchResult::AddOrdered);
|
search->addResults(items, SearchResult::AddOrdered);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<QString> ClangdFindReferences::Private::getContainingFunctionName(
|
ClangdAstNode ClangdFindReferences::Private::getContainingFunction(
|
||||||
const ClangdAstPath &astPath, const Range& range)
|
const ClangdAstPath &astPath, const Range& range)
|
||||||
{
|
{
|
||||||
const ClangdAstNode* containingFuncNode{nullptr};
|
const ClangdAstNode* containingFuncNode{nullptr};
|
||||||
const ClangdAstNode* lastCompoundStmtNode{nullptr};
|
const ClangdAstNode* lastCompoundStmtNode{nullptr};
|
||||||
@@ -346,12 +477,13 @@ std::optional<QString> ClangdFindReferences::Private::getContainingFunctionName(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!containingFuncNode || !containingFuncNode->isValid())
|
if (!containingFuncNode || !containingFuncNode->isValid())
|
||||||
return std::nullopt;
|
return {};
|
||||||
|
|
||||||
return containingFuncNode->detail();
|
return *containingFuncNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &searchTerm)
|
static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &searchTerm,
|
||||||
|
const QStringList &expectedDeclTypes)
|
||||||
{
|
{
|
||||||
bool potentialWrite = false;
|
bool potentialWrite = false;
|
||||||
bool isFunction = false;
|
bool isFunction = false;
|
||||||
@@ -359,6 +491,12 @@ static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &search
|
|||||||
QString invokedConstructor;
|
QString invokedConstructor;
|
||||||
if (path.last().role() == "expression" && path.last().kind() == "CXXConstruct")
|
if (path.last().role() == "expression" && path.last().kind() == "CXXConstruct")
|
||||||
invokedConstructor = path.last().detail().value_or(QString());
|
invokedConstructor = path.last().detail().value_or(QString());
|
||||||
|
|
||||||
|
// Sometimes (TM), it can happen that none of the AST nodes have a range,
|
||||||
|
// so path construction fails.
|
||||||
|
if (path.last().kind() == "TranslationUnit")
|
||||||
|
return Usage::Tag::Used;
|
||||||
|
|
||||||
const auto isPotentialWrite = [&] { return potentialWrite && !isFunction; };
|
const auto isPotentialWrite = [&] { return potentialWrite && !isFunction; };
|
||||||
const auto isSomeSortOfTemplate = [&](auto declPathIt) {
|
const auto isSomeSortOfTemplate = [&](auto declPathIt) {
|
||||||
if (declPathIt->kind() == "Function") {
|
if (declPathIt->kind() == "Function") {
|
||||||
@@ -407,8 +545,10 @@ static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &search
|
|||||||
if (pathIt->role() == "declaration") {
|
if (pathIt->role() == "declaration") {
|
||||||
if (symbolIsDataType)
|
if (symbolIsDataType)
|
||||||
return {};
|
return {};
|
||||||
if (!invokedConstructor.isEmpty() && invokedConstructor == searchTerm)
|
if (!expectedDeclTypes.isEmpty() && !expectedDeclTypes.contains(pathIt->kind()))
|
||||||
return {};
|
return {};
|
||||||
|
if (!invokedConstructor.isEmpty() && invokedConstructor == searchTerm)
|
||||||
|
return Usage::Tag::ConstructorDestructor;
|
||||||
if (pathIt->arcanaContains("cinit")) {
|
if (pathIt->arcanaContains("cinit")) {
|
||||||
if (pathIt == path.rbegin())
|
if (pathIt == path.rbegin())
|
||||||
return {Usage::Tag::Declaration, Usage::Tag::Write};
|
return {Usage::Tag::Declaration, Usage::Tag::Write};
|
||||||
@@ -421,6 +561,10 @@ static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &search
|
|||||||
return Usage::Tag::Read;
|
return Usage::Tag::Read;
|
||||||
}
|
}
|
||||||
Usage::Tags tags = Usage::Tag::Declaration;
|
Usage::Tags tags = Usage::Tag::Declaration;
|
||||||
|
if (pathIt->arcanaContains(" used ") || pathIt->arcanaContains(" referenced "))
|
||||||
|
tags |= Usage::Tag::Used;
|
||||||
|
if (pathIt->kind() == "CXXConstructor" || pathIt->kind() == "CXXDestructor")
|
||||||
|
tags |= Usage::Tag::ConstructorDestructor;
|
||||||
const auto children = pathIt->children().value_or(QList<ClangdAstNode>());
|
const auto children = pathIt->children().value_or(QList<ClangdAstNode>());
|
||||||
for (const ClangdAstNode &child : children) {
|
for (const ClangdAstNode &child : children) {
|
||||||
if (child.role() == "attribute") {
|
if (child.role() == "attribute") {
|
||||||
@@ -432,6 +576,15 @@ static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &search
|
|||||||
}
|
}
|
||||||
if (isSomeSortOfTemplate(pathIt))
|
if (isSomeSortOfTemplate(pathIt))
|
||||||
tags |= Usage::Tag::Template;
|
tags |= Usage::Tag::Template;
|
||||||
|
if (pathIt->kind() == "Function" || pathIt->kind() == "CXXMethod") {
|
||||||
|
const QString detail = pathIt->detail().value_or(QString());
|
||||||
|
static const QString opString(QLatin1String("operator"));
|
||||||
|
if (detail.size() > opString.size() && detail.startsWith(opString)
|
||||||
|
&& !detail.at(opString.size()).isLetterOrNumber()
|
||||||
|
&& detail.at(opString.size()) != '_') {
|
||||||
|
tags |= Usage::Tag::Operator;
|
||||||
|
}
|
||||||
|
}
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
if (pathIt->kind() == "MemberInitializer")
|
if (pathIt->kind() == "MemberInitializer")
|
||||||
@@ -447,8 +600,10 @@ static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &search
|
|||||||
const bool isBinaryOp = pathIt->kind() == "BinaryOperator";
|
const bool isBinaryOp = pathIt->kind() == "BinaryOperator";
|
||||||
const bool isOpCall = pathIt->kind() == "CXXOperatorCall";
|
const bool isOpCall = pathIt->kind() == "CXXOperatorCall";
|
||||||
if (isBinaryOp || isOpCall) {
|
if (isBinaryOp || isOpCall) {
|
||||||
if (isOpCall && symbolIsDataType) // Constructor invocation.
|
if (isOpCall && symbolIsDataType) { // Constructor invocation.
|
||||||
return {};
|
if (searchTerm == invokedConstructor)
|
||||||
|
return Usage::Tag::ConstructorDestructor;
|
||||||
|
return {};}
|
||||||
|
|
||||||
const QString op = pathIt->operatorString();
|
const QString op = pathIt->operatorString();
|
||||||
if (op.endsWith("=") && op != "==") { // Assignment.
|
if (op.endsWith("=") && op != "==") { // Assignment.
|
||||||
@@ -473,6 +628,19 @@ static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &search
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClangdFindReferences::CheckUnusedData::~CheckUnusedData()
|
||||||
|
{
|
||||||
|
if (!serverRestarted) {
|
||||||
|
if (openedExtraFileForLink && q->d->client() && q->d->client()->reachable()
|
||||||
|
&& !q->d->client()->documentForFilePath(link.targetFilePath)) {
|
||||||
|
q->d->client()->closeExtraFile(link.targetFilePath);
|
||||||
|
}
|
||||||
|
if (!q->d->canceled && (!declHasUsedTag || recursiveCallDetected) && QTC_GUARD(search))
|
||||||
|
search->addResults(declDefItems, SearchResult::AddOrdered);
|
||||||
|
}
|
||||||
|
callback(link);
|
||||||
|
}
|
||||||
|
|
||||||
class ClangdFindLocalReferences::Private
|
class ClangdFindLocalReferences::Private
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <coreplugin/find/searchresultitem.h>
|
#include <coreplugin/find/searchresultitem.h>
|
||||||
#include <cppeditor/cursorineditor.h>
|
#include <cppeditor/cursorineditor.h>
|
||||||
|
#include <utils/link.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ QT_BEGIN_NAMESPACE
|
|||||||
class QTextCursor;
|
class QTextCursor;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace Core { class SearchResult; }
|
||||||
namespace TextEditor { class TextDocument; }
|
namespace TextEditor { class TextDocument; }
|
||||||
|
|
||||||
namespace ClangCodeModel::Internal {
|
namespace ClangCodeModel::Internal {
|
||||||
@@ -23,9 +25,11 @@ class ClangdFindReferences : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ClangdFindReferences(ClangdClient *client, TextEditor::TextDocument *document,
|
ClangdFindReferences(ClangdClient *client, TextEditor::TextDocument *document,
|
||||||
const QTextCursor &cursor, const QString &searchTerm,
|
const QTextCursor &cursor, const QString &searchTerm,
|
||||||
const std::optional<QString> &replacement, bool categorize);
|
const std::optional<QString> &replacement, bool categorize);
|
||||||
|
ClangdFindReferences(ClangdClient *client, const Utils::Link &link, Core::SearchResult *search,
|
||||||
|
const Utils::LinkHandler &callback);
|
||||||
~ClangdFindReferences();
|
~ClangdFindReferences();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -34,6 +38,7 @@ signals:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
class Private;
|
class Private;
|
||||||
|
class CheckUnusedData;
|
||||||
Private * const d;
|
Private * const d;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -352,6 +352,22 @@ void ClangModelManagerSupport::switchHeaderSource(const Utils::FilePath &filePat
|
|||||||
CppModelManager::switchHeaderSource(inNextSplit, CppModelManager::Backend::Builtin);
|
CppModelManager::switchHeaderSource(inNextSplit, CppModelManager::Backend::Builtin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClangModelManagerSupport::checkUnused(const Utils::Link &link, Core::SearchResult *search,
|
||||||
|
const Utils::LinkHandler &callback)
|
||||||
|
{
|
||||||
|
if (const ProjectExplorer::Project * const project
|
||||||
|
= ProjectExplorer::SessionManager::projectForFile(link.targetFilePath)) {
|
||||||
|
if (ClangdClient * const client = clientWithProject(project);
|
||||||
|
client && client->isFullyIndexed()) {
|
||||||
|
client->checkUnused(link, search, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CppModelManager::instance()->modelManagerSupport(
|
||||||
|
CppModelManager::Backend::Builtin)->checkUnused(link, search, callback);
|
||||||
|
}
|
||||||
|
|
||||||
bool ClangModelManagerSupport::usesClangd(const TextEditor::TextDocument *document) const
|
bool ClangModelManagerSupport::usesClangd(const TextEditor::TextDocument *document) const
|
||||||
{
|
{
|
||||||
return clientForFile(document->filePath());
|
return clientForFile(document->filePath());
|
||||||
|
@@ -63,6 +63,8 @@ private:
|
|||||||
void globalRename(const CppEditor::CursorInEditor &cursor, const QString &replacement) override;
|
void globalRename(const CppEditor::CursorInEditor &cursor, const QString &replacement) override;
|
||||||
void findUsages(const CppEditor::CursorInEditor &cursor) const override;
|
void findUsages(const CppEditor::CursorInEditor &cursor) const override;
|
||||||
void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) override;
|
void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) override;
|
||||||
|
void checkUnused(const Utils::Link &link, Core::SearchResult *search,
|
||||||
|
const Utils::LinkHandler &callback) override;
|
||||||
|
|
||||||
void onEditorOpened(Core::IEditor *editor);
|
void onEditorOpened(Core::IEditor *editor);
|
||||||
void onCurrentEditorChanged(Core::IEditor *newCurrent);
|
void onCurrentEditorChanged(Core::IEditor *newCurrent);
|
||||||
|
@@ -251,7 +251,8 @@ void ClangdTestFindReferences::test_data()
|
|||||||
// Some of these are conceptually questionable, as S is a type and thus we cannot "read from"
|
// Some of these are conceptually questionable, as S is a type and thus we cannot "read from"
|
||||||
// or "write to" it. But it probably matches the intuitive user expectation.
|
// or "write to" it. But it probably matches the intuitive user expectation.
|
||||||
QTest::newRow("struct type") << "defs.h" << 7 << ItemList{
|
QTest::newRow("struct type") << "defs.h" << 7 << ItemList{
|
||||||
makeItem(1, 7, Usage::Tag::Declaration), makeItem(2, 4, Usage::Tag::Declaration),
|
makeItem(1, 7, Usage::Tag::Declaration),
|
||||||
|
makeItem(2, 4, (Usage::Tags{Usage::Tag::Declaration, Usage::Tag::ConstructorDestructor})),
|
||||||
makeItem(20, 19, Usage::Tags()), makeItem(10, 9, Usage::Tag::WritableRef),
|
makeItem(20, 19, Usage::Tags()), makeItem(10, 9, Usage::Tag::WritableRef),
|
||||||
makeItem(12, 4, Usage::Tag::Write), makeItem(44, 12, Usage::Tag::Read),
|
makeItem(12, 4, Usage::Tag::Write), makeItem(44, 12, Usage::Tag::Read),
|
||||||
makeItem(45, 13, Usage::Tag::Read), makeItem(47, 12, Usage::Tag::Write),
|
makeItem(45, 13, Usage::Tag::Read), makeItem(47, 12, Usage::Tag::Write),
|
||||||
@@ -266,7 +267,8 @@ void ClangdTestFindReferences::test_data()
|
|||||||
makeItem(13, 21, Usage::Tags()), makeItem(32, 8, Usage::Tags())};
|
makeItem(13, 21, Usage::Tags()), makeItem(32, 8, Usage::Tags())};
|
||||||
|
|
||||||
QTest::newRow("constructor") << "defs.h" << 627 << ItemList{
|
QTest::newRow("constructor") << "defs.h" << 627 << ItemList{
|
||||||
makeItem(31, 4, Usage::Tag::Declaration), makeItem(36, 7, Usage::Tags())};
|
makeItem(31, 4, (Usage::Tags{Usage::Tag::Declaration, Usage::Tag::ConstructorDestructor})),
|
||||||
|
makeItem(36, 7, Usage::Tag::ConstructorDestructor)};
|
||||||
|
|
||||||
QTest::newRow("subclass") << "defs.h" << 450 << ItemList{
|
QTest::newRow("subclass") << "defs.h" << 450 << ItemList{
|
||||||
makeItem(20, 7, Usage::Tag::Declaration), makeItem(5, 4, Usage::Tags()),
|
makeItem(20, 7, Usage::Tag::Declaration), makeItem(5, 4, Usage::Tags()),
|
||||||
@@ -303,7 +305,10 @@ void ClangdTestFindReferences::test()
|
|||||||
const SearchResultItem &curExpected = expectedResults.at(i);
|
const SearchResultItem &curExpected = expectedResults.at(i);
|
||||||
QCOMPARE(curActual.mainRange().begin.line, curExpected.mainRange().begin.line);
|
QCOMPARE(curActual.mainRange().begin.line, curExpected.mainRange().begin.line);
|
||||||
QCOMPARE(curActual.mainRange().begin.column, curExpected.mainRange().begin.column);
|
QCOMPARE(curActual.mainRange().begin.column, curExpected.mainRange().begin.column);
|
||||||
QCOMPARE(curActual.userData(), curExpected.userData());
|
const auto actualTags = Usage::Tags::fromInt(curActual.userData().toInt())
|
||||||
|
& ~Usage::Tags(Usage::Tag::Used);
|
||||||
|
const auto expectedTags = Usage::Tags::fromInt(curExpected.userData().toInt());
|
||||||
|
QCOMPARE(actualTags, expectedTags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,8 +18,11 @@
|
|||||||
#include <texteditor/basehoverhandler.h>
|
#include <texteditor/basehoverhandler.h>
|
||||||
#include <utils/executeondestruction.h>
|
#include <utils/executeondestruction.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
#include <utils/textutils.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTextDocument>
|
||||||
|
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
using namespace TextEditor;
|
using namespace TextEditor;
|
||||||
@@ -193,4 +196,27 @@ void BuiltinModelManagerSupport::switchHeaderSource(const Utils::FilePath &fileP
|
|||||||
openEditor(otherFile, inNextSplit);
|
openEditor(otherFile, inNextSplit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BuiltinModelManagerSupport::checkUnused(const Utils::Link &link, SearchResult *search,
|
||||||
|
const Utils::LinkHandler &callback)
|
||||||
|
{
|
||||||
|
CPlusPlus::Snapshot snapshot = CppModelManager::instance()->snapshot();
|
||||||
|
QFile file(link.targetFilePath.toString());
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
|
return callback(link);
|
||||||
|
const QByteArray &contents = file.readAll();
|
||||||
|
CPlusPlus::Document::Ptr cppDoc = snapshot.preprocessedDocument(contents, link.targetFilePath);
|
||||||
|
if (!cppDoc->parse())
|
||||||
|
return callback(link);
|
||||||
|
cppDoc->check();
|
||||||
|
snapshot.insert(cppDoc);
|
||||||
|
QTextDocument doc(QString::fromUtf8(contents));
|
||||||
|
QTextCursor cursor(&doc);
|
||||||
|
cursor.setPosition(Utils::Text::positionInText(&doc, link.targetLine, link.targetColumn + 1));
|
||||||
|
Internal::CanonicalSymbol cs(cppDoc, snapshot);
|
||||||
|
CPlusPlus::Symbol *canonicalSymbol = cs(cursor);
|
||||||
|
if (!canonicalSymbol || !canonicalSymbol->identifier())
|
||||||
|
return callback(link);
|
||||||
|
CppModelManager::checkForUnusedSymbol(search, link, canonicalSymbol, cs.context(), callback);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace CppEditor::Internal
|
} // namespace CppEditor::Internal
|
||||||
|
@@ -41,6 +41,8 @@ private:
|
|||||||
void globalRename(const CursorInEditor &data, const QString &replacement) override;
|
void globalRename(const CursorInEditor &data, const QString &replacement) override;
|
||||||
void findUsages(const CursorInEditor &data) const override;
|
void findUsages(const CursorInEditor &data) const override;
|
||||||
void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) override;
|
void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) override;
|
||||||
|
void checkUnused(const Utils::Link &link, Core::SearchResult *search,
|
||||||
|
const Utils::LinkHandler &callback) override;
|
||||||
|
|
||||||
QScopedPointer<CppCompletionAssistProvider> m_completionAssistProvider;
|
QScopedPointer<CppCompletionAssistProvider> m_completionAssistProvider;
|
||||||
QScopedPointer<FollowSymbolUnderCursor> m_followSymbol;
|
QScopedPointer<FollowSymbolUnderCursor> m_followSymbol;
|
||||||
|
@@ -276,6 +276,27 @@ bool CppEditorPlugin::initialize(const QStringList & /*arguments*/, QString *err
|
|||||||
connect(showPreprocessedInSplitAction, &QAction::triggered,
|
connect(showPreprocessedInSplitAction, &QAction::triggered,
|
||||||
this, [] { CppModelManager::showPreprocessedFile(true); });
|
this, [] { CppModelManager::showPreprocessedFile(true); });
|
||||||
|
|
||||||
|
QAction * const findUnusedFunctionsAction = new QAction(tr("Find Unused Functions"), this);
|
||||||
|
command = ActionManager::registerAction(findUnusedFunctionsAction,
|
||||||
|
"CppTools.FindUnusedFunctions");
|
||||||
|
mcpptools->addAction(command);
|
||||||
|
connect(findUnusedFunctionsAction, &QAction::triggered,
|
||||||
|
this, [] { CppModelManager::findUnusedFunctions({}); });
|
||||||
|
QAction * const findUnusedFunctionsInSubProjectAction
|
||||||
|
= new QAction(tr("Find Unused C/C++ Functions"), this);
|
||||||
|
command = ActionManager::registerAction(findUnusedFunctionsInSubProjectAction,
|
||||||
|
"CppTools.FindUnusedFunctionsInSubProject");
|
||||||
|
for (ActionContainer * const projectContextMenu : {
|
||||||
|
ActionManager::actionContainer(ProjectExplorer::Constants::M_SUBPROJECTCONTEXT),
|
||||||
|
ActionManager::actionContainer(ProjectExplorer::Constants::M_PROJECTCONTEXT)}) {
|
||||||
|
projectContextMenu->addSeparator(ProjectExplorer::Constants::G_PROJECT_TREE);
|
||||||
|
projectContextMenu->addAction(command, ProjectExplorer::Constants::G_PROJECT_TREE);
|
||||||
|
}
|
||||||
|
connect(findUnusedFunctionsInSubProjectAction, &QAction::triggered, this, [] {
|
||||||
|
if (const Node * const node = ProjectTree::currentNode(); node && node->asFolderNode())
|
||||||
|
CppModelManager::findUnusedFunctions(node->directory());
|
||||||
|
});
|
||||||
|
|
||||||
MacroExpander *expander = globalMacroExpander();
|
MacroExpander *expander = globalMacroExpander();
|
||||||
expander->registerVariable("Cpp:LicenseTemplate",
|
expander->registerVariable("Cpp:LicenseTemplate",
|
||||||
tr("The license template."),
|
tr("The license template."),
|
||||||
|
@@ -784,6 +784,56 @@ void CppFindReferences::renameMacroUses(const CPlusPlus::Macro ¯o, const QSt
|
|||||||
findMacroUses(macro, textToReplace, true);
|
findMacroUses(macro, textToReplace, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CppFindReferences::checkUnused(Core::SearchResult *search, const Link &link,
|
||||||
|
CPlusPlus::Symbol *symbol,
|
||||||
|
const CPlusPlus::LookupContext &context,
|
||||||
|
const LinkHandler &callback)
|
||||||
|
{
|
||||||
|
const auto isProperUsage = [symbol](const CPlusPlus::Usage &usage) {
|
||||||
|
if (!usage.tags.testFlag(CPlusPlus::Usage::Tag::Declaration))
|
||||||
|
return usage.containingFunctionSymbol != symbol;
|
||||||
|
return usage.tags.testAnyFlags({CPlusPlus::Usage::Tag::Override,
|
||||||
|
CPlusPlus::Usage::Tag::MocInvokable,
|
||||||
|
CPlusPlus::Usage::Tag::Template,
|
||||||
|
CPlusPlus::Usage::Tag::Operator,
|
||||||
|
CPlusPlus::Usage::Tag::ConstructorDestructor});
|
||||||
|
};
|
||||||
|
const auto watcher = new QFutureWatcher<CPlusPlus::Usage>();
|
||||||
|
connect(watcher, &QFutureWatcherBase::finished, watcher,
|
||||||
|
[watcher, link, callback, search, isProperUsage] {
|
||||||
|
watcher->deleteLater();
|
||||||
|
if (watcher->isCanceled())
|
||||||
|
return callback(link);
|
||||||
|
for (int i = 0; i < watcher->future().resultCount(); ++i) {
|
||||||
|
if (isProperUsage(watcher->resultAt(i)))
|
||||||
|
return callback(link);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < watcher->future().resultCount(); ++i) {
|
||||||
|
const CPlusPlus::Usage usage = watcher->resultAt(i);
|
||||||
|
SearchResultItem item;
|
||||||
|
item.setFilePath(usage.path);
|
||||||
|
item.setLineText(usage.lineText);
|
||||||
|
item.setMainRange(usage.line, usage.col, usage.len);
|
||||||
|
item.setUseTextEditorFont(true);
|
||||||
|
search->addResult(item);
|
||||||
|
}
|
||||||
|
callback(link);
|
||||||
|
});
|
||||||
|
connect(watcher, &QFutureWatcherBase::resultsReadyAt, search,
|
||||||
|
[watcher, isProperUsage](int first, int end) {
|
||||||
|
for (int i = first; i < end; ++i) {
|
||||||
|
if (isProperUsage(watcher->resultAt(i))) {
|
||||||
|
watcher->cancel();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(search, &SearchResult::canceled, watcher, [watcher] { watcher->cancel(); });
|
||||||
|
connect(search, &SearchResult::destroyed, watcher, [watcher] { watcher->cancel(); });
|
||||||
|
watcher->setFuture(Utils::runAsync(m_modelManager->sharedThreadPool(), find_helper,
|
||||||
|
m_modelManager->workingCopy(), context, symbol, true));
|
||||||
|
}
|
||||||
|
|
||||||
void CppFindReferences::createWatcher(const QFuture<CPlusPlus::Usage> &future, SearchResult *search)
|
void CppFindReferences::createWatcher(const QFuture<CPlusPlus::Usage> &future, SearchResult *search)
|
||||||
{
|
{
|
||||||
auto watcher = new QFutureWatcher<CPlusPlus::Usage>();
|
auto watcher = new QFutureWatcher<CPlusPlus::Usage>();
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
#include <coreplugin/find/searchresultwindow.h>
|
#include <coreplugin/find/searchresultwindow.h>
|
||||||
#include <cplusplus/FindUsages.h>
|
#include <cplusplus/FindUsages.h>
|
||||||
#include <utils/filepath.h>
|
#include <utils/filepath.h>
|
||||||
|
#include <utils/link.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
@@ -69,6 +70,9 @@ public:
|
|||||||
void findMacroUses(const CPlusPlus::Macro ¯o);
|
void findMacroUses(const CPlusPlus::Macro ¯o);
|
||||||
void renameMacroUses(const CPlusPlus::Macro ¯o, const QString &replacement = QString());
|
void renameMacroUses(const CPlusPlus::Macro ¯o, const QString &replacement = QString());
|
||||||
|
|
||||||
|
void checkUnused(Core::SearchResult *search, const Utils::Link &link, CPlusPlus::Symbol *symbol,
|
||||||
|
const CPlusPlus::LookupContext &context, const Utils::LinkHandler &callback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupSearch(Core::SearchResult *search);
|
void setupSearch(Core::SearchResult *search);
|
||||||
void onReplaceButtonClicked(Core::SearchResult *search, const QString &text,
|
void onReplaceButtonClicked(Core::SearchResult *search, const QString &text,
|
||||||
|
@@ -26,12 +26,15 @@
|
|||||||
#include "symbolfinder.h"
|
#include "symbolfinder.h"
|
||||||
#include "symbolsfindfilter.h"
|
#include "symbolsfindfilter.h"
|
||||||
|
|
||||||
|
#include <coreplugin/actionmanager/actionmanager.h>
|
||||||
#include <coreplugin/coreconstants.h>
|
#include <coreplugin/coreconstants.h>
|
||||||
#include <coreplugin/documentmanager.h>
|
#include <coreplugin/documentmanager.h>
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
#include <coreplugin/find/searchresultwindow.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <coreplugin/jsexpander.h>
|
#include <coreplugin/jsexpander.h>
|
||||||
#include <coreplugin/messagemanager.h>
|
#include <coreplugin/messagemanager.h>
|
||||||
|
#include <coreplugin/progressmanager/futureprogress.h>
|
||||||
#include <coreplugin/progressmanager/progressmanager.h>
|
#include <coreplugin/progressmanager/progressmanager.h>
|
||||||
#include <coreplugin/vcsmanager.h>
|
#include <coreplugin/vcsmanager.h>
|
||||||
#include <cplusplus/ASTPath.h>
|
#include <cplusplus/ASTPath.h>
|
||||||
@@ -59,9 +62,11 @@
|
|||||||
#include <utils/hostosinfo.h>
|
#include <utils/hostosinfo.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
|
#include <utils/runextensions.h>
|
||||||
#include <utils/savefile.h>
|
#include <utils/savefile.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@@ -72,10 +77,13 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QRegularExpressionMatch>
|
#include <QRegularExpressionMatch>
|
||||||
#include <QTextBlock>
|
#include <QTextBlock>
|
||||||
|
#include <QThread>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QWriteLocker>
|
#include <QWriteLocker>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#if defined(QTCREATOR_WITH_DUMP_AST) && defined(Q_CC_GNU)
|
#if defined(QTCREATOR_WITH_DUMP_AST) && defined(Q_CC_GNU)
|
||||||
#define WITH_AST_DUMP
|
#define WITH_AST_DUMP
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -456,6 +464,179 @@ void CppModelManager::showPreprocessedFile(bool inNextSplit)
|
|||||||
compiler->start();
|
compiler->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FindUnusedActionsEnabledSwitcher
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FindUnusedActionsEnabledSwitcher()
|
||||||
|
: actions{Core::ActionManager::command("CppTools.FindUnusedFunctions"),
|
||||||
|
Core::ActionManager::command("CppTools.FindUnusedFunctionsInSubProject")}
|
||||||
|
{
|
||||||
|
for (Core::Command * const action : actions)
|
||||||
|
action->action()->setEnabled(false);
|
||||||
|
}
|
||||||
|
~FindUnusedActionsEnabledSwitcher()
|
||||||
|
{
|
||||||
|
for (Core::Command * const action : actions)
|
||||||
|
action->action()->setEnabled(true);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const QList<Core::Command *> actions;
|
||||||
|
};
|
||||||
|
using FindUnusedActionsEnabledSwitcherPtr = std::shared_ptr<FindUnusedActionsEnabledSwitcher>;
|
||||||
|
|
||||||
|
static void checkNextFunctionForUnused(
|
||||||
|
const QPointer<Core::SearchResult> &search,
|
||||||
|
const std::shared_ptr<QFutureInterface<bool>> &findRefsFuture,
|
||||||
|
const FindUnusedActionsEnabledSwitcherPtr &actionsSwitcher)
|
||||||
|
{
|
||||||
|
if (!search || findRefsFuture->isCanceled())
|
||||||
|
return;
|
||||||
|
QVariantMap data = search->userData().toMap();
|
||||||
|
QVariant &remainingLinks = data["remaining"];
|
||||||
|
QVariantList remainingLinksList = remainingLinks.toList();
|
||||||
|
QVariant &activeLinks = data["active"];
|
||||||
|
QVariantList activeLinksList = activeLinks.toList();
|
||||||
|
if (remainingLinksList.isEmpty()) {
|
||||||
|
if (activeLinksList.isEmpty()) {
|
||||||
|
search->finishSearch(false);
|
||||||
|
findRefsFuture->reportFinished();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto link = qvariant_cast<Link>(remainingLinksList.takeFirst());
|
||||||
|
activeLinksList << QVariant::fromValue(link);
|
||||||
|
remainingLinks = remainingLinksList;
|
||||||
|
activeLinks = activeLinksList;
|
||||||
|
search->setUserData(data);
|
||||||
|
CppModelManager::instance()->modelManagerSupport(CppModelManager::Backend::Best)
|
||||||
|
->checkUnused(link, search, [search, link, findRefsFuture, actionsSwitcher](const Link &) {
|
||||||
|
if (!search || findRefsFuture->isCanceled())
|
||||||
|
return;
|
||||||
|
const int newProgress = findRefsFuture->progressValue() + 1;
|
||||||
|
findRefsFuture->setProgressValueAndText(newProgress, Tr::tr("Checked %1 of %2 functions")
|
||||||
|
.arg(newProgress).arg(findRefsFuture->progressMaximum()));
|
||||||
|
QVariantMap data = search->userData().toMap();
|
||||||
|
QVariant &activeLinks = data["active"];
|
||||||
|
QVariantList activeLinksList = activeLinks.toList();
|
||||||
|
QTC_CHECK(activeLinksList.removeOne(QVariant::fromValue(link)));
|
||||||
|
activeLinks = activeLinksList;
|
||||||
|
search->setUserData(data);
|
||||||
|
checkNextFunctionForUnused(search, findRefsFuture, actionsSwitcher);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CppModelManager::findUnusedFunctions(const FilePath &folder)
|
||||||
|
{
|
||||||
|
const auto actionsSwitcher = std::make_shared<FindUnusedActionsEnabledSwitcher>();
|
||||||
|
|
||||||
|
// Step 1: Employ locator to find all functions
|
||||||
|
Core::ILocatorFilter *const functionsFilter
|
||||||
|
= Utils::findOrDefault(Core::ILocatorFilter::allLocatorFilters(),
|
||||||
|
Utils::equal(&Core::ILocatorFilter::id,
|
||||||
|
Id(Constants::FUNCTIONS_FILTER_ID)));
|
||||||
|
QTC_ASSERT(functionsFilter, return);
|
||||||
|
const QPointer<Core::SearchResult> search
|
||||||
|
= Core::SearchResultWindow::instance()
|
||||||
|
->startNewSearch(tr("Find Unused Functions"),
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
Core::SearchResultWindow::SearchOnly,
|
||||||
|
Core::SearchResultWindow::PreserveCaseDisabled,
|
||||||
|
"CppEditor");
|
||||||
|
connect(search, &Core::SearchResult::activated, [](const Core::SearchResultItem &item) {
|
||||||
|
Core::EditorManager::openEditorAtSearchResult(item);
|
||||||
|
});
|
||||||
|
Core::SearchResultWindow::instance()->popup(Core::IOutputPane::ModeSwitch
|
||||||
|
| Core::IOutputPane::WithFocus);
|
||||||
|
const auto locatorWatcher = new QFutureWatcher<Core::LocatorFilterEntry>(search);
|
||||||
|
functionsFilter->prepareSearch({});
|
||||||
|
connect(search, &Core::SearchResult::canceled, locatorWatcher, [locatorWatcher] {
|
||||||
|
locatorWatcher->cancel();
|
||||||
|
});
|
||||||
|
connect(locatorWatcher, &QFutureWatcher<Core::LocatorFilterEntry>::finished, search,
|
||||||
|
[locatorWatcher, search, folder, actionsSwitcher] {
|
||||||
|
locatorWatcher->deleteLater();
|
||||||
|
if (locatorWatcher->isCanceled()) {
|
||||||
|
search->finishSearch(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Links links;
|
||||||
|
for (int i = 0; i < locatorWatcher->future().resultCount(); ++i) {
|
||||||
|
const Core::LocatorFilterEntry &entry = locatorWatcher->resultAt(i);
|
||||||
|
static const QStringList prefixBlacklist{"main(", "~", "qHash(", "begin()", "end()",
|
||||||
|
"cbegin()", "cend()", "constBegin()", "constEnd()"};
|
||||||
|
if (Utils::anyOf(prefixBlacklist, [&entry](const QString &prefix) {
|
||||||
|
return entry.displayName.startsWith(prefix); })) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Link link;
|
||||||
|
if (entry.internalData.canConvert<Link>()) {
|
||||||
|
link = qvariant_cast<Link>(entry.internalData);
|
||||||
|
} else {
|
||||||
|
const auto item = qvariant_cast<IndexItem::Ptr>(entry.internalData);
|
||||||
|
if (item) {
|
||||||
|
link = Link(FilePath::fromString(item->fileName()), item->line(),
|
||||||
|
item->column());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (link.hasValidTarget() && link.targetFilePath.isReadableFile()
|
||||||
|
&& (folder.isEmpty() || link.targetFilePath.isChildOf(folder))
|
||||||
|
&& SessionManager::projectForFile(link.targetFilePath)) {
|
||||||
|
links << link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (links.isEmpty()) {
|
||||||
|
search->finishSearch(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QVariantMap remainingAndActiveLinks;
|
||||||
|
remainingAndActiveLinks.insert("active", QVariantList());
|
||||||
|
remainingAndActiveLinks.insert("remaining",
|
||||||
|
Utils::transform<QVariantList>(links, [](const Link &l) { return QVariant::fromValue(l);
|
||||||
|
}));
|
||||||
|
search->setUserData(remainingAndActiveLinks);
|
||||||
|
const auto findRefsFuture = std::make_shared<QFutureInterface<bool>>();
|
||||||
|
Core::FutureProgress *const progress
|
||||||
|
= Core::ProgressManager::addTask(findRefsFuture->future(),
|
||||||
|
Tr::tr("Finding Unused Functions"),
|
||||||
|
"CppEditor.FindUnusedFunctions");
|
||||||
|
connect(progress,
|
||||||
|
&Core::FutureProgress::canceled,
|
||||||
|
search,
|
||||||
|
[search, future = std::weak_ptr<QFutureInterface<bool>>(findRefsFuture)] {
|
||||||
|
search->finishSearch(true);
|
||||||
|
if (const auto f = future.lock()) {
|
||||||
|
f->cancel();
|
||||||
|
f->reportFinished();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
findRefsFuture->setProgressRange(0, links.size());
|
||||||
|
connect(search, &Core::SearchResult::canceled, [findRefsFuture] {
|
||||||
|
findRefsFuture->cancel();
|
||||||
|
findRefsFuture->reportFinished();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Step 2: Forward search results one by one to backend to check which functions are unused.
|
||||||
|
// We keep several requests in flight for decent throughput.
|
||||||
|
const int inFlightCount = std::min(QThread::idealThreadCount() / 2 + 1, int(links.size()));
|
||||||
|
for (int i = 0; i < inFlightCount; ++i)
|
||||||
|
checkNextFunctionForUnused(search, findRefsFuture, actionsSwitcher);
|
||||||
|
});
|
||||||
|
locatorWatcher->setFuture(
|
||||||
|
Utils::runAsync([functionsFilter](QFutureInterface<Core::LocatorFilterEntry> &future) {
|
||||||
|
future.reportResults(functionsFilter->matchesFor(future, {}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CppModelManager::checkForUnusedSymbol(Core::SearchResult *search,
|
||||||
|
const Link &link,
|
||||||
|
CPlusPlus::Symbol *symbol,
|
||||||
|
const CPlusPlus::LookupContext &context,
|
||||||
|
const LinkHandler &callback)
|
||||||
|
{
|
||||||
|
instance()->d->m_findReferences->checkUnused(search, link, symbol, context, callback);
|
||||||
|
}
|
||||||
|
|
||||||
int argumentPositionOf(const AST *last, const CallAST *callAst)
|
int argumentPositionOf(const AST *last, const CallAST *callAst)
|
||||||
{
|
{
|
||||||
if (!callAst || !callAst->expression_list)
|
if (!callAst || !callAst->expression_list)
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
namespace Core {
|
namespace Core {
|
||||||
class IDocument;
|
class IDocument;
|
||||||
class IEditor;
|
class IEditor;
|
||||||
|
class SearchResult;
|
||||||
}
|
}
|
||||||
namespace CPlusPlus {
|
namespace CPlusPlus {
|
||||||
class AST;
|
class AST;
|
||||||
@@ -181,6 +182,11 @@ public:
|
|||||||
static void findUsages(const CursorInEditor &data, Backend backend = Backend::Best);
|
static void findUsages(const CursorInEditor &data, Backend backend = Backend::Best);
|
||||||
static void switchHeaderSource(bool inNextSplit, Backend backend = Backend::Best);
|
static void switchHeaderSource(bool inNextSplit, Backend backend = Backend::Best);
|
||||||
static void showPreprocessedFile(bool inNextSplit);
|
static void showPreprocessedFile(bool inNextSplit);
|
||||||
|
static void findUnusedFunctions(const Utils::FilePath &folder);
|
||||||
|
static void checkForUnusedSymbol(Core::SearchResult *search, const Utils::Link &link,
|
||||||
|
CPlusPlus::Symbol *symbol,
|
||||||
|
const CPlusPlus::LookupContext &context,
|
||||||
|
const Utils::LinkHandler &callback);
|
||||||
|
|
||||||
static Core::ILocatorFilter *createAuxiliaryCurrentDocumentFilter();
|
static Core::ILocatorFilter *createAuxiliaryCurrentDocumentFilter();
|
||||||
|
|
||||||
@@ -234,6 +240,8 @@ public:
|
|||||||
// for VcsBaseSubmitEditor
|
// for VcsBaseSubmitEditor
|
||||||
Q_INVOKABLE QSet<QString> symbolsInFiles(const QSet<Utils::FilePath> &files) const;
|
Q_INVOKABLE QSet<QString> symbolsInFiles(const QSet<Utils::FilePath> &files) const;
|
||||||
|
|
||||||
|
ModelManagerSupport *modelManagerSupport(Backend backend) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/// Project data might be locked while this is emitted.
|
/// Project data might be locked while this is emitted.
|
||||||
void aboutToRemoveFiles(const QStringList &files);
|
void aboutToRemoveFiles(const QStringList &files);
|
||||||
@@ -281,8 +289,6 @@ private:
|
|||||||
|
|
||||||
WorkingCopy buildWorkingCopyList();
|
WorkingCopy buildWorkingCopyList();
|
||||||
|
|
||||||
ModelManagerSupport *modelManagerSupport(Backend backend) const;
|
|
||||||
|
|
||||||
void ensureUpdated();
|
void ensureUpdated();
|
||||||
QStringList internalProjectFiles() const;
|
QStringList internalProjectFiles() const;
|
||||||
ProjectExplorer::HeaderPaths internalHeaderPaths() const;
|
ProjectExplorer::HeaderPaths internalHeaderPaths() const;
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Core { class SearchResult; }
|
||||||
namespace TextEditor {
|
namespace TextEditor {
|
||||||
class TextDocument;
|
class TextDocument;
|
||||||
class BaseHoverHandler;
|
class BaseHoverHandler;
|
||||||
@@ -51,6 +52,9 @@ public:
|
|||||||
virtual void globalRename(const CursorInEditor &data, const QString &replacement) = 0;
|
virtual void globalRename(const CursorInEditor &data, const QString &replacement) = 0;
|
||||||
virtual void findUsages(const CursorInEditor &data) const = 0;
|
virtual void findUsages(const CursorInEditor &data) const = 0;
|
||||||
virtual void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) = 0;
|
virtual void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) = 0;
|
||||||
|
|
||||||
|
virtual void checkUnused(const Utils::Link &link, Core::SearchResult *search,
|
||||||
|
const Utils::LinkHandler &callback) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // CppEditor namespace
|
} // CppEditor namespace
|
||||||
|
@@ -1180,12 +1180,12 @@ void tst_FindUsages::templateClass_className()
|
|||||||
findUsages(classTS);
|
findUsages(classTS);
|
||||||
QCOMPARE(findUsages.usages().size(), 7);
|
QCOMPARE(findUsages.usages().size(), 7);
|
||||||
QCOMPARE(findUsages.usages().at(0).tags, Usage::Tag::Declaration);
|
QCOMPARE(findUsages.usages().at(0).tags, Usage::Tag::Declaration);
|
||||||
QCOMPARE(findUsages.usages().at(1).tags, Usage::Tags());
|
QCOMPARE(findUsages.usages().at(1).tags, Usage::Tag::ConstructorDestructor);
|
||||||
QCOMPARE(findUsages.usages().at(2).tags, Usage::Tags());
|
QCOMPARE(findUsages.usages().at(2).tags, Usage::Tag::ConstructorDestructor);
|
||||||
QCOMPARE(findUsages.usages().at(3).tags, Usage::Tags());
|
QCOMPARE(findUsages.usages().at(3).tags, Usage::Tags());
|
||||||
QCOMPARE(findUsages.usages().at(4).tags, Usage::Tags());
|
QCOMPARE(findUsages.usages().at(4).tags, Usage::Tag::ConstructorDestructor);
|
||||||
QCOMPARE(findUsages.usages().at(5).tags, Usage::Tags());
|
QCOMPARE(findUsages.usages().at(5).tags, Usage::Tags());
|
||||||
QCOMPARE(findUsages.usages().at(6).tags, Usage::Tags());
|
QCOMPARE(findUsages.usages().at(6).tags, Usage::Tag::ConstructorDestructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_FindUsages::templateFunctionParameters()
|
void tst_FindUsages::templateFunctionParameters()
|
||||||
@@ -2324,7 +2324,7 @@ int main()
|
|||||||
find(structS);
|
find(structS);
|
||||||
QCOMPARE(find.usages().size(), 18);
|
QCOMPARE(find.usages().size(), 18);
|
||||||
QCOMPARE(find.usages().at(0).tags, Usage::Tag::Declaration);
|
QCOMPARE(find.usages().at(0).tags, Usage::Tag::Declaration);
|
||||||
QCOMPARE(find.usages().at(1).tags, Usage::Tags());
|
QCOMPARE(find.usages().at(1).tags, Usage::Tag::ConstructorDestructor);
|
||||||
QCOMPARE(find.usages().at(2).tags, Usage::Tags());
|
QCOMPARE(find.usages().at(2).tags, Usage::Tags());
|
||||||
QCOMPARE(find.usages().at(3).tags, Usage::Tags());
|
QCOMPARE(find.usages().at(3).tags, Usage::Tags());
|
||||||
QCOMPARE(find.usages().at(4).tags, Usage::Tags());
|
QCOMPARE(find.usages().at(4).tags, Usage::Tags());
|
||||||
|
Reference in New Issue
Block a user