diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp index 126b3e9b5b6..89949ab688a 100644 --- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp +++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp @@ -205,7 +205,8 @@ QVector ClangCodeModelPlugin::createTestObjects() const { return { new Tests::ClangCodeCompletionTest, - new Tests::ClangdTests, + new Tests::ClangdTestFindReferences, + new Tests::ClangdTestFollowSymbol, }; } #endif diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index d1ec86393e4..6e42f4375ef 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -897,7 +897,6 @@ void ClangdClient::followSymbol( void ClangdClient::Private::handleGotoDefinitionResult() { QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return); - QTC_ASSERT(followSymbolData->cursorNode.isValid(), return); qCDebug(clangdLog) << "handling go to definition result"; diff --git a/src/plugins/clangcodemodel/clangutils.h b/src/plugins/clangcodemodel/clangutils.h index 7e59eb886e5..22ae56b4b58 100644 --- a/src/plugins/clangcodemodel/clangutils.h +++ b/src/plugins/clangcodemodel/clangutils.h @@ -78,7 +78,7 @@ public: QString error; }; -enum CompilationDbPurpose { Project, CodeModel }; +enum class CompilationDbPurpose { Project, CodeModel }; GenerateCompilationDbResult generateCompilationDB(CppTools::ProjectInfo projectInfo, CompilationDbPurpose purpose); diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index d0b0cf97bcd..127e7a1e943 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -43,6 +42,7 @@ #include #include +#include #include #include #include @@ -56,7 +56,19 @@ namespace ClangCodeModel { namespace Internal { namespace Tests { -void ClangdTests::initTestCase() +ClangdTest::~ClangdTest() +{ + if (m_project) + ProjectExplorerPlugin::unloadProject(m_project); + delete m_projectDir; +} + +Utils::FilePath ClangdTest::filePath(const QString &fileName) const +{ + return Utils::FilePath::fromString(m_projectDir->absolutePath(fileName.toLocal8Bit())); +} + +void ClangdTest::initTestCase() { const auto settings = CppTools::codeModelSettings(); const QString clangdFromEnv = qEnvironmentVariable("QTC_CLANGD"); @@ -66,249 +78,284 @@ void ClangdTests::initTestCase() if (clangd.isEmpty() || !clangd.exists()) QSKIP("clangd binary not found"); settings->setUseClangd(true); + + // Find suitable kit. + m_kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) { + return k->isValid() && QtSupport::QtKitAspect::qtVersion(k); + }); + if (!m_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. + m_projectDir = new TemporaryCopiedDir(qrcPath(QFileInfo(m_projectFileName) + .baseName().toLocal8Bit())); + QVERIFY(m_projectDir->isValid()); + const auto openProjectResult = ProjectExplorerPlugin::openProject( + m_projectDir->absolutePath(m_projectFileName.toUtf8())); + QVERIFY2(openProjectResult, qPrintable(openProjectResult.errorMessage())); + m_project = openProjectResult.project(); + m_project->configureAsExampleProject(m_kit); + + // Setting up the project should result in a clangd client being created. + // Wait until that has happened. + const auto modelManagerSupport = ClangModelManagerSupport::instance(); + m_client = modelManagerSupport->clientForProject(openProjectResult.project()); + if (!m_client) { + QVERIFY(waitForSignalOrTimeout(modelManagerSupport, + &ClangModelManagerSupport::createdClient, timeOutInMs())); + m_client = modelManagerSupport->clientForProject(m_project); + } + QVERIFY(m_client); + m_client->enableTesting(); + + // Wait until the client is fully initialized, i.e. it's completed the handshake + // with the server. + if (!m_client->reachable()) + QVERIFY(waitForSignalOrTimeout(m_client, &ClangdClient::initialized, timeOutInMs())); + QVERIFY(m_client->reachable()); + + // The kind of AST support we need was introduced in LLVM 13. + if (m_minVersion != -1 && m_client->versionNumber() < QVersionNumber(m_minVersion)) + QSKIP("clangd is too old"); + + // Wait for index to build. + if (!m_client->isFullyIndexed()) + QVERIFY(waitForSignalOrTimeout(m_client, &ClangdClient::indexingFinished, timeOutInMs())); + QVERIFY(m_client->isFullyIndexed()); + + // Open cpp documents. + for (const QString &sourceFileName : qAsConst(m_sourceFileNames)) { + const auto sourceFilePath = Utils::FilePath::fromString( + m_projectDir->absolutePath(sourceFileName.toLocal8Bit())); + QVERIFY2(sourceFilePath.exists(), qPrintable(sourceFilePath.toUserOutput())); + IEditor * const editor = EditorManager::openEditor(sourceFilePath); + QVERIFY(editor); + const auto doc = qobject_cast(editor->document()); + QVERIFY(doc); + QVERIFY(m_client->documentForFilePath(sourceFilePath) == doc); + m_sourceDocuments.insert(sourceFileName, doc); + } +} + +ClangdTestFindReferences::ClangdTestFindReferences() +{ + setProjectFileName("find-usages.pro"); + setSourceFileNames({"defs.h", "main.cpp"}); + setMinimumVersion(13); +} + +void ClangdTestFindReferences::initTestCase() +{ + ClangdTest::initTestCase(); + connect(client(), &ClangdClient::foundReferences, this, + [this](const QList &results) { + if (results.isEmpty()) + return; + if (results.first().path().first().endsWith("defs.h")) + m_actualResults = results + m_actualResults; // Guarantee expected file order. + else + m_actualResults += results; + }); +} + +void ClangdTestFindReferences::test_data() +{ + QTest::addColumn("fileName"); + QTest::addColumn("pos"); + using ItemList = QList; + QTest::addColumn("expectedResults"); + + static const auto makeItem = [](int line, int column, Usage::Type type) { + SearchResultItem item; + item.setMainRange(line, column, 0); + item.setUserData(int(type)); + return item; + }; + + QTest::newRow("struct member") << "defs.h" << 55 << ItemList{ + makeItem(2, 17, Usage::Type::Read), makeItem(3, 15, Usage::Type::Declaration), + makeItem(6, 17, Usage::Type::WritableRef), makeItem(8, 11, Usage::Type::WritableRef), + makeItem(9, 13, Usage::Type::WritableRef), makeItem(10, 12, Usage::Type::WritableRef), + makeItem(11, 13, Usage::Type::WritableRef), makeItem(12, 14, Usage::Type::WritableRef), + makeItem(13, 26, Usage::Type::WritableRef), makeItem(14, 23, Usage::Type::Read), + makeItem(15, 14, Usage::Type::Read), makeItem(16, 24, Usage::Type::WritableRef), + makeItem(17, 15, Usage::Type::WritableRef), makeItem(18, 22, Usage::Type::Read), + makeItem(19, 12, Usage::Type::WritableRef), makeItem(20, 12, Usage::Type::Read), + makeItem(21, 13, Usage::Type::WritableRef), makeItem(22, 13, Usage::Type::Read), + makeItem(23, 12, Usage::Type::Read), makeItem(42, 20, Usage::Type::Read), + makeItem(44, 15, Usage::Type::Read), makeItem(47, 15, Usage::Type::Write), + makeItem(50, 11, Usage::Type::Read), makeItem(51, 11, Usage::Type::Write), + makeItem(52, 9, Usage::Type::Write), makeItem(53, 7, Usage::Type::Write), + makeItem(56, 7, Usage::Type::Write), makeItem(56, 25, Usage::Type::Other), + makeItem(58, 13, Usage::Type::Read), makeItem(58, 25, Usage::Type::Read), + makeItem(59, 7, Usage::Type::Write), makeItem(59, 24, Usage::Type::Read)}; + QTest::newRow("constructor member initialization") << "defs.h" << 68 << ItemList{ + makeItem(2, 10, Usage::Type::Write), makeItem(4, 8, Usage::Type::Declaration)}; + QTest::newRow("direct member initialization") << "defs.h" << 101 << ItemList{ + makeItem(5, 21, Usage::Type::Initialization), makeItem(45, 16, Usage::Type::Read)}; + + // FIXME: The override gets reported twice. clangd bug? + QTest::newRow("pure virtual declaration") << "defs.h" << 420 << ItemList{ + makeItem(17, 17, Usage::Type::Declaration), makeItem(21, 9, Usage::Type::Declaration), + makeItem(21, 9, Usage::Type::Declaration)}; + + QTest::newRow("pointer variable") << "main.cpp" << 52 << ItemList{ + makeItem(6, 10, Usage::Type::Initialization), makeItem(8, 4, Usage::Type::Write), + makeItem(10, 4, Usage::Type::Write), makeItem(24, 5, Usage::Type::Write), + makeItem(25, 11, Usage::Type::WritableRef), makeItem(26, 11, Usage::Type::Read), + makeItem(27, 10, Usage::Type::WritableRef), makeItem(28, 10, Usage::Type::Read), + makeItem(29, 11, Usage::Type::Read), makeItem(30, 15, Usage::Type::WritableRef), + makeItem(31, 22, Usage::Type::Read)}; + QTest::newRow("struct variable") << "main.cpp" << 39 << ItemList{ + makeItem(5, 7, Usage::Type::Declaration), makeItem(6, 15, Usage::Type::WritableRef), + makeItem(8, 9, Usage::Type::WritableRef), makeItem(9, 11, Usage::Type::WritableRef), + makeItem(11, 4, Usage::Type::Write), makeItem(11, 11, Usage::Type::WritableRef), + makeItem(12, 12, Usage::Type::WritableRef), makeItem(13, 6, Usage::Type::Write), + makeItem(14, 21, Usage::Type::Read), makeItem(15, 4, Usage::Type::Write), + makeItem(15, 12, Usage::Type::Read), makeItem(16, 22, Usage::Type::WritableRef), + makeItem(17, 13, Usage::Type::WritableRef), makeItem(18, 20, Usage::Type::Read), + makeItem(19, 10, Usage::Type::WritableRef), makeItem(20, 10, Usage::Type::Read), + makeItem(21, 11, Usage::Type::WritableRef), makeItem(22, 11, Usage::Type::Read), + makeItem(23, 10, Usage::Type::Read), makeItem(32, 4, Usage::Type::Write), + makeItem(33, 23, Usage::Type::WritableRef), makeItem(34, 23, Usage::Type::Read), + makeItem(35, 15, Usage::Type::WritableRef), makeItem(36, 22, Usage::Type::WritableRef), + makeItem(37, 4, Usage::Type::Read), makeItem(38, 4, Usage::Type::WritableRef), + makeItem(39, 6, Usage::Type::WritableRef), makeItem(40, 4, Usage::Type::Read), + makeItem(41, 4, Usage::Type::WritableRef), makeItem(42, 4, Usage::Type::Read), + makeItem(42, 18, Usage::Type::Read), makeItem(43, 11, Usage::Type::Write), + makeItem(54, 4, Usage::Type::Other), makeItem(55, 4, Usage::Type::Other)}; + + // Some of these are conceptually questionable, as S is a type and thus we cannot "read from" + // or "write to" it. But it probably matches the intuitive user expectation. + QTest::newRow("struct type") << "defs.h" << 7 << ItemList{ + makeItem(1, 7, Usage::Type::Declaration), makeItem(2, 4, Usage::Type::Declaration), + makeItem(20, 19, Usage::Type::Other), makeItem(10, 9, Usage::Type::WritableRef), + makeItem(12, 4, Usage::Type::Write), makeItem(44, 12, Usage::Type::Read), + makeItem(45, 13, Usage::Type::Read), makeItem(47, 12, Usage::Type::Write), + makeItem(50, 8, Usage::Type::Read), makeItem(51, 8, Usage::Type::Write), + makeItem(52, 6, Usage::Type::Write), makeItem(53, 4, Usage::Type::Write), + makeItem(56, 4, Usage::Type::Write), makeItem(56, 22, Usage::Type::Other), + makeItem(58, 10, Usage::Type::Read), makeItem(58, 22, Usage::Type::Read), + makeItem(59, 4, Usage::Type::Write), makeItem(59, 21, Usage::Type::Read)}; + + QTest::newRow("subclass") << "defs.h" << 450 << ItemList{ + makeItem(20, 7, Usage::Type::Declaration), makeItem(5, 4, Usage::Type::Other), + makeItem(13, 21, Usage::Type::Other), makeItem(32, 8, Usage::Type::Other)}; + QTest::newRow("array variable") << "main.cpp" << 1134 << ItemList{ + makeItem(57, 8, Usage::Type::Declaration), makeItem(58, 4, Usage::Type::Write), + makeItem(59, 15, Usage::Type::Read)}; } // 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() +void ClangdTestFindReferences::test() { - // Find suitable kit. - const QList 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"); + QFETCH(QString, fileName); + QFETCH(int, pos); + QFETCH(QList, expectedResults); - // 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); + TextEditor::TextDocument * const doc = document(fileName); + QVERIFY(doc); + QTextCursor cursor(doc->document()); + cursor.setPosition(pos); + client()->findUsages(doc, cursor, {}); + QVERIFY(waitForSignalOrTimeout(client(), &ClangdClient::findUsagesDone, timeOutInMs())); - // 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, timeOutInMs())); - client = modelManagerSupport->clientForProject(openProjectResult.project()); + QCOMPARE(m_actualResults.size(), expectedResults.size()); + for (int i = 0; i < m_actualResults.size(); ++i) { + const SearchResultItem &curActual = m_actualResults.at(i); + const SearchResultItem &curExpected = expectedResults.at(i); + QCOMPARE(curActual.mainRange().begin.line, curExpected.mainRange().begin.line); + QCOMPARE(curActual.mainRange().begin.column, curExpected.mainRange().begin.column); + QCOMPARE(curActual.userData(), curExpected.userData()); } - 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, timeOutInMs())); - 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"); +ClangdTestFollowSymbol::ClangdTestFollowSymbol() +{ + setProjectFileName("follow-symbol.pro"); + setSourceFileNames({"main.cpp", "header.h"}); + setMinimumVersion(12); +} - // Wait for index to build. - if (!client->isFullyIndexed()) - QVERIFY(waitForSignalOrTimeout(client, &ClangdClient::indexingFinished, timeOutInMs())); - QVERIFY(client->isFullyIndexed()); +void ClangdTestFollowSymbol::test_data() +{ + QTest::addColumn("sourceFile"); + QTest::addColumn("sourceLine"); + QTest::addColumn("sourceColumn"); + QTest::addColumn("targetFile"); + QTest::addColumn("targetLine"); + QTest::addColumn("targetColumn"); - // Open cpp documents. - struct EditorCloser { - static void cleanup(IEditor *editor) { EditorManager::closeEditors({editor}); } + QTest::newRow("on namespace") << "main.cpp" << 27 << 1 << "header.h" << 28 << 11; + QTest::newRow("class ref") << "main.cpp" << 27 << 9 << "header.h" << 34 << 7; + QTest::newRow("forward decl (same file)") << "header.h" << 32 << 7 << "header.h" << 34 << 7; + QTest::newRow("forward decl (different file") << "header.h" << 48 << 9 << "main.cpp" << 54 << 7; + QTest::newRow("class definition (same file)") << "header.h" << 34 << 7 << "header.h" << 32 << 7; + QTest::newRow("class definition (different file)") << "main.cpp" << 54 << 7 + << "header.h" << 48 << 7; + QTest::newRow("constructor decl") << "header.h" << 36 << 5 << "main.cpp" << 27 << 14; + QTest::newRow("constructor definition") << "main.cpp" << 27 << 14 << "header.h" << 36 << 5; + QTest::newRow("member ref") << "main.cpp" << 39 << 10 << "header.h" << 38 << 18; + QTest::newRow("union member ref") << "main.cpp" << 91 << 20 << "main.cpp" << 86 << 13; + QTest::newRow("member decl") << "header.h" << 38 << 18 << "header.h" << 38 << 18; + QTest::newRow("function ref") << "main.cpp" << 66 << 12 << "main.cpp" << 35 << 5; + QTest::newRow("member function ref") << "main.cpp" << 42 << 12 << "main.cpp" << 49 << 21; + QTest::newRow("function with no def ref") << "main.cpp" << 43 << 5 << "header.h" << 59 << 5; + QTest::newRow("function def") << "main.cpp" << 35 << 5 << "header.h" << 52 << 5; + QTest::newRow("member function def") << "main.cpp" << 49 << 21 << "header.h" << 43 << 9; + QTest::newRow("member function decl") << "header.h" << 43 << 9 << "main.cpp" << 49 << 21; + QTest::newRow("include") << "main.cpp" << 25 << 13 << "header.h" << 1 << 1; + QTest::newRow("local var") << "main.cpp" << 39 << 6 << "main.cpp" << 36 << 9; + QTest::newRow("alias") << "main.cpp" << 36 << 5 << "main.cpp" << 33 << 7; + QTest::newRow("static var") << "main.cpp" << 40 << 27 << "header.h" << 30 << 7; + QTest::newRow("member function ref (other class)") << "main.cpp" << 62 << 39 + << "cursor.cpp" << 104 << 22; + QTest::newRow("macro ref") << "main.cpp" << 66 << 43 << "header.h" << 27 << 9; + QTest::newRow("on namespace 2") << "main.cpp" << 27 << 3 << "header.h" << 28 << 11; + QTest::newRow("after namespace") << "main.cpp" << 27 << 7 << "header.h" << 28 << 11; + QTest::newRow("operator def") << "main.cpp" << 76 << 13 << "main.cpp" << 72 << 9; + QTest::newRow("operator def 2") << "main.cpp" << 80 << 15 << "main.cpp" << 73 << 10; + QTest::newRow("operator decl") << "main.cpp" << 72 << 12 << "main.cpp" << 76 << 10; + QTest::newRow("operator decl 2") << "main.cpp" << 73 << 12 << "main.cpp" << 80 << 11; +} + +void ClangdTestFollowSymbol::test() +{ + QFETCH(QString, sourceFile); + QFETCH(int, sourceLine); + QFETCH(int, sourceColumn); + QFETCH(QString, targetFile); + QFETCH(int, targetLine); + QFETCH(int, targetColumn); + + TextEditor::TextDocument * const doc = document(sourceFile); + QVERIFY(doc); + + QTimer timer; + timer.setSingleShot(true); + QEventLoop loop; + QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + Utils::Link actualLink; + const auto handler = [&actualLink, &loop](const Utils::Link &l) { + actualLink = l; + loop.quit(); }; - const auto headerPath = Utils::FilePath::fromString(testDir.absolutePath("defs.h")); - QVERIFY2(headerPath.exists(), qPrintable(headerPath.toUserOutput())); - QScopedPointer headerEditor(EditorManager::openEditor(headerPath)); - QVERIFY(headerEditor); - const auto headerDoc = qobject_cast(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 cppFileEditor(EditorManager::openEditor(cppFilePath)); - QVERIFY(cppFileEditor); - const auto cppDoc = qobject_cast(cppFileEditor->document()); - QVERIFY(cppDoc); - QVERIFY(client->documentForFilePath(cppFilePath) == cppDoc); + QTextCursor cursor(doc->document()); + const int pos = Utils::Text::positionInText(doc->document(), sourceLine, sourceColumn); + cursor.setPosition(pos); + client()->followSymbol(doc, cursor, nullptr, handler, true, false); + timer.start(10000); + loop.exec(); + QVERIFY(timer.isActive()); + timer.stop(); - // ... and we're ready to go. - QList searchResults; - connect(client, &ClangdClient::foundReferences, this, - [&searchResults](const QList &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, timeOutInMs())); \ -} 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); + QCOMPARE(actualLink.targetFilePath, filePath(targetFile)); + QEXPECT_FAIL("union member ref", "FIXME: clangd points to union", Abort); + QCOMPARE(actualLink.targetLine, targetLine); + QCOMPARE(actualLink.targetColumn + 1, targetColumn); } } // namespace Tests diff --git a/src/plugins/clangcodemodel/test/clangdtests.h b/src/plugins/clangcodemodel/test/clangdtests.h index ddf2c1b1d27..9a730793bda 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.h +++ b/src/plugins/clangcodemodel/test/clangdtests.h @@ -25,20 +25,83 @@ #pragma once +#include +#include +#include + +#include #include +#include + +namespace ProjectExplorer { +class Kit; +class Project; +} +namespace TextEditor { class TextDocument; } namespace ClangCodeModel { namespace Internal { +class ClangdClient; namespace Tests { -class ClangdTests : public QObject +class ClangdTest : public QObject { Q_OBJECT +public: + ~ClangdTest(); + +protected: + // Convention: base bame == name of parent dir + void setProjectFileName(const QString &fileName) { m_projectFileName = fileName; } + + void setSourceFileNames(const QStringList &fileNames) { m_sourceFileNames = fileNames; } + void setMinimumVersion(int version) { m_minVersion = version; } + + ClangdClient *client() const { return m_client; } + Utils::FilePath filePath(const QString &fileName) const; + TextEditor::TextDocument *document(const QString &fileName) const { + return m_sourceDocuments.value(fileName); + } + +protected slots: + virtual void initTestCase(); + +private: + CppTools::Tests::TemporaryCopiedDir *m_projectDir = nullptr; + QString m_projectFileName; + QStringList m_sourceFileNames; + QHash m_sourceDocuments; + ProjectExplorer::Kit *m_kit = nullptr; + ProjectExplorer::Project *m_project = nullptr; + ClangdClient *m_client = nullptr; + int m_minVersion = -1; +}; + +class ClangdTestFindReferences : public ClangdTest +{ + Q_OBJECT +public: + ClangdTestFindReferences(); private slots: - void initTestCase(); + void initTestCase() override; + void init() { m_actualResults.clear(); } + void test_data(); + void test(); - void testFindReferences(); +private: + QList m_actualResults; +}; + +class ClangdTestFollowSymbol : public ClangdTest +{ + Q_OBJECT +public: + ClangdTestFollowSymbol(); + +private slots: + void test_data(); + void test(); }; } // namespace Tests diff --git a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc index 073e9fa309e..9d09b7060fd 100644 --- a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc +++ b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc @@ -32,5 +32,10 @@ find-usages/defs.h find-usages/main.cpp find-usages/find-usages.pro + follow-symbol/cursor.cpp + follow-symbol/cursor.h + follow-symbol/follow-symbol.pro + follow-symbol/header.h + follow-symbol/main.cpp diff --git a/src/plugins/clangcodemodel/test/data/follow-symbol/cursor.cpp b/src/plugins/clangcodemodel/test/data/follow-symbol/cursor.cpp new file mode 100644 index 00000000000..82df2cb378c --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/follow-symbol/cursor.cpp @@ -0,0 +1,147 @@ +#include "cursor.h" + +void function(int x) +{ + +} + +namespace Namespace +{ +SuperClass::SuperClass(int x) noexcept + : y(x) +{ + int LocalVariable; +} + +int SuperClass::Method() +{ + Method(); + AbstractVirtualMethod(y); + int LocalVariable; + return y; +} + +int SuperClass::VirtualMethod(int z) +{ + AbstractVirtualMethod(z); + + return y; +} + +bool SuperClass::ConstMethod() const +{ + return y; +} + +void SuperClass::StaticMethod() +{ + using longint = long long int; + using lint = longint; + + lint foo; + + foo = 30; + + const lint bar = 20; +} +} + +template +void TemplateFunction(T LocalVariableParameter) +{ + T LocalVariable; +} + +Namespace::SuperClass::operator int() const +{ + int LocalVariable; +} + +int Namespace::SuperClass::operator ++() const +{ + int LocalVariable; + + return LocalVariable; +} + +Namespace::SuperClass::~SuperClass() +{ + int LocalVariable; +} + +void Struct::FinalVirtualMethod() +{ + +} + +void f1(Struct *FindFunctionCaller) +{ + FindFunctionCaller->FinalVirtualMethod(); +} + +void f2(){ + Struct *s = new Struct; + + f1(s); +} + +void f3() +{ + auto FindFunctionCaller = Struct(); + + FindFunctionCaller.FinalVirtualMethod(); +} + + +void f4() +{ + Struct s; + + auto *sPointer = &s; + auto sValue = s; +} + +void NonFinalStruct::function() +{ + FinalVirtualMethod(); +} + +void OutputFunction(int &out, int in = 1, const int &in2=2, int *out2=nullptr); +void InputFunction(const int &value); + +void f5() +{ + int OutputValue; + int InputValue = 20; + + OutputFunction(OutputValue); + InputFunction(InputValue); +} + +void ArgumentCountZero(); +void ArgumentCountTwo(int one, const int &two); +void IntegerValue(int); +void LValueReference(int &); +void ConstLValueReference(const int &); +void PointerToConst(const int *); +void Pointer(int *); +void ConstantPointer(int *const); +void ConstIntegerValue(const int); + +void NonFinalStruct::ProtectedMethodAccessSpecifier() {} + +extern int ExternVarStorageClass; + +static void StaticMethodStorageClass() {} + +template const T &InvalidStorageClass(const T &type) { return type; } + +namespace Outer { +namespace { + +} + +enum { + X, Y +}; +} diff --git a/src/plugins/clangcodemodel/test/data/follow-symbol/cursor.h b/src/plugins/clangcodemodel/test/data/follow-symbol/cursor.h new file mode 100644 index 00000000000..7db9be6e97b --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/follow-symbol/cursor.h @@ -0,0 +1,44 @@ + + + +namespace Namespace +{ +class SuperClass; +/** + * A brief comment + */ +class SuperClass +{ + SuperClass() = default; + SuperClass(int x) noexcept; + int Method(); + virtual int VirtualMethod(int z); + virtual int AbstractVirtualMethod(int z) = 0; + bool ConstMethod() const; + static void StaticMethod(); + operator int() const; + int operator ++() const; + ~SuperClass(); + +private: + int y; +}; +} + +struct Struct final +{ + virtual void FinalVirtualMethod() final; +}; + +union Union +{ + +}; + +struct NonFinalStruct +{ + virtual void FinalVirtualMethod() final; + void function(); +protected: + void ProtectedMethodAccessSpecifier(); +}; diff --git a/src/plugins/clangcodemodel/test/data/follow-symbol/follow-symbol.pro b/src/plugins/clangcodemodel/test/data/follow-symbol/follow-symbol.pro new file mode 100644 index 00000000000..7d2c3147a3e --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/follow-symbol/follow-symbol.pro @@ -0,0 +1,4 @@ +TEMPLATE = app +CONFIG -= qt +HEADERS = cursor.h header.h +SOURCES = cursor.cpp main.cpp diff --git a/src/plugins/clangcodemodel/test/data/follow-symbol/header.h b/src/plugins/clangcodemodel/test/data/follow-symbol/header.h new file mode 100644 index 00000000000..86308340231 --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/follow-symbol/header.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 + +#define TEST_DEFINE 1 +namespace Fooish +{ +float flvalue = 100.f; + +class Bar; + +class Bar { +public: + Bar(); + + volatile int member = 0; +}; + +struct Barish +{ + int foo(float p, int u); + int mem = 10; +}; +} + +class FooClass; + +int foo(const float p, int u); + +int foo(); + +int foo(float p, int u) +{ + return foo() + p + u; +} + +int foo(int x, float y); diff --git a/src/plugins/clangcodemodel/test/data/follow-symbol/main.cpp b/src/plugins/clangcodemodel/test/data/follow-symbol/main.cpp new file mode 100644 index 00000000000..56b189e77c1 --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/follow-symbol/main.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "header.h" +#include "cursor.h" +Fooish::Bar::Bar() { + +} + +class X; + +using YYY = Fooish::Bar; + +int foo() { + YYY bar; + bar.member = 30; + Fooish::Barish barish; + bar.member++; + barish.mem = Fooish::flvalue; + + barish.foo(1.f, 2); + foo(1, 2.f); + return 1; + + X* x; +} + +int Fooish::Barish::foo(float p, int u) +{ + return ::foo() + p + u; +} + +class FooClass +{ +public: + FooClass(); + static int mememember; +}; + +FooClass::FooClass() { + NonFinalStruct nfStruct; nfStruct.function(); +} + +int main() { + return foo() + FooClass::mememember + TEST_DEFINE; +} + +class Bar +{ +public: + int operator&(); + Bar& operator[](int); +}; + +int Bar::operator&() { + return 0; +} + +Bar& Bar::operator[](int) { + return *this; +} + +struct S { + union { + int i = 12; + void *something; + }; + int func(bool b) { + if (b) + return i; + int i = 42; + return i; + } +};