forked from qt-creator/qt-creator
ClangCodeModel: Support access type categorization
... with "Find Usages", as we do in the built-in code model. Note 1: This is very slow, so it's for now only enabled if the search results come from a small number of files. Possible ways of speeding up the operation to be investigated. Note 2: All test cases from the old code model also pass here, but checking with non-trivial real-world projects shows a lot of mis-categorizations. Well will fix them one by one. Note 3: This functionality requires clangd >= 13. Change-Id: Ib3500b52996dbbf9d7d9712d729179bcbd3262fc Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
add_qtc_plugin(ClangCodeModel
|
add_qtc_plugin(ClangCodeModel
|
||||||
CONDITION TARGET libclang
|
CONDITION TARGET libclang
|
||||||
DEPENDS ClangSupport CPlusPlus
|
DEPENDS ClangSupport CPlusPlus
|
||||||
PLUGIN_DEPENDS Core CppTools LanguageClient TextEditor
|
PLUGIN_DEPENDS Core CppTools LanguageClient QtSupport TextEditor
|
||||||
PLUGIN_TEST_DEPENDS CppEditor QmakeProjectManager
|
PLUGIN_TEST_DEPENDS CppEditor QmakeProjectManager
|
||||||
SOURCES
|
SOURCES
|
||||||
clangactivationsequencecontextprocessor.cpp clangactivationsequencecontextprocessor.h
|
clangactivationsequencecontextprocessor.cpp clangactivationsequencecontextprocessor.h
|
||||||
@@ -52,5 +52,6 @@ extend_qtc_plugin(ClangCodeModel
|
|||||||
test/clangautomationutils.cpp test/clangautomationutils.h
|
test/clangautomationutils.cpp test/clangautomationutils.h
|
||||||
test/clangbatchfileprocessor.cpp test/clangbatchfileprocessor.h
|
test/clangbatchfileprocessor.cpp test/clangbatchfileprocessor.h
|
||||||
test/clangcodecompletion_test.cpp test/clangcodecompletion_test.h
|
test/clangcodecompletion_test.cpp test/clangcodecompletion_test.h
|
||||||
|
test/clangdtests.cpp test/clangdtests.h
|
||||||
test/data/clangtestdata.qrc
|
test/data/clangtestdata.qrc
|
||||||
)
|
)
|
||||||
|
@@ -96,11 +96,13 @@ equals(TEST, 1) {
|
|||||||
test/clangautomationutils.h \
|
test/clangautomationutils.h \
|
||||||
test/clangbatchfileprocessor.h \
|
test/clangbatchfileprocessor.h \
|
||||||
test/clangcodecompletion_test.h \
|
test/clangcodecompletion_test.h \
|
||||||
|
test/clangdtests.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
test/clangautomationutils.cpp \
|
test/clangautomationutils.cpp \
|
||||||
test/clangbatchfileprocessor.cpp \
|
test/clangbatchfileprocessor.cpp \
|
||||||
test/clangcodecompletion_test.cpp \
|
test/clangcodecompletion_test.cpp \
|
||||||
|
test/clangdtests.cpp
|
||||||
|
|
||||||
RESOURCES += test/data/clangtestdata.qrc
|
RESOURCES += test/data/clangtestdata.qrc
|
||||||
OTHER_FILES += $$files(test/data/*)
|
OTHER_FILES += $$files(test/data/*)
|
||||||
|
@@ -8,6 +8,7 @@ QtcPlugin {
|
|||||||
Depends { name: "Core" }
|
Depends { name: "Core" }
|
||||||
Depends { name: "CppTools" }
|
Depends { name: "CppTools" }
|
||||||
Depends { name: "ProjectExplorer" }
|
Depends { name: "ProjectExplorer" }
|
||||||
|
Depends { name: "QtSupport"; condition: qtc.testsEnabled }
|
||||||
Depends { name: "TextEditor" }
|
Depends { name: "TextEditor" }
|
||||||
Depends { name: "Utils" }
|
Depends { name: "Utils" }
|
||||||
Depends { name: "ClangSupport" }
|
Depends { name: "ClangSupport" }
|
||||||
@@ -114,6 +115,8 @@ QtcPlugin {
|
|||||||
"clangbatchfileprocessor.h",
|
"clangbatchfileprocessor.h",
|
||||||
"clangcodecompletion_test.cpp",
|
"clangcodecompletion_test.cpp",
|
||||||
"clangcodecompletion_test.h",
|
"clangcodecompletion_test.h",
|
||||||
|
"clangdtests.cpp",
|
||||||
|
"clangdtests.h",
|
||||||
"data/clangtestdata.qrc",
|
"data/clangtestdata.qrc",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -10,3 +10,5 @@ QTC_PLUGIN_DEPENDS += \
|
|||||||
QTC_TEST_DEPENDS += \
|
QTC_TEST_DEPENDS += \
|
||||||
cppeditor \
|
cppeditor \
|
||||||
qmakeprojectmanager
|
qmakeprojectmanager
|
||||||
|
|
||||||
|
equals(TEST, 1): QTC_PLUGIN_DEPENDS += qtsupport
|
||||||
|
@@ -32,6 +32,7 @@
|
|||||||
#ifdef WITH_TESTS
|
#ifdef WITH_TESTS
|
||||||
# include "test/clangbatchfileprocessor.h"
|
# include "test/clangbatchfileprocessor.h"
|
||||||
# include "test/clangcodecompletion_test.h"
|
# include "test/clangcodecompletion_test.h"
|
||||||
|
# include "test/clangdtests.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <coreplugin/actionmanager/actioncontainer.h>
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
||||||
@@ -204,6 +205,7 @@ QVector<QObject *> ClangCodeModelPlugin::createTestObjects() const
|
|||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
new Tests::ClangCodeCompletionTest,
|
new Tests::ClangCodeCompletionTest,
|
||||||
|
new Tests::ClangdTests,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -25,10 +25,21 @@
|
|||||||
|
|
||||||
#include "clangdclient.h"
|
#include "clangdclient.h"
|
||||||
|
|
||||||
|
#include <coreplugin/find/searchresultitem.h>
|
||||||
|
#include <coreplugin/find/searchresultwindow.h>
|
||||||
|
#include <cplusplus/FindUsages.h>
|
||||||
#include <cpptools/cppcodemodelsettings.h>
|
#include <cpptools/cppcodemodelsettings.h>
|
||||||
|
#include <cpptools/cppfindreferences.h>
|
||||||
#include <cpptools/cpptoolsreuse.h>
|
#include <cpptools/cpptoolsreuse.h>
|
||||||
#include <languageclient/languageclientinterface.h>
|
#include <languageclient/languageclientinterface.h>
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
using namespace CPlusPlus;
|
||||||
|
using namespace Core;
|
||||||
using namespace LanguageClient;
|
using namespace LanguageClient;
|
||||||
using namespace LanguageServerProtocol;
|
using namespace LanguageServerProtocol;
|
||||||
|
|
||||||
@@ -36,13 +47,240 @@ namespace ClangCodeModel {
|
|||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
static Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg);
|
static Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg);
|
||||||
|
|
||||||
static QString indexingToken() { return "backgroundIndexProgress"; }
|
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 ¶ms) : Request("textDocument/ast", params) {}
|
||||||
|
};
|
||||||
|
|
||||||
static BaseClientInterface *clientInterface(const Utils::FilePath &jsonDbDir)
|
static BaseClientInterface *clientInterface(const Utils::FilePath &jsonDbDir)
|
||||||
{
|
{
|
||||||
Utils::CommandLine cmd{CppTools::codeModelSettings()->clangdFilePath(),
|
Utils::CommandLine cmd{CppTools::codeModelSettings()->clangdFilePath(),
|
||||||
{"--index", "--background-index", "--limit-results=0"}};
|
{"--background-index", "--limit-results=0"}};
|
||||||
if (!jsonDbDir.isEmpty())
|
if (!jsonDbDir.isEmpty())
|
||||||
cmd.addArg("--compile-commands-dir=" + jsonDbDir.toString());
|
cmd.addArg("--compile-commands-dir=" + jsonDbDir.toString());
|
||||||
if (clangdLog().isDebugEnabled())
|
if (clangdLog().isDebugEnabled())
|
||||||
@@ -52,8 +290,44 @@ static BaseClientInterface *clientInterface(const Utils::FilePath &jsonDbDir)
|
|||||||
return interface;
|
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)
|
ClangdClient::ClangdClient(ProjectExplorer::Project *project, const Utils::FilePath &jsonDbDir)
|
||||||
: Client(clientInterface(jsonDbDir))
|
: Client(clientInterface(jsonDbDir)), d(new Private(this))
|
||||||
{
|
{
|
||||||
setName(tr("clangd"));
|
setName(tr("clangd"));
|
||||||
LanguageFilter langFilter;
|
LanguageFilter langFilter;
|
||||||
@@ -69,11 +343,252 @@ ClangdClient::ClangdClient(ProjectExplorer::Project *project, const Utils::FileP
|
|||||||
setCurrentProject(project);
|
setCurrentProject(project);
|
||||||
connect(this, &Client::workDone, this, [this](const ProgressToken &token) {
|
connect(this, &Client::workDone, this, [this](const ProgressToken &token) {
|
||||||
const QString * const val = Utils::get_if<QString>(&token);
|
const QString * const val = Utils::get_if<QString>(&token);
|
||||||
if (val && *val == indexingToken())
|
if (val && *val == indexingToken()) {
|
||||||
m_isFullyIndexed = true;
|
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();
|
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 Internal
|
||||||
} // namespace ClangCodeModel
|
} // namespace ClangCodeModel
|
||||||
|
@@ -27,7 +27,11 @@
|
|||||||
|
|
||||||
#include <languageclient/client.h>
|
#include <languageclient/client.h>
|
||||||
|
|
||||||
|
#include <QVersionNumber>
|
||||||
|
|
||||||
|
namespace Core { class SearchResultItem; }
|
||||||
namespace ProjectExplorer { class Project; }
|
namespace ProjectExplorer { class Project; }
|
||||||
|
namespace TextEditor { class TextDocument; }
|
||||||
|
|
||||||
namespace ClangCodeModel {
|
namespace ClangCodeModel {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
@@ -37,11 +41,26 @@ class ClangdClient : public LanguageClient::Client
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ClangdClient(ProjectExplorer::Project *project, const Utils::FilePath &jsonDbDir);
|
ClangdClient(ProjectExplorer::Project *project, const Utils::FilePath &jsonDbDir);
|
||||||
|
~ClangdClient() override;
|
||||||
|
|
||||||
bool isFullyIndexed() const { return m_isFullyIndexed; }
|
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();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void indexingFinished();
|
||||||
|
void foundReferences(const QList<Core::SearchResultItem> &items);
|
||||||
|
void findUsagesDone();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_isFullyIndexed = false;
|
class Private;
|
||||||
|
Private * const d;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
@@ -64,7 +64,6 @@
|
|||||||
#include <utils/runextensions.h>
|
#include <utils/runextensions.h>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QFile>
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QTextBlock>
|
#include <QTextBlock>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@@ -281,7 +280,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr
|
|||||||
}
|
}
|
||||||
if (Client * const oldClient = clientForProject(project))
|
if (Client * const oldClient = clientForProject(project))
|
||||||
LanguageClientManager::shutdownClient(oldClient);
|
LanguageClientManager::shutdownClient(oldClient);
|
||||||
Client * const client = createClient(project, jsonDbDir);
|
ClangdClient * const client = createClient(project, jsonDbDir);
|
||||||
connect(client, &Client::initialized, this, [client, project, projectInfo, jsonDbDir] {
|
connect(client, &Client::initialized, this, [client, project, projectInfo, jsonDbDir] {
|
||||||
using namespace ProjectExplorer;
|
using namespace ProjectExplorer;
|
||||||
if (!CppTools::codeModelSettings()->useClangd())
|
if (!CppTools::codeModelSettings()->useClangd())
|
||||||
@@ -309,18 +308,8 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr
|
|||||||
if (!cxxNode)
|
if (!cxxNode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QFile cxxFile(cxxNode->filePath().toString());
|
client->openExtraFile(cxxNode->filePath());
|
||||||
if (!cxxFile.open(QIODevice::ReadOnly))
|
client->closeExtraFile(cxxNode->filePath());
|
||||||
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()})));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -347,7 +336,9 @@ ClangdClient *ClangModelManagerSupport::clientForProject(
|
|||||||
ClangdClient *ClangModelManagerSupport::createClient(ProjectExplorer::Project *project,
|
ClangdClient *ClangModelManagerSupport::createClient(ProjectExplorer::Project *project,
|
||||||
const Utils::FilePath &jsonDbDir)
|
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)
|
void ClangModelManagerSupport::onEditorOpened(Core::IEditor *editor)
|
||||||
|
@@ -83,6 +83,9 @@ public:
|
|||||||
|
|
||||||
static ClangModelManagerSupport *instance();
|
static ClangModelManagerSupport *instance();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void createdClient(ClangdClient *client);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onEditorOpened(Core::IEditor *editor);
|
void onEditorOpened(Core::IEditor *editor);
|
||||||
void onEditorClosed(const QList<Core::IEditor *> &editors);
|
void onEditorClosed(const QList<Core::IEditor *> &editors);
|
||||||
|
@@ -97,11 +97,9 @@ void RefactoringEngine::findUsages(const CppTools::CursorInEditor &cursor,
|
|||||||
->findUsages(cursor, std::move(callback));
|
->findUsages(cursor, std::move(callback));
|
||||||
return;
|
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()))
|
if (!client->documentOpen(cursor.textDocument()))
|
||||||
client->openDocument(cursor.textDocument()); // TODO: Just a workaround
|
client->openDocument(cursor.textDocument()); // TODO: Just a workaround
|
||||||
client->symbolSupport().findUsages(cursor.textDocument(), cursor.cursor());
|
client->findUsages(cursor.textDocument(), cursor.cursor());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
@@ -138,5 +138,10 @@ TextEditor::ProposalModelPtr completionResults(TextEditor::BaseTextEditor *textE
|
|||||||
return waitForCompletions.proposalModel;
|
return waitForCompletions.proposalModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString qrcPath(const QByteArray &relativeFilePath)
|
||||||
|
{
|
||||||
|
return QLatin1String(":/unittests/ClangCodeModel/") + QString::fromUtf8(relativeFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace ClangCodeModel
|
} // namespace ClangCodeModel
|
||||||
|
@@ -39,5 +39,7 @@ TextEditor::ProposalModelPtr completionResults(TextEditor::BaseTextEditor *textE
|
|||||||
const QStringList &includePaths = QStringList(),
|
const QStringList &includePaths = QStringList(),
|
||||||
int timeOutInMs = 10000);
|
int timeOutInMs = 10000);
|
||||||
|
|
||||||
|
QString qrcPath(const QByteArray &relativeFilePath);
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace ClangCodeModel
|
} // namespace ClangCodeModel
|
||||||
|
@@ -58,9 +58,6 @@ using namespace ClangCodeModel::Internal;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
QString qrcPath(const QByteArray relativeFilePath)
|
|
||||||
{ return QLatin1String(":/unittests/ClangCodeModel/") + QString::fromUtf8(relativeFilePath); }
|
|
||||||
|
|
||||||
CppTools::Tests::TemporaryDir *globalTemporaryDir()
|
CppTools::Tests::TemporaryDir *globalTemporaryDir()
|
||||||
{
|
{
|
||||||
static CppTools::Tests::TemporaryDir dir;
|
static CppTools::Tests::TemporaryDir dir;
|
||||||
|
331
src/plugins/clangcodemodel/test/clangdtests.cpp
Normal file
331
src/plugins/clangcodemodel/test/clangdtests.cpp
Normal file
@@ -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
|
47
src/plugins/clangcodemodel/test/clangdtests.h
Normal file
47
src/plugins/clangcodemodel/test/clangdtests.h
Normal file
@@ -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
|
||||||
|
|
@@ -29,5 +29,8 @@
|
|||||||
<file>membercompletion-friend.cpp</file>
|
<file>membercompletion-friend.cpp</file>
|
||||||
<file>functionCompletionFiltered.cpp</file>
|
<file>functionCompletionFiltered.cpp</file>
|
||||||
<file>functionCompletionFiltered2.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>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
28
src/plugins/clangcodemodel/test/data/find-usages/defs.h
Normal file
28
src/plugins/clangcodemodel/test/data/find-usages/defs.h
Normal file
@@ -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);
|
@@ -0,0 +1,4 @@
|
|||||||
|
TEMPLATE = app
|
||||||
|
QT = core
|
||||||
|
HEADERS = defs.h
|
||||||
|
SOURCES = main.cpp
|
60
src/plugins/clangcodemodel/test/data/find-usages/main.cpp
Normal file
60
src/plugins/clangcodemodel/test/data/find-usages/main.cpp
Normal file
@@ -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];
|
||||||
|
}
|
Reference in New Issue
Block a user