summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/plugins/clangcodemodel/CMakeLists.txt3
-rw-r--r--src/plugins/clangcodemodel/clangcodemodel.pro2
-rw-r--r--src/plugins/clangcodemodel/clangcodemodel.qbs3
-rw-r--r--src/plugins/clangcodemodel/clangcodemodel_dependencies.pri2
-rw-r--r--src/plugins/clangcodemodel/clangcodemodelplugin.cpp2
-rw-r--r--src/plugins/clangcodemodel/clangdclient.cpp525
-rw-r--r--src/plugins/clangcodemodel/clangdclient.h23
-rw-r--r--src/plugins/clangcodemodel/clangmodelmanagersupport.cpp21
-rw-r--r--src/plugins/clangcodemodel/clangmodelmanagersupport.h3
-rw-r--r--src/plugins/clangcodemodel/clangrefactoringengine.cpp4
-rw-r--r--src/plugins/clangcodemodel/test/clangautomationutils.cpp5
-rw-r--r--src/plugins/clangcodemodel/test/clangautomationutils.h2
-rw-r--r--src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp3
-rw-r--r--src/plugins/clangcodemodel/test/clangdtests.cpp331
-rw-r--r--src/plugins/clangcodemodel/test/clangdtests.h47
-rw-r--r--src/plugins/clangcodemodel/test/data/clangtestdata.qrc3
-rw-r--r--src/plugins/clangcodemodel/test/data/find-usages/defs.h28
-rw-r--r--src/plugins/clangcodemodel/test/data/find-usages/find-usages.pro4
-rw-r--r--src/plugins/clangcodemodel/test/data/find-usages/main.cpp60
19 files changed, 1042 insertions, 29 deletions
diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt
index c8c8d9ec7f..6da3ac14b4 100644
--- a/src/plugins/clangcodemodel/CMakeLists.txt
+++ b/src/plugins/clangcodemodel/CMakeLists.txt
@@ -1,7 +1,7 @@
add_qtc_plugin(ClangCodeModel
CONDITION TARGET libclang
DEPENDS ClangSupport CPlusPlus
- PLUGIN_DEPENDS Core CppTools LanguageClient TextEditor
+ PLUGIN_DEPENDS Core CppTools LanguageClient QtSupport TextEditor
PLUGIN_TEST_DEPENDS CppEditor QmakeProjectManager
SOURCES
clangactivationsequencecontextprocessor.cpp clangactivationsequencecontextprocessor.h
@@ -52,5 +52,6 @@ extend_qtc_plugin(ClangCodeModel
test/clangautomationutils.cpp test/clangautomationutils.h
test/clangbatchfileprocessor.cpp test/clangbatchfileprocessor.h
test/clangcodecompletion_test.cpp test/clangcodecompletion_test.h
+ test/clangdtests.cpp test/clangdtests.h
test/data/clangtestdata.qrc
)
diff --git a/src/plugins/clangcodemodel/clangcodemodel.pro b/src/plugins/clangcodemodel/clangcodemodel.pro
index 22623b7c16..f6d7b79071 100644
--- a/src/plugins/clangcodemodel/clangcodemodel.pro
+++ b/src/plugins/clangcodemodel/clangcodemodel.pro
@@ -96,11 +96,13 @@ equals(TEST, 1) {
test/clangautomationutils.h \
test/clangbatchfileprocessor.h \
test/clangcodecompletion_test.h \
+ test/clangdtests.h
SOURCES += \
test/clangautomationutils.cpp \
test/clangbatchfileprocessor.cpp \
test/clangcodecompletion_test.cpp \
+ test/clangdtests.cpp
RESOURCES += test/data/clangtestdata.qrc
OTHER_FILES += $$files(test/data/*)
diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs
index d291d50fc0..d16c11f732 100644
--- a/src/plugins/clangcodemodel/clangcodemodel.qbs
+++ b/src/plugins/clangcodemodel/clangcodemodel.qbs
@@ -8,6 +8,7 @@ QtcPlugin {
Depends { name: "Core" }
Depends { name: "CppTools" }
Depends { name: "ProjectExplorer" }
+ Depends { name: "QtSupport"; condition: qtc.testsEnabled }
Depends { name: "TextEditor" }
Depends { name: "Utils" }
Depends { name: "ClangSupport" }
@@ -114,6 +115,8 @@ QtcPlugin {
"clangbatchfileprocessor.h",
"clangcodecompletion_test.cpp",
"clangcodecompletion_test.h",
+ "clangdtests.cpp",
+ "clangdtests.h",
"data/clangtestdata.qrc",
]
}
diff --git a/src/plugins/clangcodemodel/clangcodemodel_dependencies.pri b/src/plugins/clangcodemodel/clangcodemodel_dependencies.pri
index eb6635835b..f32eaa4c4f 100644
--- a/src/plugins/clangcodemodel/clangcodemodel_dependencies.pri
+++ b/src/plugins/clangcodemodel/clangcodemodel_dependencies.pri
@@ -10,3 +10,5 @@ QTC_PLUGIN_DEPENDS += \
QTC_TEST_DEPENDS += \
cppeditor \
qmakeprojectmanager
+
+equals(TEST, 1): QTC_PLUGIN_DEPENDS += qtsupport
diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
index 81c596b909..126b3e9b5b 100644
--- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
+++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
@@ -32,6 +32,7 @@
#ifdef WITH_TESTS
# include "test/clangbatchfileprocessor.h"
# include "test/clangcodecompletion_test.h"
+# include "test/clangdtests.h"
#endif
#include <coreplugin/actionmanager/actioncontainer.h>
@@ -204,6 +205,7 @@ QVector<QObject *> ClangCodeModelPlugin::createTestObjects() const
{
return {
new Tests::ClangCodeCompletionTest,
+ new Tests::ClangdTests,
};
}
#endif
diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp
index aef95e7d52..c7e543c439 100644
--- a/src/plugins/clangcodemodel/clangdclient.cpp
+++ b/src/plugins/clangcodemodel/clangdclient.cpp
@@ -25,10 +25,21 @@
#include "clangdclient.h"
+#include <coreplugin/find/searchresultitem.h>
+#include <coreplugin/find/searchresultwindow.h>
+#include <cplusplus/FindUsages.h>
#include <cpptools/cppcodemodelsettings.h>
+#include <cpptools/cppfindreferences.h>
#include <cpptools/cpptoolsreuse.h>
#include <languageclient/languageclientinterface.h>
+#include <QFile>
+#include <QHash>
+#include <QPointer>
+#include <QRegularExpression>
+
+using namespace CPlusPlus;
+using namespace Core;
using namespace LanguageClient;
using namespace LanguageServerProtocol;
@@ -36,13 +47,240 @@ namespace ClangCodeModel {
namespace Internal {
static Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg);
-
static QString indexingToken() { return "backgroundIndexProgress"; }
+class AstParams : public JsonObject
+{
+public:
+ AstParams() {}
+ AstParams(const TextDocumentIdentifier &document, const Range &range);
+ using JsonObject::JsonObject;
+
+ // The open file to inspect.
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); }
+
+ // The region of the source code whose AST is fetched. The highest-level node that entirely
+ // contains the range is returned.
+ Utils::optional<Range> range() const { return optionalValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ bool isValid() const override { return contains(textDocumentKey); }
+};
+
+class AstNode : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ static constexpr char roleKey[] = "role";
+ static constexpr char arcanaKey[] = "arcana";
+
+ // The general kind of node, such as “expression”. Corresponds to clang’s base AST node type,
+ // such as Expr. The most common are “expression”, “statement”, “type” and “declaration”.
+ QString role() const { return typedValue<QString>(roleKey); }
+
+ // The specific kind of node, such as “BinaryOperator”. Corresponds to clang’s concrete
+ // node class, with Expr etc suffix dropped.
+ QString kind() const { return typedValue<QString>(kindKey); }
+
+ // Brief additional details, such as ‘||’. Information present here depends on the node kind.
+ Utils::optional<QString> detail() const { return optionalValue<QString>(detailKey); }
+
+ // One line dump of information, similar to that printed by clang -Xclang -ast-dump.
+ // Only available for certain types of nodes.
+ Utils::optional<QString> arcana() const { return optionalValue<QString>(arcanaKey); }
+
+ // The part of the code that produced this node. Missing for implicit nodes, nodes produced
+ // by macro expansion, etc.
+ Range range() const { return typedValue<Range>(rangeKey); }
+
+ // Descendants describing the internal structure. The tree of nodes is similar to that printed
+ // by clang -Xclang -ast-dump, or that traversed by clang::RecursiveASTVisitor.
+ Utils::optional<QList<AstNode>> children() const { return optionalArray<AstNode>(childrenKey); }
+
+ bool hasRange() const { return contains(rangeKey); }
+
+ bool arcanaContains(const QString &s) const
+ {
+ const Utils::optional<QString> arcanaString = arcana();
+ return arcanaString && arcanaString->contains(s);
+ }
+
+ bool detailIs(const QString &s) const
+ {
+ return detail() && detail().value() == s;
+ }
+
+ QString type() const
+ {
+ const Utils::optional<QString> arcanaString = arcana();
+ if (!arcanaString)
+ return {};
+ const int quote1Offset = arcanaString->indexOf('\'');
+ if (quote1Offset == -1)
+ return {};
+ const int quote2Offset = arcanaString->indexOf('\'', quote1Offset + 1);
+ if (quote2Offset == -1)
+ return {};
+ return arcanaString->mid(quote1Offset + 1, quote2Offset - quote1Offset - 1);
+ }
+
+ // Returns true <=> the type is "recursively const".
+ // E.g. returns true for "const int &", "const int *" and "const int * const *",
+ // and false for "int &" and "const int **".
+ // For non-pointer types such as "int", we check whether they are uses as lvalues
+ // or rvalues.
+ bool hasConstType() const
+ {
+ QString theType = type();
+ if (theType.endsWith("const"))
+ theType.chop(5);
+ const int ptrRefCount = theType.count('*') + theType.count('&');
+ const int constCount = theType.count("const");
+ if (ptrRefCount == 0)
+ return constCount > 0 || detailIs("LValueToRValue");
+ return ptrRefCount <= constCount;
+ }
+
+ bool childContainsRange(int index, const Range &range) const
+ {
+ const Utils::optional<QList<AstNode>> childList = children();
+ return childList && childList->size() > index
+ && childList->at(index).range().contains(range);
+ }
+
+ QString operatorString() const
+ {
+ if (kind() == "BinaryOperator")
+ return detail().value_or(QString());
+ QTC_ASSERT(kind() == "CXXOperatorCall", return {});
+ const Utils::optional<QString> arcanaString = arcana();
+ if (!arcanaString)
+ return {};
+ const int closingQuoteOffset = arcanaString->lastIndexOf('\'');
+ if (closingQuoteOffset <= 0)
+ return {};
+ const int openingQuoteOffset = arcanaString->lastIndexOf('\'', closingQuoteOffset - 1);
+ if (openingQuoteOffset == -1)
+ return {};
+ return arcanaString->mid(openingQuoteOffset + 1, closingQuoteOffset
+ - openingQuoteOffset - 1);
+ }
+
+ bool isValid() const override
+ {
+ return contains(roleKey) && contains(kindKey);
+ }
+};
+
+static QList<AstNode> getAstPath(const AstNode &root, const Range &range)
+{
+ QList<AstNode> path;
+ QList<AstNode> queue{root};
+ bool isRoot = true;
+ while (!queue.isEmpty()) {
+ AstNode curNode = queue.takeFirst();
+ if (!isRoot && !curNode.hasRange())
+ continue;
+ if (curNode.range() == range)
+ return path << curNode;
+ if (isRoot || curNode.range().contains(range)) {
+ path << curNode;
+ const auto children = curNode.children();
+ if (!children)
+ break;
+ queue = children.value();
+ }
+ isRoot = false;
+ }
+ return path;
+}
+
+static Usage::Type getUsageType(const QList<AstNode> &path)
+{
+ bool potentialWrite = false;
+ const bool symbolIsDataType = path.last().role() == "type" && path.last().kind() == "Record";
+ for (auto pathIt = path.rbegin(); pathIt != path.rend(); ++pathIt) {
+ if (pathIt->arcanaContains("non_odr_use_unevaluated"))
+ return Usage::Type::Other;
+ if (pathIt->kind() == "CXXDelete")
+ return Usage::Type::Write;
+ if (pathIt->kind() == "CXXNew")
+ return Usage::Type::Other;
+ if (pathIt->kind() == "Switch" || pathIt->kind() == "If")
+ return Usage::Type::Read;
+ if (pathIt->kind() == "Call" || pathIt->kind() == "CXXMemberCall")
+ return potentialWrite ? Usage::Type::WritableRef : Usage::Type::Read;
+ if ((pathIt->kind() == "DeclRef" || pathIt->kind() == "Member")
+ && pathIt->arcanaContains("lvalue")) {
+ potentialWrite = true;
+ }
+ if (pathIt->role() == "declaration") {
+ if (symbolIsDataType)
+ return Usage::Type::Other;
+ if (pathIt->arcanaContains("cinit")) {
+ if (pathIt == path.rbegin())
+ return Usage::Type::Initialization;
+ if (pathIt->childContainsRange(0, path.last().range()))
+ return Usage::Type::Initialization;
+ if (!pathIt->hasConstType())
+ return Usage::Type::WritableRef;
+ return Usage::Type::Read;
+ }
+ return Usage::Type::Declaration;
+ }
+ if (pathIt->kind() == "MemberInitializer")
+ return pathIt == path.rbegin() ? Usage::Type::Write : Usage::Type::Read;
+ if (pathIt->kind() == "UnaryOperator"
+ && (pathIt->detailIs("++") || pathIt->detailIs("--"))) {
+ return Usage::Type::Write;
+ }
+
+ // LLVM uses BinaryOperator only for built-in types; for classes, CXXOperatorCall
+ // is used. The latter has an additional node at index 0, so the left-hand side
+ // of an assignment is at index 1.
+ const bool isBinaryOp = pathIt->kind() == "BinaryOperator";
+ const bool isOpCall = pathIt->kind() == "CXXOperatorCall";
+ if (isBinaryOp || isOpCall) {
+ if (isOpCall && symbolIsDataType) // Constructor invocation.
+ return Usage::Type::Other;
+
+ const QString op = pathIt->operatorString();
+ if (op.endsWith("=") && op != "==") { // Assignment.
+ const int lhsIndex = isBinaryOp ? 0 : 1;
+ if (pathIt->childContainsRange(lhsIndex, path.last().range()))
+ return Usage::Type::Write;
+ return potentialWrite ? Usage::Type::WritableRef : Usage::Type::Read;
+ }
+ return Usage::Type::Read;
+ }
+
+ if (pathIt->kind() == "ImplicitCast") {
+ if (pathIt->detailIs("FunctionToPointerDecay"))
+ return Usage::Type::Other;
+ if (pathIt->hasConstType())
+ return Usage::Type::Read;
+ potentialWrite = true;
+ continue;
+ }
+ }
+
+ return Usage::Type::Other;
+}
+
+class AstRequest : public Request<AstNode, std::nullptr_t, AstParams>
+{
+public:
+ using Request::Request;
+ explicit AstRequest(const AstParams &params) : Request("textDocument/ast", params) {}
+};
+
static BaseClientInterface *clientInterface(const Utils::FilePath &jsonDbDir)
{
Utils::CommandLine cmd{CppTools::codeModelSettings()->clangdFilePath(),
- {"--index", "--background-index", "--limit-results=0"}};
+ {"--background-index", "--limit-results=0"}};
if (!jsonDbDir.isEmpty())
cmd.addArg("--compile-commands-dir=" + jsonDbDir.toString());
if (clangdLog().isDebugEnabled())
@@ -52,8 +290,44 @@ static BaseClientInterface *clientInterface(const Utils::FilePath &jsonDbDir)
return interface;
}
+class ReferencesFileData {
+public:
+ QList<QPair<Range, QString>> rangesAndLineText;
+ QString fileContent;
+ AstNode ast;
+};
+class ReferencesData {
+public:
+ void setCanceled() { search->setUserData(true); }
+ bool isCanceled() const { return search && search->userData().toBool(); }
+
+ QMap<DocumentUri, ReferencesFileData> fileData;
+ QList<MessageId> pendingAstRequests;
+ QPointer<SearchResult> search;
+ quint64 key;
+};
+
+class ClangdClient::Private
+{
+public:
+ Private(ClangdClient *q) : q(q) {}
+
+ void handleFindUsagesResult(quint64 key, const QList<Location> &locations);
+ void addSearchResultsForFile(const ReferencesData &refData, const Utils::FilePath &file,
+ const ReferencesFileData &fileData);
+ void reportAllSearchResultsAndFinish(const ReferencesData &data);
+ void finishSearch(const ReferencesData &refData, bool canceled);
+
+ ClangdClient * const q;
+ QHash<quint64, ReferencesData> runningFindUsages;
+ Utils::optional<QVersionNumber> versionNumber;
+ quint64 nextFindUsagesKey = 0;
+ bool isFullyIndexed = false;
+ bool isTesting = false;
+};
+
ClangdClient::ClangdClient(ProjectExplorer::Project *project, const Utils::FilePath &jsonDbDir)
- : Client(clientInterface(jsonDbDir))
+ : Client(clientInterface(jsonDbDir)), d(new Private(this))
{
setName(tr("clangd"));
LanguageFilter langFilter;
@@ -69,11 +343,252 @@ ClangdClient::ClangdClient(ProjectExplorer::Project *project, const Utils::FileP
setCurrentProject(project);
connect(this, &Client::workDone, this, [this](const ProgressToken &token) {
const QString * const val = Utils::get_if<QString>(&token);
- if (val && *val == indexingToken())
- m_isFullyIndexed = true;
+ if (val && *val == indexingToken()) {
+ d->isFullyIndexed = true;
+ emit indexingFinished();
+ }
});
+
+ connect(this, &Client::initialized, this, [this] {
+ // If we get this signal while there are pending searches, it means that
+ // the client was re-initialized, i.e. clangd crashed.
+
+ // Report all search results found so far.
+ for (quint64 key : d->runningFindUsages.keys())
+ d->reportAllSearchResultsAndFinish(d->runningFindUsages.value(key));
+ QTC_CHECK(d->runningFindUsages.isEmpty());
+ });
+
start();
}
+ClangdClient::~ClangdClient()
+{
+ delete d;
+}
+
+bool ClangdClient::isFullyIndexed() const { return d->isFullyIndexed; }
+
+void ClangdClient::openExtraFile(const Utils::FilePath &filePath, const QString &content)
+{
+ QFile cxxFile(filePath.toString());
+ if (content.isEmpty() && !cxxFile.open(QIODevice::ReadOnly))
+ return;
+ TextDocumentItem item;
+ item.setLanguageId("cpp");
+ item.setUri(DocumentUri::fromFilePath(filePath));
+ item.setText(!content.isEmpty() ? content : QString::fromUtf8(cxxFile.readAll()));
+ item.setVersion(0);
+ sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
+}
+
+void ClangdClient::closeExtraFile(const Utils::FilePath &filePath)
+{
+ sendContent(DidCloseTextDocumentNotification(DidCloseTextDocumentParams(
+ TextDocumentIdentifier{DocumentUri::fromFilePath(filePath)})));
+}
+
+void ClangdClient::findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor)
+{
+ if (versionNumber() < QVersionNumber(13)) {
+ symbolSupport().findUsages(document, cursor);
+ return;
+ }
+
+ QTextCursor termCursor(cursor);
+ termCursor.select(QTextCursor::WordUnderCursor);
+ const QString searchTerm = termCursor.selectedText(); // TODO: This will be wrong for e.g. operators. Use a Symbol info request to get the real symbol string.
+ if (searchTerm.isEmpty())
+ return;
+
+ ReferencesData refData;
+ refData.search = SearchResultWindow::instance()->startNewSearch(
+ tr("C++ Usages:"),
+ {},
+ searchTerm,
+ SearchResultWindow::SearchOnly,
+ SearchResultWindow::PreserveCaseDisabled,
+ "CppEditor");
+ refData.search->setFilter(new CppTools::CppSearchResultFilter);
+ connect(refData.search, &SearchResult::activated, [](const SearchResultItem& item) {
+ Core::EditorManager::openEditorAtSearchResult(item);
+ });
+ SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
+ refData.key = d->nextFindUsagesKey++;
+ d->runningFindUsages.insert(refData.key, refData);
+
+ const Utils::optional<MessageId> requestId = symbolSupport().findUsages(
+ document, cursor, [this, key = refData.key](const QList<Location> &locations) {
+ d->handleFindUsagesResult(key, locations);
+ });
+
+ if (!requestId) {
+ d->finishSearch(refData, false);
+ return;
+ }
+ connect(refData.search, &SearchResult::cancelled, this, [this, requestId, key = refData.key] {
+ const auto refData = d->runningFindUsages.find(key);
+ if (refData == d->runningFindUsages.end())
+ return;
+ cancelRequest(*requestId);
+ refData->setCanceled();
+ refData->search->disconnect(this);
+ d->finishSearch(*refData, true);
+ });
+}
+
+void ClangdClient::enableTesting() { d->isTesting = true; }
+
+QVersionNumber ClangdClient::versionNumber() const
+{
+ if (d->versionNumber)
+ return d->versionNumber.value();
+
+ const QRegularExpression versionPattern("^clangd version (\\d+)\\.(\\d+)\\.(\\d+).*$");
+ QTC_CHECK(versionPattern.isValid());
+ const QRegularExpressionMatch match = versionPattern.match(serverVersion());
+ if (match.isValid()) {
+ d->versionNumber.emplace({match.captured(1).toInt(), match.captured(2).toInt(),
+ match.captured(3).toInt()});
+ } else {
+ qCWarning(clangdLog) << "Failed to parse clangd server string" << serverVersion();
+ d->versionNumber.emplace({0});
+ }
+ return d->versionNumber.value();
+}
+
+void ClangdClient::Private::handleFindUsagesResult(quint64 key, const QList<Location> &locations)
+{
+ const auto refData = runningFindUsages.find(key);
+ if (refData == runningFindUsages.end())
+ return;
+ if (!refData->search || refData->isCanceled()) {
+ finishSearch(*refData, true);
+ return;
+ }
+ refData->search->disconnect(q);
+
+ qCDebug(clangdLog) << "found" << locations.size() << "locations";
+ if (locations.isEmpty()) {
+ finishSearch(*refData, false);
+ return;
+ }
+
+ QObject::connect(refData->search, &SearchResult::cancelled, q, [this, key] {
+ const auto refData = runningFindUsages.find(key);
+ if (refData == runningFindUsages.end())
+ return;
+ refData->setCanceled();
+ refData->search->disconnect(q);
+ for (const MessageId &id : qAsConst(refData->pendingAstRequests))
+ q->cancelRequest(id);
+ refData->pendingAstRequests.clear();
+ finishSearch(*refData, true);
+ });
+
+ for (const Location &loc : locations) // TODO: Can contain duplicates. Rather fix in clang than work around it here.
+ refData->fileData[loc.uri()].rangesAndLineText << qMakePair(loc.range(), QString()); // TODO: Can we assume that locations for the same file are grouped?
+ for (auto it = refData->fileData.begin(); it != refData->fileData.end(); ++it) {
+ const QStringList lines = SymbolSupport::getFileContents(
+ it.key().toFilePath().toString());
+ it->fileContent = lines.join('\n');
+ for (auto &rangeWithText : it.value().rangesAndLineText) {
+ const int lineNo = rangeWithText.first.start().line();
+ if (lineNo >= 0 && lineNo < lines.size())
+ rangeWithText.second = lines.at(lineNo);
+ }
+ }
+
+ qCDebug(clangdLog) << "document count is" << refData->fileData.size();
+ if (refData->fileData.size() > 15) { // TODO: If we need to keep this, make it configurable.
+ qCDebug(clangdLog) << "skipping AST retrieval";
+ reportAllSearchResultsAndFinish(*refData);
+ return;
+ }
+
+ for (auto it = refData->fileData.begin(); it != refData->fileData.end(); ++it) {
+ const bool extraOpen = !q->documentForFilePath(it.key().toFilePath());
+ if (extraOpen)
+ q->openExtraFile(it.key().toFilePath(), it->fileContent);
+ it->fileContent.clear();
+
+ AstParams params;
+ params.setTextDocument(TextDocumentIdentifier(it.key()));
+ AstRequest request(params);
+ request.setResponseCallback([this, key, loc = it.key(), request]
+ (AstRequest::Response response) {
+ qCDebug(clangdLog) << "AST response for" << loc.toFilePath();
+ const auto refData = runningFindUsages.find(key);
+ if (refData == runningFindUsages.end())
+ return;
+ if (!refData->search || refData->isCanceled())
+ return;
+ ReferencesFileData &data = refData->fileData[loc];
+ const auto result = response.result();
+ if (result)
+ data.ast = *result;
+ refData->pendingAstRequests.removeOne(request.id());
+ qCDebug(clangdLog) << refData->pendingAstRequests.size()
+ << "AST requests still pending";
+ addSearchResultsForFile(*refData, loc.toFilePath(), data);
+ refData->fileData.remove(loc);
+ if (refData->pendingAstRequests.isEmpty()) {
+ qDebug(clangdLog) << "retrieved all ASTs";
+ finishSearch(*refData, false);
+ }
+ });
+ qCDebug(clangdLog) << "requesting AST for" << it.key().toFilePath();
+ refData->pendingAstRequests << request.id();
+ q->sendContent(request);
+
+ if (extraOpen)
+ q->closeExtraFile(it.key().toFilePath());
+ }
+}
+
+void ClangdClient::Private::addSearchResultsForFile(const ReferencesData &refData,
+ const Utils::FilePath &file,
+ const ReferencesFileData &fileData)
+{
+ QList<SearchResultItem> items;
+ qCDebug(clangdLog) << file << "has valid AST:" << fileData.ast.isValid();
+ for (const auto &rangeWithText : fileData.rangesAndLineText) {
+ const Range &range = rangeWithText.first;
+ const Usage::Type usageType = fileData.ast.isValid()
+ ? getUsageType(getAstPath(fileData.ast, qAsConst(range)))
+ : Usage::Type::Other;
+ SearchResultItem item;
+ item.setUserData(int(usageType));
+ item.setStyle(CppTools::colorStyleForUsageType(usageType));
+ item.setFilePath(file);
+ item.setMainRange(SymbolSupport::convertRange(range));
+ item.setUseTextEditorFont(true);
+ item.setLineText(rangeWithText.second);
+ items << item;
+ }
+ if (isTesting)
+ emit q->foundReferences(items);
+ else
+ refData.search->addResults(items, SearchResult::AddOrdered);
+}
+
+void ClangdClient::Private::reportAllSearchResultsAndFinish(const ReferencesData &refData)
+{
+ for (auto it = refData.fileData.begin(); it != refData.fileData.end(); ++it)
+ addSearchResultsForFile(refData, it.key().toFilePath(), it.value());
+ finishSearch(refData, refData.isCanceled());
+}
+
+void ClangdClient::Private::finishSearch(const ReferencesData &refData, bool canceled)
+{
+ if (isTesting) {
+ emit q->findUsagesDone();
+ } else if (refData.search) {
+ refData.search->finishSearch(canceled);
+ refData.search->disconnect(q);
+ }
+ runningFindUsages.remove(refData.key);
+}
+
} // namespace Internal
} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h
index f639419bbd..f5d7c022f7 100644
--- a/src/plugins/clangcodemodel/clangdclient.h
+++ b/src/plugins/clangcodemodel/clangdclient.h
@@ -27,7 +27,11 @@
#include <languageclient/client.h>
+#include <QVersionNumber>
+
+namespace Core { class SearchResultItem; }
namespace ProjectExplorer { class Project; }
+namespace TextEditor { class TextDocument; }
namespace ClangCodeModel {
namespace Internal {
@@ -37,11 +41,26 @@ class ClangdClient : public LanguageClient::Client
Q_OBJECT
public:
ClangdClient(ProjectExplorer::Project *project, const Utils::FilePath &jsonDbDir);
+ ~ClangdClient() override;
+
+ bool isFullyIndexed() const;
+ QVersionNumber versionNumber() const;
+
+ void openExtraFile(const Utils::FilePath &filePath, const QString &content = {});
+ void closeExtraFile(const Utils::FilePath &filePath);
+
+ void findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor);
+
+ void enableTesting();
- bool isFullyIndexed() const { return m_isFullyIndexed; }
+signals:
+ void indexingFinished();
+ void foundReferences(const QList<Core::SearchResultItem> &items);
+ void findUsagesDone();
private:
- bool m_isFullyIndexed = false;
+ class Private;
+ Private * const d;
};
} // namespace Internal
diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
index 8215e0ba8a..e95bcb71d9 100644
--- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
+++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
@@ -64,7 +64,6 @@
#include <utils/runextensions.h>
#include <QApplication>
-#include <QFile>
#include <QMenu>
#include <QTextBlock>
#include <QTimer>
@@ -281,7 +280,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr
}
if (Client * const oldClient = clientForProject(project))
LanguageClientManager::shutdownClient(oldClient);
- Client * const client = createClient(project, jsonDbDir);
+ ClangdClient * const client = createClient(project, jsonDbDir);
connect(client, &Client::initialized, this, [client, project, projectInfo, jsonDbDir] {
using namespace ProjectExplorer;
if (!CppTools::codeModelSettings()->useClangd())
@@ -309,18 +308,8 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr
if (!cxxNode)
return;
- QFile cxxFile(cxxNode->filePath().toString());
- if (!cxxFile.open(QIODevice::ReadOnly))
- return;
- using namespace LanguageServerProtocol;
- TextDocumentItem item;
- item.setLanguageId("text/x-c++src");
- item.setUri(DocumentUri::fromFilePath(cxxNode->filePath()));
- item.setText(QString::fromUtf8(cxxFile.readAll()));
- item.setVersion(0);
- client->sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
- client->sendContent(DidCloseTextDocumentNotification(DidCloseTextDocumentParams(
- TextDocumentIdentifier{item.uri()})));
+ client->openExtraFile(cxxNode->filePath());
+ client->closeExtraFile(cxxNode->filePath());
});
});
@@ -347,7 +336,9 @@ ClangdClient *ClangModelManagerSupport::clientForProject(
ClangdClient *ClangModelManagerSupport::createClient(ProjectExplorer::Project *project,
const Utils::FilePath &jsonDbDir)
{
- return new ClangdClient(project, jsonDbDir);
+ const auto client = new ClangdClient(project, jsonDbDir);
+ emit createdClient(client);
+ return client;
}
void ClangModelManagerSupport::onEditorOpened(Core::IEditor *editor)
diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.h b/src/plugins/clangcodemodel/clangmodelmanagersupport.h
index 16f84e7163..21da8e1aea 100644
--- a/src/plugins/clangcodemodel/clangmodelmanagersupport.h
+++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.h
@@ -83,6 +83,9 @@ public:
static ClangModelManagerSupport *instance();
+signals:
+ void createdClient(ClangdClient *client);
+
private:
void onEditorOpened(Core::IEditor *editor);
void onEditorClosed(const QList<Core::IEditor *> &editors);
diff --git a/src/plugins/clangcodemodel/clangrefactoringengine.cpp b/src/plugins/clangcodemodel/clangrefactoringengine.cpp
index 0328bc89ba..dc0e06899f 100644
--- a/src/plugins/clangcodemodel/clangrefactoringengine.cpp
+++ b/src/plugins/clangcodemodel/clangrefactoringengine.cpp
@@ -97,11 +97,9 @@ void RefactoringEngine::findUsages(const CppTools::CursorInEditor &cursor,
->findUsages(cursor, std::move(callback));
return;
}
- // TODO: We want to keep our "access type info" feature.
- // Check whether we can support it using clang 12's textDocument/ast request
if (!client->documentOpen(cursor.textDocument()))
client->openDocument(cursor.textDocument()); // TODO: Just a workaround
- client->symbolSupport().findUsages(cursor.textDocument(), cursor.cursor());
+ client->findUsages(cursor.textDocument(), cursor.cursor());
}
} // namespace Internal
diff --git a/src/plugins/clangcodemodel/test/clangautomationutils.cpp b/src/plugins/clangcodemodel/test/clangautomationutils.cpp
index 269b29acd4..45030f7fba 100644
--- a/src/plugins/clangcodemodel/test/clangautomationutils.cpp
+++ b/src/plugins/clangcodemodel/test/clangautomationutils.cpp
@@ -138,5 +138,10 @@ TextEditor::ProposalModelPtr completionResults(TextEditor::BaseTextEditor *textE
return waitForCompletions.proposalModel;
}
+QString qrcPath(const QByteArray &relativeFilePath)
+{
+ return QLatin1String(":/unittests/ClangCodeModel/") + QString::fromUtf8(relativeFilePath);
+}
+
} // namespace Internal
} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/test/clangautomationutils.h b/src/plugins/clangcodemodel/test/clangautomationutils.h
index 4e16db9f2d..dcb62eda76 100644
--- a/src/plugins/clangcodemodel/test/clangautomationutils.h
+++ b/src/plugins/clangcodemodel/test/clangautomationutils.h
@@ -39,5 +39,7 @@ TextEditor::ProposalModelPtr completionResults(TextEditor::BaseTextEditor *textE
const QStringList &includePaths = QStringList(),
int timeOutInMs = 10000);
+QString qrcPath(const QByteArray &relativeFilePath);
+
} // namespace Internal
} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
index c4dd8e882e..a0d8aef463 100644
--- a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
+++ b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
@@ -58,9 +58,6 @@ using namespace ClangCodeModel::Internal;
namespace {
-QString qrcPath(const QByteArray relativeFilePath)
-{ return QLatin1String(":/unittests/ClangCodeModel/") + QString::fromUtf8(relativeFilePath); }
-
CppTools::Tests::TemporaryDir *globalTemporaryDir()
{
static CppTools::Tests::TemporaryDir dir;
diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp
new file mode 100644
index 0000000000..0145f79cdb
--- /dev/null
+++ b/src/plugins/clangcodemodel/test/clangdtests.cpp
@@ -0,0 +1,331 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "clangdtests.h"
+
+#include "clangautomationutils.h"
+#include "clangbatchfileprocessor.h"
+#include "../clangdclient.h"
+#include "../clangmodelmanagersupport.h"
+
+#include <cplusplus/FindUsages.h>
+#include <cpptools/cppcodemodelsettings.h>
+#include <cpptools/cpptoolsreuse.h>
+#include <cpptools/cpptoolstestcase.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/find/searchresultitem.h>
+#include <projectexplorer/kitmanager.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectexplorer.h>
+#include <qtsupport/qtkitinformation.h>
+#include <utils/algorithm.h>
+
+#include <QEventLoop>
+#include <QScopedPointer>
+#include <QTimer>
+#include <QtTest>
+
+using namespace CPlusPlus;
+using namespace Core;
+using namespace ProjectExplorer;
+
+namespace ClangCodeModel {
+namespace Internal {
+namespace Tests {
+
+void ClangdTests::initTestCase()
+{
+ const auto settings = CppTools::codeModelSettings();
+ const QString clangdFromEnv = qEnvironmentVariable("QTC_CLANGD");
+ if (!clangdFromEnv.isEmpty())
+ settings->setClangdFilePath(Utils::FilePath::fromString(clangdFromEnv));
+ const auto clangd = settings->clangdFilePath();
+ if (clangd.isEmpty() || !clangd.exists())
+ QSKIP("clangd binary not found");
+ settings->setUseClangd(true);
+}
+
+template <typename Signal> static bool waitForSignalOrTimeout(
+ const typename QtPrivate::FunctionPointer<Signal>::Object *sender, Signal signal)
+{
+ QTimer timer;
+ timer.setSingleShot(true);
+ timer.setInterval(timeOutInMs());
+ QEventLoop loop;
+ QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
+ QObject::connect(sender, signal, &loop, &QEventLoop::quit);
+ timer.start();
+ loop.exec();
+ return timer.isActive();
+}
+
+// The main point here is to test our access type categorization.
+// We do not try to stress-test clangd's "Find References" functionality; such tests belong
+// into LLVM.
+void ClangdTests::testFindReferences()
+{
+ // Find suitable kit.
+ const QList<Kit *> allKits = KitManager::kits();
+ if (allKits.isEmpty())
+ QSKIP("This test requires at least one kit to be present");
+ Kit * const kit = Utils::findOr(allKits, nullptr, [](const Kit *k) {
+ return k->isValid() && QtSupport::QtKitAspect::qtVersion(k);
+ });
+ if (!kit)
+ QSKIP("The test requires at least one valid kit with a valid Qt");
+
+ // Copy project out of qrc file, open it, and set up target.
+ CppTools::Tests::TemporaryCopiedDir testDir(qrcPath("find-usages"));
+ QVERIFY(testDir.isValid());
+ const auto openProjectResult = ProjectExplorerPlugin::openProject(
+ testDir.absolutePath("find-usages.pro"));
+ QVERIFY2(openProjectResult, qPrintable(openProjectResult.errorMessage()));
+ openProjectResult.project()->configureAsExampleProject(kit);
+
+ // Setting up the project should result in a clangd client being created.
+ // Wait until that has happened.
+ const auto modelManagerSupport = ClangModelManagerSupport::instance();
+ ClangdClient *client = modelManagerSupport->clientForProject(openProjectResult.project());
+ if (!client) {
+ QVERIFY(waitForSignalOrTimeout(modelManagerSupport,
+ &ClangModelManagerSupport::createdClient));
+ client = modelManagerSupport->clientForProject(openProjectResult.project());
+ }
+ QVERIFY(client);
+ client->enableTesting();
+
+ // Wait until the client is fully initialized, i.e. it's completed the handshake
+ // with the server.
+ if (!client->reachable())
+ QVERIFY(waitForSignalOrTimeout(client, &ClangdClient::initialized));
+ QVERIFY(client->reachable());
+
+ // The kind of AST support we need was introduced in LLVM 13.
+ if (client->versionNumber() < QVersionNumber(13))
+ QSKIP("Find Usages test needs clang >= 13");
+
+ // Wait for index to build.
+ if (!client->isFullyIndexed())
+ QVERIFY(waitForSignalOrTimeout(client, &ClangdClient::indexingFinished));
+ QVERIFY(client->isFullyIndexed());
+
+ // Open cpp documents.
+ struct EditorCloser {
+ static void cleanup(IEditor *editor) { EditorManager::closeEditors({editor}); }
+ };
+ const auto headerPath = Utils::FilePath::fromString(testDir.absolutePath("defs.h"));
+ QVERIFY2(headerPath.exists(), qPrintable(headerPath.toUserOutput()));
+ QScopedPointer<IEditor, EditorCloser> headerEditor(
+ EditorManager::openEditor(headerPath.toString()));
+ QVERIFY(headerEditor);
+ const auto headerDoc = qobject_cast<TextEditor::TextDocument *>(headerEditor->document());
+ QVERIFY(headerDoc);
+ QVERIFY(client->documentForFilePath(headerPath) == headerDoc);
+ const auto cppFilePath = Utils::FilePath::fromString(testDir.absolutePath("main.cpp"));
+ QVERIFY2(cppFilePath.exists(), qPrintable(cppFilePath.toUserOutput()));
+ QScopedPointer<IEditor, EditorCloser> cppFileEditor(
+ EditorManager::openEditor(cppFilePath.toString()));
+ QVERIFY(cppFileEditor);
+ const auto cppDoc = qobject_cast<TextEditor::TextDocument *>(cppFileEditor->document());
+ QVERIFY(cppDoc);
+ QVERIFY(client->documentForFilePath(cppFilePath) == cppDoc);
+
+ // ... and we're ready to go.
+ QList<SearchResultItem> searchResults;
+ connect(client, &ClangdClient::foundReferences, this,
+ [&searchResults](const QList<SearchResultItem> &results) {
+ if (results.isEmpty())
+ return;
+ if (results.first().path().first().endsWith("defs.h"))
+ searchResults = results + searchResults; // Guarantee expected file order.
+ else
+ searchResults += results;
+ });
+
+#define FIND_USAGES(doc, pos) do { \
+ QTextCursor cursor((doc)->document()); \
+ cursor.setPosition((pos)); \
+ searchResults.clear(); \
+ client->findUsages((doc), cursor); \
+ QVERIFY(waitForSignalOrTimeout(client, &ClangdClient::findUsagesDone)); \
+} while (false)
+
+#define EXPECT_RESULT(index, lne, col, type) \
+ QCOMPARE(searchResults.at(index).mainRange().begin.line, lne); \
+ QCOMPARE(searchResults.at(index).mainRange().begin.column, col); \
+ QCOMPARE(searchResults.at(index).userData().toInt(), int(type))
+
+ // All kinds of checks involving a struct member.
+ FIND_USAGES(headerDoc, 55);
+ QCOMPARE(searchResults.size(), 32);
+ EXPECT_RESULT(0, 2, 17, Usage::Type::Read);
+ EXPECT_RESULT(1, 3, 15, Usage::Type::Declaration);
+ EXPECT_RESULT(2, 6, 17, Usage::Type::WritableRef);
+ EXPECT_RESULT(3, 8, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(4, 9, 13, Usage::Type::WritableRef);
+ EXPECT_RESULT(5, 10, 12, Usage::Type::WritableRef);
+ EXPECT_RESULT(6, 11, 13, Usage::Type::WritableRef);
+ EXPECT_RESULT(7, 12, 14, Usage::Type::WritableRef);
+ EXPECT_RESULT(8, 13, 26, Usage::Type::WritableRef);
+ EXPECT_RESULT(9, 14, 23, Usage::Type::Read);
+ EXPECT_RESULT(10, 15, 14, Usage::Type::Read);
+ EXPECT_RESULT(11, 16, 24, Usage::Type::WritableRef);
+ EXPECT_RESULT(12, 17, 15, Usage::Type::WritableRef);
+ EXPECT_RESULT(13, 18, 22, Usage::Type::Read);
+ EXPECT_RESULT(14, 19, 12, Usage::Type::WritableRef);
+ EXPECT_RESULT(15, 20, 12, Usage::Type::Read);
+ EXPECT_RESULT(16, 21, 13, Usage::Type::WritableRef);
+ EXPECT_RESULT(17, 22, 13, Usage::Type::Read);
+ EXPECT_RESULT(18, 23, 12, Usage::Type::Read);
+ EXPECT_RESULT(19, 42, 20, Usage::Type::Read);
+ EXPECT_RESULT(20, 44, 15, Usage::Type::Read);
+ EXPECT_RESULT(21, 47, 15, Usage::Type::Write);
+ EXPECT_RESULT(22, 50, 11, Usage::Type::Read);
+ EXPECT_RESULT(23, 51, 11, Usage::Type::Write);
+ EXPECT_RESULT(24, 52, 9, Usage::Type::Write);
+ EXPECT_RESULT(25, 53, 7, Usage::Type::Write);
+ EXPECT_RESULT(26, 56, 7, Usage::Type::Write);
+ EXPECT_RESULT(27, 56, 25, Usage::Type::Other);
+ EXPECT_RESULT(28, 58, 13, Usage::Type::Read);
+ EXPECT_RESULT(29, 58, 25, Usage::Type::Read);
+ EXPECT_RESULT(30, 59, 7, Usage::Type::Write);
+ EXPECT_RESULT(31, 59, 24, Usage::Type::Read);
+
+ // Detect constructor member initialization as a write access.
+ FIND_USAGES(headerDoc, 68);
+ QCOMPARE(searchResults.size(), 2);
+ EXPECT_RESULT(0, 2, 10, Usage::Type::Write);
+ EXPECT_RESULT(1, 4, 8, Usage::Type::Declaration);
+
+ // Detect direct member initialization.
+ FIND_USAGES(headerDoc, 101);
+ QCOMPARE(searchResults.size(), 2);
+ EXPECT_RESULT(0, 5, 21, Usage::Type::Initialization);
+ EXPECT_RESULT(1, 45, 16, Usage::Type::Read);
+
+ // Make sure that pure virtual declaration is not mistaken for an assignment.
+ FIND_USAGES(headerDoc, 420);
+ QCOMPARE(searchResults.size(), 3); // FIXME: The override gets reported twice. clang bug?
+ EXPECT_RESULT(0, 17, 17, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 21, 9, Usage::Type::Declaration);
+ EXPECT_RESULT(2, 21, 9, Usage::Type::Declaration);
+
+ // References to pointer variable.
+ FIND_USAGES(cppDoc, 52);
+ QCOMPARE(searchResults.size(), 11);
+ EXPECT_RESULT(0, 6, 10, Usage::Type::Initialization);
+ EXPECT_RESULT(1, 8, 4, Usage::Type::Write);
+ EXPECT_RESULT(2, 10, 4, Usage::Type::Write);
+ EXPECT_RESULT(3, 24, 5, Usage::Type::Write);
+ EXPECT_RESULT(4, 25, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(5, 26, 11, Usage::Type::Read);
+ EXPECT_RESULT(6, 27, 10, Usage::Type::WritableRef);
+ EXPECT_RESULT(7, 28, 10, Usage::Type::Read);
+ EXPECT_RESULT(8, 29, 11, Usage::Type::Read);
+ EXPECT_RESULT(9, 30, 15, Usage::Type::WritableRef);
+ EXPECT_RESULT(10, 31, 22, Usage::Type::Read);
+
+ // References to struct variable, directly and via members.
+ FIND_USAGES(cppDoc, 39);
+ QCOMPARE(searchResults.size(), 34);
+ EXPECT_RESULT(0, 5, 7, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 6, 15, Usage::Type::WritableRef);
+ EXPECT_RESULT(2, 8, 9, Usage::Type::WritableRef);
+ EXPECT_RESULT(3, 9, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(4, 11, 4, Usage::Type::Write);
+ EXPECT_RESULT(5, 11, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(6, 12, 12, Usage::Type::WritableRef);
+ EXPECT_RESULT(7, 13, 6, Usage::Type::Write);
+ EXPECT_RESULT(8, 14, 21, Usage::Type::Read);
+ EXPECT_RESULT(9, 15, 4, Usage::Type::Write);
+ EXPECT_RESULT(10, 15, 12, Usage::Type::Read);
+ EXPECT_RESULT(11, 16, 22, Usage::Type::WritableRef);
+ EXPECT_RESULT(12, 17, 13, Usage::Type::WritableRef);
+ EXPECT_RESULT(13, 18, 20, Usage::Type::Read);
+ EXPECT_RESULT(14, 19, 10, Usage::Type::WritableRef);
+ EXPECT_RESULT(15, 20, 10, Usage::Type::Read);
+ EXPECT_RESULT(16, 21, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(17, 22, 11, Usage::Type::Read);
+ EXPECT_RESULT(18, 23, 10, Usage::Type::Read);
+ EXPECT_RESULT(19, 32, 4, Usage::Type::Write);
+ EXPECT_RESULT(20, 33, 23, Usage::Type::WritableRef);
+ EXPECT_RESULT(21, 34, 23, Usage::Type::Read);
+ EXPECT_RESULT(22, 35, 15, Usage::Type::WritableRef);
+ EXPECT_RESULT(23, 36, 22, Usage::Type::WritableRef);
+ EXPECT_RESULT(24, 37, 4, Usage::Type::Read);
+ EXPECT_RESULT(25, 38, 4, Usage::Type::WritableRef);
+ EXPECT_RESULT(26, 39, 6, Usage::Type::WritableRef);
+ EXPECT_RESULT(27, 40, 4, Usage::Type::Read);
+ EXPECT_RESULT(28, 41, 4, Usage::Type::WritableRef);
+ EXPECT_RESULT(29, 42, 4, Usage::Type::Read);
+ EXPECT_RESULT(30, 42, 18, Usage::Type::Read);
+ EXPECT_RESULT(31, 43, 11, Usage::Type::Write);
+ EXPECT_RESULT(32, 54, 4, Usage::Type::Other);
+ EXPECT_RESULT(33, 55, 4, Usage::Type::Other);
+
+ // References to struct type.
+ FIND_USAGES(headerDoc, 7);
+ QCOMPARE(searchResults.size(), 18);
+ EXPECT_RESULT(0, 1, 7, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 2, 4, Usage::Type::Declaration);
+ EXPECT_RESULT(2, 20, 19, Usage::Type::Other);
+
+ // 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.
+ EXPECT_RESULT(3, 10, 9, Usage::Type::WritableRef);
+ EXPECT_RESULT(4, 12, 4, Usage::Type::Write);
+ EXPECT_RESULT(5, 44, 12, Usage::Type::Read);
+ EXPECT_RESULT(6, 45, 13, Usage::Type::Read);
+ EXPECT_RESULT(7, 47, 12, Usage::Type::Write);
+ EXPECT_RESULT(8, 50, 8, Usage::Type::Read);
+ EXPECT_RESULT(9, 51, 8, Usage::Type::Write);
+ EXPECT_RESULT(10, 52, 6, Usage::Type::Write);
+ EXPECT_RESULT(11, 53, 4, Usage::Type::Write);
+ EXPECT_RESULT(12, 56, 4, Usage::Type::Write);
+ EXPECT_RESULT(13, 56, 22, Usage::Type::Other);
+ EXPECT_RESULT(14, 58, 10, Usage::Type::Read);
+ EXPECT_RESULT(15, 58, 22, Usage::Type::Read);
+ EXPECT_RESULT(16, 59, 4, Usage::Type::Write);
+ EXPECT_RESULT(17, 59, 21, Usage::Type::Read);
+
+ // References to subclass type.
+ FIND_USAGES(headerDoc, 450);
+ QCOMPARE(searchResults.size(), 4);
+ EXPECT_RESULT(0, 20, 7, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 5, 4, Usage::Type::Other);
+ EXPECT_RESULT(2, 13, 21, Usage::Type::Other);
+ EXPECT_RESULT(3, 32, 8, Usage::Type::Other);
+
+ // References to array variables.
+ FIND_USAGES(cppDoc, 1134);
+ QCOMPARE(searchResults.size(), 3);
+ EXPECT_RESULT(0, 57, 8, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 58, 4, Usage::Type::Write);
+ EXPECT_RESULT(2, 59, 15, Usage::Type::Read);
+}
+
+} // namespace Tests
+} // namespace Internal
+} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/test/clangdtests.h b/src/plugins/clangcodemodel/test/clangdtests.h
new file mode 100644
index 0000000000..ddf2c1b1d2
--- /dev/null
+++ b/src/plugins/clangcodemodel/test/clangdtests.h
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QObject>
+
+namespace ClangCodeModel {
+namespace Internal {
+namespace Tests {
+
+class ClangdTests : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+
+ void testFindReferences();
+};
+
+} // namespace Tests
+} // namespace Internal
+} // namespace ClangCodeModel
+
diff --git a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc
index d1013a7d29..073e9fa309 100644
--- a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc
+++ b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc
@@ -29,5 +29,8 @@
<file>membercompletion-friend.cpp</file>
<file>functionCompletionFiltered.cpp</file>
<file>functionCompletionFiltered2.cpp</file>
+ <file>find-usages/defs.h</file>
+ <file>find-usages/main.cpp</file>
+ <file>find-usages/find-usages.pro</file>
</qresource>
</RCC>
diff --git a/src/plugins/clangcodemodel/test/data/find-usages/defs.h b/src/plugins/clangcodemodel/test/data/find-usages/defs.h
new file mode 100644
index 0000000000..8480e6f6e5
--- /dev/null
+++ b/src/plugins/clangcodemodel/test/data/find-usages/defs.h
@@ -0,0 +1,28 @@
+struct S {
+ S() : value2(value) {}
+ static int value;
+ int value2 : 2;
+ static const int value3 = 0;
+ static void *p;
+ static const void *p2;
+ struct Nested {
+ int constFunc() const;
+ void constFunc(int) const;
+ void nonConstFunc();
+ } n;
+ Nested constFunc() const;
+ void nonConstFunc();
+ static void staticFunc1() {}
+ static void staticFunc2();
+ virtual void pureVirtual() = 0;
+};
+
+struct S2 : public S {
+ void pureVirtual() override {}
+};
+
+void func1(int &);
+void func2(const int &);
+void func3(int *);
+void func4(const int *);
+void func5(int);
diff --git a/src/plugins/clangcodemodel/test/data/find-usages/find-usages.pro b/src/plugins/clangcodemodel/test/data/find-usages/find-usages.pro
new file mode 100644
index 0000000000..c447485530
--- /dev/null
+++ b/src/plugins/clangcodemodel/test/data/find-usages/find-usages.pro
@@ -0,0 +1,4 @@
+TEMPLATE = app
+QT = core
+HEADERS = defs.h
+SOURCES = main.cpp
diff --git a/src/plugins/clangcodemodel/test/data/find-usages/main.cpp b/src/plugins/clangcodemodel/test/data/find-usages/main.cpp
new file mode 100644
index 0000000000..36941dc473
--- /dev/null
+++ b/src/plugins/clangcodemodel/test/data/find-usages/main.cpp
@@ -0,0 +1,60 @@
+#include "defs.h"
+
+int main()
+{
+ S2 s;
+ auto *p = &s.value;
+ int **pp;
+ p = &s.value;
+ *pp = &s.value;
+ p = &S::value;
+ s.p = &s.value;
+ S::p = &s.value;
+ (&s)->p = &((new S2)->value);
+ const int *p2 = &s.value;
+ s.p2 = &s.value;
+ int * const p3 = &s.value;
+ int &r = s.value;
+ const int &cr = s.value;
+ func1(s.value);
+ func2(s.value);
+ func3(&s.value);
+ func4(&s.value);
+ func5(s.value);
+ *p = 5;
+ func1(*p);
+ func2(*p);
+ func3(p);
+ func4(p);
+ func5(*p);
+ int &r2 = *p;
+ const int &cr2 = *p;
+ s = S2();
+ auto * const ps = &s;
+ const auto *ps2 = &s;
+ auto &pr = s;
+ const auto pr2 = &s;
+ s.constFunc().nonConstFunc();
+ s.nonConstFunc();
+ (&s)->nonConstFunc();
+ s.n.constFunc();
+ s.n.nonConstFunc();
+ s.n.constFunc(s.value);
+ delete s.p;
+ switch (S::value) {
+ case S::value3: break;
+ }
+ switch (S::value = 5) {
+ default: break;
+ }
+ if (S::value) {}
+ if (S::value = 0) {}
+ ++S::value;
+ S::value--;
+ s.staticFunc1();
+ s.staticFunc2();
+ S::value = sizeof S::value;
+ int array[3];
+ array[S::value] = S::value;
+ S::value = array[S::value];
+}