diff options
Diffstat (limited to 'tests/auto/qmlls/utils/tst_qmlls_utils.cpp')
-rw-r--r-- | tests/auto/qmlls/utils/tst_qmlls_utils.cpp | 4156 |
1 files changed, 4156 insertions, 0 deletions
diff --git a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp new file mode 100644 index 0000000000..332dc13590 --- /dev/null +++ b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp @@ -0,0 +1,4156 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "tst_qmlls_utils.h" +#include <algorithm> +#include <optional> + +#include <QtCore/private/qduplicatetracker_p.h> +#include <QtQmlLS/private/qdochtmlparser_p.h> + +// some helper constants for the tests +const static int positionAfterOneIndent = 5; +const static QString noResultExpected; +// constants for resultIndex +const static int firstResult = 0; +const static int secondResult = 1; +// constants for expectedItemsCount +const static int outOfOne = 1; +const static int outOfTwo = 2; + +// enable/disable additional debug output +constexpr static bool enable_debug_output = true; + +static QString printSet(const QSet<QString> &s) +{ + const QString r = QStringList(s.begin(), s.end()).join(u", "_s); + return r; +} + +static QString readFileContent(const QString &testFileName) { + QFile file(testFileName); + if (file.open(QIODeviceBase::ReadOnly)) + return QString::fromUtf8(file.readAll()); + return QString{}; +}; + +std::tuple<QQmlJS::Dom::DomItem, QQmlJS::Dom::DomItem> +tst_qmlls_utils::createEnvironmentAndLoadFile(const QString &filePath) +{ + CacheKey cacheKey = QDir::cleanPath(filePath + u"/.."_s); + if (auto entry = cache.find(cacheKey); entry != cache.end()) { + QQmlJS::Dom::DomItem env{ *entry }; + return { env, env.field(QQmlJS::Dom::Fields::qmlFileWithPath).key(filePath) }; + }; + + QStringList qmltypeDirs = + QStringList({ dataDirectory(), QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) }); + + // This should be exactly the same options as qmlls uses in qqmlcodemodel. + // Otherwise, this test will not test the codepaths also used by qmlls and will be useless. + const QQmlJS::Dom::DomCreationOptions options = QQmlJS::Dom::DomCreationOptions{} + | QQmlJS::Dom::DomCreationOption::WithSemanticAnalysis + | QQmlJS::Dom::DomCreationOption::WithScriptExpressions + | QQmlJS::Dom::DomCreationOption::WithRecovery; + + auto envPtr = QQmlJS::Dom::DomEnvironment::create( + qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options); + + QQmlJS::Dom::DomItem file; + QQmlJS::Dom::DomItem env(envPtr); + envPtr->loadFile(QQmlJS::Dom::FileToLoad::fromFileSystem(envPtr, filePath), + [&file](QQmlJS::Dom::Path, const QQmlJS::Dom::DomItem &, + const QQmlJS::Dom::DomItem &newIt) { file = newIt; }); + + envPtr->loadPendingDependencies(); + envPtr->loadBuiltins(); + + cache[cacheKey] = envPtr; + return std::make_tuple(env, file); +} + +void tst_qmlls_utils::textOffsetRowColumnConversions_data() +{ + QTest::addColumn<QString>("code"); + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + QTest::addColumn<qsizetype>("expectedOffset"); + QTest::addColumn<QChar>("expectedChar"); + // in case they differ from line and character, e.g. when accessing non-existing line or rows + // set to -1 when same as before + QTest::addColumn<int>("expectedLine"); + QTest::addColumn<int>("expectedCharacter"); + + QTest::newRow("oneline") << u"Hello World!"_s << 0 << 6 << 6ll << QChar('W') << -1 << -1; + QTest::newRow("multi-line") << u"Hello World!\n How are you? \n Bye!\n"_s << 0 << 6 << 6ll + << QChar('W') << -1 << -1; + QTest::newRow("multi-line2") << u"Hello World!\n How are you? \n Bye!\n"_s << 1 << 5 << 18ll + << QChar('a') << -1 << -1; + QTest::newRow("multi-line3") << u"Hello World!\n How are you? \n Bye!\n"_s << 2 << 1 << 29ll + << QChar('B') << -1 << -1; + + QTest::newRow("newlines") << u"A\nB\r\nC\n\r\nD\r\n\r"_s << 0 << 0 << 0ll << QChar('A') << -1 + << -1; + QTest::newRow("newlines2") << u"A\nB\r\nC\n\r\nD\r\n\r"_s << 1 << 0 << 2ll << QChar('B') << -1 + << -1; + + // try to access '\r' + QTest::newRow("newlines3") << u"A\nB\r\nC\n\r\nD\r\n\r"_s << 1 << 1 << 3ll << QChar('\r') << -1 + << -1; + // try to access '\n', should return the last character of the line (which is '\r' in this case) + QTest::newRow("newlines4") << u"A\nB\r\nC\n\r\nD\r\n\r"_s << 1 << 2 << 3ll << QChar('\r') << -1 + << 1; + // try to access after the end of the line, should return the last character of the line (which + // is '\r' in this case) + QTest::newRow("afterLineEnd") << u"A\nB\r\nC\n\r\nD\r\n\r"_s << 1 << 42 << 3ll << QChar('\r') + << -1 << 1; + + // try to access an inexisting column, seems to return the last character of the last line. + QTest::newRow("afterColumnEnd") + << u"A\nB\r\nC\n\r\nD\r\n\rAX"_s << 42 << 0 << 15ll << QChar('X') << 5 << 2; +} + +void tst_qmlls_utils::textOffsetRowColumnConversions() +{ + QFETCH(QString, code); + QFETCH(int, line); + QFETCH(int, character); + QFETCH(qsizetype, expectedOffset); + QFETCH(QChar, expectedChar); + QFETCH(int, expectedLine); + QFETCH(int, expectedCharacter); + + qsizetype offset = QQmlLSUtils::textOffsetFrom(code, line, character); + + QCOMPARE(offset, expectedOffset); + if (offset < code.size()) + QCOMPARE(code[offset], expectedChar); + + auto [computedRow, computedColumn] = QQmlLSUtils::textRowAndColumnFrom(code, expectedOffset); + if (expectedLine == -1) + expectedLine = line; + if (expectedCharacter == -1) + expectedCharacter = character; + + QCOMPARE(computedRow, expectedLine); + QCOMPARE(computedColumn, expectedCharacter); +} + +void tst_qmlls_utils::findItemFromLocation_data() +{ + QTest::addColumn<QString>("filePath"); + // keep in mind that line and character are starting at 1! + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + // in case there are multiple items to be found (e.g. for a location between two objects), the + // item to be checked against + QTest::addColumn<int>("resultIndex"); + QTest::addColumn<int>("expectedItemsCount"); + QTest::addColumn<QQmlJS::Dom::DomType>("expectedType"); + // set to -1 when unchanged from above line and character, starts at 1 + QTest::addColumn<int>("expectedLine"); + QTest::addColumn<int>("expectedCharacter"); + + const QString file1Qml = testFile(u"file1.qml"_s); + + QTest::addRow("findIntProperty") << file1Qml << 9 << 18 << firstResult << outOfOne + << QQmlJS::Dom::DomType::PropertyDefinition + // start of the "property"-token of the "a" property + << -1 << positionAfterOneIndent; + QTest::addRow("findIntProperty2") << file1Qml << 9 << 10 << firstResult << outOfOne + << QQmlJS::Dom::DomType::PropertyDefinition + // start of the "property"-token of the "a" property + << -1 << positionAfterOneIndent; + QTest::addRow("findIntBinding") + << file1Qml << 30 << positionAfterOneIndent << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression + // start of the a identifier of the "a" binding + << -1 << positionAfterOneIndent; + QTest::addRow("findIntBinding2") << file1Qml << 30 << 8 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptLiteral + << -1 << 8; + + QTest::addRow("colorBinding") << file1Qml << 39 << 13 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 + << 2 * positionAfterOneIndent - 1; + + QTest::addRow("findVarProperty") << file1Qml << 12 << 12 << firstResult << outOfOne + << QQmlJS::Dom::DomType::PropertyDefinition + // start of the "property"-token of the "d" property + << -1 << positionAfterOneIndent; + QTest::addRow("findVarBinding") << file1Qml << 31 << 8 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptLiteral << -1 << 8; + QTest::addRow("beforeEProperty") + << file1Qml << 13 << positionAfterOneIndent << firstResult << outOfOne + << QQmlJS::Dom::DomType::PropertyDefinition + // start of the "property"-token of the "e" property + << -1 << -1; + QTest::addRow("onEProperty") << file1Qml << 13 << 24 << firstResult << outOfOne + << QQmlJS::Dom::DomType::PropertyDefinition + // start of the "property"-token of the "e" property + << -1 << positionAfterOneIndent; + QTest::addRow("afterEProperty") << file1Qml << 13 << 25 << firstResult << outOfOne + << QQmlJS::Dom::DomType::PropertyDefinition + // start of the "property"-token of the "e" property + << -1 << positionAfterOneIndent; + + QTest::addRow("property-in-ic") << file1Qml << 28 << 38 << firstResult << outOfOne + << QQmlJS::Dom::DomType::PropertyDefinition << -1 << 26; + + QTest::addRow("onCChild") << file1Qml << 16 << positionAfterOneIndent << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 + << positionAfterOneIndent; + + // check for off-by-one/overlapping items + QTest::addRow("closingBraceOfC") + << file1Qml << 16 << 19 << firstResult << outOfOne << QQmlJS::Dom::DomType::QmlObject + << -1 << positionAfterOneIndent; + QTest::addRow("beforeClosingBraceOfC") + << file1Qml << 16 << 18 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 12; + QTest::addRow("firstBetweenCandD") + << file1Qml << 16 << 20 << secondResult << outOfTwo << QQmlJS::Dom::DomType::QmlObject + << -1 << positionAfterOneIndent; + QTest::addRow("secondBetweenCandD") + << file1Qml << 16 << 20 << firstResult << outOfTwo + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << -1; + + QTest::addRow("afterD") << file1Qml << 16 << 21 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 20; + + // check what happens between items (it should not crash) + + QTest::addRow("onWhitespaceBeforeC") + << file1Qml << 16 << 1 << firstResult << outOfOne << QQmlJS::Dom::DomType::Map << 9 + << positionAfterOneIndent; + + QTest::addRow("onWhitespaceAfterC") + << file1Qml << 17 << 8 << firstResult << outOfOne << QQmlJS::Dom::DomType::QmlObject + << -1 << positionAfterOneIndent; + + QTest::addRow("onWhitespaceBetweenCAndD") << file1Qml << 17 << 23 << firstResult << outOfOne + << QQmlJS::Dom::DomType::Map << 16 << 8; + QTest::addRow("onWhitespaceBetweenCAndD2") << file1Qml << 17 << 24 << firstResult << outOfOne + << QQmlJS::Dom::DomType::Map << 16 << 8; + + QTest::addRow("ic") << file1Qml << 15 << 5 << firstResult << outOfOne + << QQmlJS::Dom::DomType::QmlComponent << -1 << 5; + QTest::addRow("ic2") << file1Qml << 15 << 20 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 18; + QTest::addRow("ic3") << file1Qml << 15 << 33 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 29; + + QTest::addRow("function") << file1Qml << 33 << 5 << firstResult << outOfOne + << QQmlJS::Dom::DomType::MethodInfo << -1 << positionAfterOneIndent; + QTest::addRow("function-parameter") + << file1Qml << 33 << 20 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 19; + // The return type of a function has no own DomItem. Instead, the return type of a function + // is saved into the MethodInfo. + QTest::addRow("function-return") + << file1Qml << 33 << 41 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 41; + QTest::addRow("function2") << file1Qml << 36 << 17 << firstResult << outOfOne + << QQmlJS::Dom::DomType::MethodInfo << -1 + << positionAfterOneIndent; + + // check rectangle property + QTest::addRow("rectangle-property") + << file1Qml << 44 << 31 << firstResult << outOfOne + << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 29; +} + +void tst_qmlls_utils::findItemFromLocation() +{ + QFETCH(QString, filePath); + QFETCH(int, line); + QFETCH(int, character); + QFETCH(int, resultIndex); + QFETCH(int, expectedItemsCount); + QFETCH(QQmlJS::Dom::DomType, expectedType); + QFETCH(int, expectedLine); + QFETCH(int, expectedCharacter); + + if (expectedLine == -1) + expectedLine = line; + if (expectedCharacter == -1) + expectedCharacter = character; + + // they all start at 1. + Q_ASSERT(line > 0); + Q_ASSERT(character > 0); + Q_ASSERT(expectedLine > 0); + Q_ASSERT(expectedCharacter > 0); + + auto [env, file] = createEnvironmentAndLoadFile(filePath); + + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1); + + QVERIFY(resultIndex < locations.size()); + QCOMPARE(locations.size(), expectedItemsCount); + + QQmlJS::Dom::DomItem itemToTest = locations[resultIndex].domItem; + // ask for the type in the args + if constexpr (enable_debug_output) { + if (itemToTest.internalKind() != expectedType) { + qDebug() << itemToTest.internalKindStr() << " has not the expected kind " + << expectedType << " for item " << itemToTest.toString(); + } + } + QCOMPARE(itemToTest.internalKind(), expectedType); + + QQmlJS::Dom::FileLocations::Tree locationToTest = locations[resultIndex].fileLocation; + QCOMPARE(locationToTest->info().fullRegion.startLine, quint32(expectedLine)); + QCOMPARE(locationToTest->info().fullRegion.startColumn, quint32(expectedCharacter)); +} + +void tst_qmlls_utils::findTypeDefinitionFromLocation_data() +{ + QTest::addColumn<QString>("filePath"); + // keep in mind that line and character are starting at 1! + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + // in case there are multiple items to be found (e.g. for a location between two objects), the + // item to be checked against + QTest::addColumn<int>("resultIndex"); + QTest::addColumn<int>("expectedItemsCount"); + QTest::addColumn<QString>("expectedFilePath"); + // set to -1 when unchanged from above line and character. 0-based. + QTest::addColumn<int>("expectedLine"); + QTest::addColumn<int>("expectedCharacter"); + + const QString file1Qml = testFile(u"file1.qml"_s); + const QString TypeQml = testFile(u"Type.qml"_s); + // pass this as file when no result is expected, e.g. for type definition of "var". + + QTest::addRow("onCProperty") << file1Qml << 11 << 16 << firstResult << outOfOne << file1Qml << 7 + << positionAfterOneIndent; + + QTest::addRow("onCProperty2") << file1Qml << 28 << 37 << firstResult << outOfOne << file1Qml + << 7 << positionAfterOneIndent; + + QTest::addRow("onCProperty3") << file1Qml << 28 << 35 << firstResult << outOfOne << file1Qml + << 7 << positionAfterOneIndent; + + QTest::addRow("onCBinding") << file1Qml << 46 << 8 << firstResult << outOfOne << file1Qml << 7 + << positionAfterOneIndent; + + QTest::addRow("onDefaultBinding") << file1Qml << 16 << positionAfterOneIndent << firstResult + << outOfOne << file1Qml << 7 << positionAfterOneIndent; + + QTest::addRow("onDefaultBindingId") + << file1Qml << 16 << 28 << firstResult << outOfOne << file1Qml << 16 << 20; + + QTest::addRow("findIntProperty") << file1Qml << 9 << 18 << firstResult << outOfOne << file1Qml + << -1 << positionAfterOneIndent; + QTest::addRow("colorBinding") << file1Qml << 39 << 8 << firstResult << outOfOne << file1Qml + << -1 << positionAfterOneIndent; + + // check what happens between items (it should not crash) + + QTest::addRow("onWhitespaceBeforeC") + << file1Qml << 16 << 1 << firstResult << outOfOne << noResultExpected << -1 << -1; + + QTest::addRow("onWhitespaceAfterC") + << file1Qml << 17 << 23 << firstResult << outOfOne << noResultExpected << -1 << -1; + + QTest::addRow("onWhitespaceBetweenCAndD") + << file1Qml << 17 << 24 << firstResult << outOfOne << noResultExpected << -1 << -1; + + QTest::addRow("ic") << file1Qml << 15 << 15 << firstResult << outOfOne << file1Qml << 15 << 15; + QTest::addRow("icBase") << file1Qml << 15 << 20 << firstResult << outOfOne + << u"TODO: file location for C++ defined types?"_s << -1 << -1; + QTest::addRow("ic3") << file1Qml << 15 << 33 << firstResult << outOfOne << file1Qml << -1 << 18; + + // TODO: type definition of function = type definition of return type? + // if not, this might need fixing: + // currently, asking the type definition of the "function" keyword returns + // the type definitin of the return type (when available). + QTest::addRow("function-keyword") << file1Qml << 33 << 5 << firstResult << outOfOne << file1Qml + << 7 << positionAfterOneIndent; + QTest::addRow("function-parameter-builtin") + << file1Qml << 33 << 20 << firstResult << outOfOne << file1Qml << -1 << -1; + QTest::addRow("function-parameter-item") << file1Qml << 33 << 36 << firstResult << outOfOne + << file1Qml << 7 << positionAfterOneIndent; + + QTest::addRow("function-return") << file1Qml << 33 << 41 << firstResult << outOfOne << file1Qml + << 7 << positionAfterOneIndent; + + QTest::addRow("void-function") + << file1Qml << 36 << 17 << firstResult << outOfOne << noResultExpected << -1 << -1; + + QTest::addRow("rectangle-property") << file1Qml << 44 << 31 << firstResult << outOfOne + << "TODO: c++ type location" << -1 << -1; + + QTest::addRow("functionParameterICUsage") + << file1Qml << 34 << 16 << firstResult << outOfOne << file1Qml << 7 << 15; + + QTest::addRow("ICBindingUsage") + << file1Qml << 47 << 21 << firstResult << outOfOne << file1Qml << 7 << 15; + QTest::addRow("ICBindingUsage2") + << file1Qml << 49 << 11 << firstResult << outOfOne << file1Qml << 7 << 15; + QTest::addRow("ICBindingUsage3") + << file1Qml << 52 << 17 << firstResult << outOfOne << file1Qml << 7 << 15; +} + +void tst_qmlls_utils::findTypeDefinitionFromLocation() +{ + QFETCH(QString, filePath); + QFETCH(int, line); + QFETCH(int, character); + QFETCH(int, resultIndex); + QFETCH(int, expectedItemsCount); + QFETCH(QString, expectedFilePath); + QFETCH(int, expectedLine); + QFETCH(int, expectedCharacter); + + if (expectedLine == -1) + expectedLine = line; + if (expectedCharacter == -1) + expectedCharacter = character; + + // they all start at 1. + Q_ASSERT(line > 0); + Q_ASSERT(character > 0); + Q_ASSERT(expectedLine > 0); + Q_ASSERT(expectedCharacter > 0); + + auto [env, file] = createEnvironmentAndLoadFile(filePath); + + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1); + + QCOMPARE(locations.size(), expectedItemsCount); + + auto base = QQmlLSUtils::findTypeDefinitionOf(locations[resultIndex].domItem); + + // if expectedFilePath is empty, we probably just want to make sure that it does + // not crash + if (expectedFilePath == noResultExpected) { + QVERIFY(!base); + return; + } + + QEXPECT_FAIL("findIntProperty", "Builtins not supported yet", Abort); + QEXPECT_FAIL("function-parameter-builtin", "Base types defined in C++ are not supported yet", + Abort); + QEXPECT_FAIL("colorBinding", "Types from C++ bases not supported yet", Abort); + QEXPECT_FAIL("rectangle-property", "Types from C++ bases not supported yet", Abort); + QEXPECT_FAIL("icBase", "Base types defined in C++ are not supported yet", Abort); + QVERIFY(base); + + auto fileObject = + locations[resultIndex].domItem.goToFile(base->filename).as<QQmlJS::Dom::QmlFile>(); + + // print some debug message when failing, instead of using QVERIFY2 + // (printing the type every time takes a lot of time). + if constexpr (enable_debug_output) { + if (!fileObject) + qDebug() << "Could not find the file" << base->filename << "in the Dom."; + } + + QVERIFY(fileObject); + QCOMPARE(base->filename, expectedFilePath); + QCOMPARE(fileObject->canonicalFilePath(), expectedFilePath); + + QCOMPARE(base->sourceLocation.startLine, quint32(expectedLine)); + QCOMPARE(base->sourceLocation.startColumn, quint32(expectedCharacter)); +} + +void tst_qmlls_utils::findLocationOfItem_data() +{ + QTest::addColumn<QString>("filePath"); + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + QTest::addColumn<int>("expectedLine"); + QTest::addColumn<int>("expectedCharacter"); + + const QString file1Qml = testFile(u"file1.qml"_s); + + QTest::addRow("root-element") << file1Qml << 6 << 2 << -1 << 1; + + QTest::addRow("property-a") << file1Qml << 9 << 18 << -1 << positionAfterOneIndent; + QTest::addRow("property-a2") << file1Qml << 9 << 10 << -1 << positionAfterOneIndent; + QTest::addRow("nested-C") << file1Qml << 20 << 9 << -1 << -1; + QTest::addRow("nested-C2") << file1Qml << 23 << 13 << -1 << -1; + QTest::addRow("D") << file1Qml << 17 << 33 << -1 << 32; + QTest::addRow("property-d-var-type") << file1Qml << 12 << 15 << -1 << 14; + + QTest::addRow("import") << file1Qml << 4 << 6 << -1 << 1; +} + +void tst_qmlls_utils::findLocationOfItem() +{ + QFETCH(QString, filePath); + QFETCH(int, line); + QFETCH(int, character); + QFETCH(int, expectedLine); + QFETCH(int, expectedCharacter); + + if (expectedLine == -1) + expectedLine = line; + if (expectedCharacter == -1) + expectedCharacter = character; + + // they all start at 1. + Q_ASSERT(line > 0); + Q_ASSERT(character > 0); + Q_ASSERT(expectedLine > 0); + Q_ASSERT(expectedCharacter > 0); + + auto [env, file] = createEnvironmentAndLoadFile(filePath); + + // grab item using already tested QQmlLSUtils::findLastItemsContaining + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1); + QCOMPARE(locations.size(), 1); + + // once the item is grabbed, make sure its line/character position can be obtained back + auto t = QQmlJS::Dom::FileLocations::treeOf(locations.front().domItem); + + QCOMPARE(t->info().fullRegion.startLine, quint32(expectedLine)); + QCOMPARE(t->info().fullRegion.startColumn, quint32(expectedCharacter)); +} + +void tst_qmlls_utils::findBaseObject_data() +{ + QTest::addColumn<QString>("filePath"); + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + // to avoid mixing up the types (because they are all called Item or QQuickItem eitherway) + // mark them with properties and detect right types by their marker property, + // usually called (in<Filename>DotQml or in<Inline component Name>) + QTest::addColumn<QSet<QString>>("expectedPropertyName"); + // because types inherit properties, make sure that derived type properties are not in the base + // type, to correctly detect mixups between types and their base types + QTest::addColumn<QSet<QString>>("unExpectedPropertyName"); + + // (non) Expected Properties Names = ePN (nEPN) + // marker properties for the root object in BaseType.qml + QSet<QString> ePNBaseType; + ePNBaseType << u"inBaseTypeDotQml"_s; + QSet<QString> nEPNBaseType; + nEPNBaseType << u"inTypeDotQml"_s; + + // marker properties for the root object in Type.qml + QSet<QString> ePNType; + ePNType << u"inBaseTypeDotQml"_s << u"inTypeDotQml"_s; + QSet<QString> nEPNType; + + // marker properties for QQuickItem (e.g. the base of "Item") + QSet<QString> ePNQQuickItem; + QSet<QString> nEPNQQuickItem; + nEPNQQuickItem << u"inBaseTypeDotQml"_s << u"inTypeDotQml"_s; + + // marker properties for MyInlineComponent + QSet<QString> ePNMyInlineComponent; + QSet<QString> nEPNMyInlineComponent; + ePNMyInlineComponent << u"inBaseTypeDotQml"_s << u"inTypeDotQml"_s << u"inMyInlineComponent"_s; + + // marker properties for MyNestedInlineComponent + const QSet<QString> ePNMyNestedInlineComponent{ u"inMyNestedInlineComponent"_s }; + const QSet<QString> nEPNMyNestedInlineComponent{ u"inBaseTypeDotQml"_s, u"inTypeDotQml"_s, + u"inMyInlineComponent"_s }; + + // marker properties for MyBaseInlineComponent + const QSet<QString> ePNMyBaseInlineComponent{ u"inBaseTypeDotQml"_s }; + const QSet<QString> nEPNMyBaseInlineComponent{ u"inTypeDotQml"_s, u"inMyInlineComponent"_s }; + + const int rootElementDefLine = 6; + QTest::addRow("root-element") << testFile(u"Type.qml"_s) << rootElementDefLine << 5 + << ePNQQuickItem << nEPNQQuickItem; + QTest::addRow("root-element-from-id") << testFile(u"Type.qml"_s) << rootElementDefLine + 1 << 12 + << ePNBaseType << nEPNBaseType; + + const int myInlineComponentDefLine = 10; + // on the component name: go to BaseType + QTest::addRow("ic-name") << testFile(u"Type.qml"_s) << myInlineComponentDefLine << 26 + << ePNBaseType << nEPNBaseType; + // on the "BaseType" type: go to QQuickitem (base type of BaseType). + QTest::addRow("ic-basetypename") << testFile(u"Type.qml"_s) << myInlineComponentDefLine << 37 + << ePNQQuickItem << nEPNQQuickItem; + QTest::addRow("ic-from-id") << testFile(u"Type.qml"_s) << myInlineComponentDefLine + 1 << 19 + << ePNBaseType << nEPNBaseType; + + const int inlineTypeDefLine = 15; + QTest::addRow("inline") << testFile(u"Type.qml"_s) << inlineTypeDefLine << 23 << ePNQQuickItem + << nEPNQQuickItem; + QTest::addRow("inline2") << testFile(u"Type.qml"_s) << inlineTypeDefLine << 38 << ePNQQuickItem + << nEPNQQuickItem; + QTest::addRow("inline3") << testFile(u"Type.qml"_s) << inlineTypeDefLine << 15 << ePNQQuickItem + << nEPNQQuickItem; + QTest::addRow("inline-from-id") << testFile(u"Type.qml"_s) << inlineTypeDefLine + 1 << 24 + << ePNBaseType << nEPNBaseType; + + const int inlineIcDefLine = 23; + QTest::addRow("inline-ic") << testFile(u"Type.qml"_s) << inlineIcDefLine << 38 + << ePNMyBaseInlineComponent << nEPNMyBaseInlineComponent; + QTest::addRow("inline-ic-from-id") << testFile(u"Type.qml"_s) << inlineIcDefLine + 1 << 28 + << ePNMyBaseInlineComponent << nEPNMyBaseInlineComponent; + + const int inlineNestedIcDefLine = 27; + QTest::addRow("inline-ic2") << testFile(u"Type.qml"_s) << inlineNestedIcDefLine << 46 + << ePNMyNestedInlineComponent << nEPNMyNestedInlineComponent; + QTest::addRow("inline-ic2-from-id") + << testFile(u"Type.qml"_s) << inlineNestedIcDefLine + 1 << 23 + << ePNMyNestedInlineComponent << nEPNMyNestedInlineComponent; +} + +void tst_qmlls_utils::findBaseObject() +{ + const QByteArray failOnInlineComponentsMessage = + "The Dom cannot resolve inline components from the basetype yet."; + + QFETCH(QString, filePath); + QFETCH(int, line); + QFETCH(int, character); + QFETCH(QSet<QString>, expectedPropertyName); + QFETCH(QSet<QString>, unExpectedPropertyName); + + // they all start at 1. + Q_ASSERT(line > 0); + Q_ASSERT(character > 0); + + auto [env, file] = createEnvironmentAndLoadFile(filePath); + + // grab item using already tested QQmlLSUtils::findLastItemsContaining + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1); + if constexpr (enable_debug_output) { + if (locations.size() > 1) { + for (auto &x : locations) + qDebug() << x.domItem.toString(); + } + } + QCOMPARE(locations.size(), 1); + + auto typeLocation = QQmlLSUtils::findTypeDefinitionOf(locations.front().domItem); + QEXPECT_FAIL("inline-ic", failOnInlineComponentsMessage, Abort); + QEXPECT_FAIL("inline-ic2", failOnInlineComponentsMessage, Abort); + QVERIFY(typeLocation); + QQmlJS::Dom::DomItem type = QQmlLSUtils::sourceLocationToDomItem( + locations.front().domItem.goToFile(typeLocation->filename), + typeLocation->sourceLocation); + auto base = QQmlLSUtils::baseObject(type); + + if constexpr (enable_debug_output) { + if (!base) + qDebug() << u"Could not find the base of type "_s << type << u" from item:\n"_s + << locations.front().domItem.toString(); + } + + QEXPECT_FAIL("inline-ic-from-id", failOnInlineComponentsMessage, Abort); + QEXPECT_FAIL("inline-ic2-from-id", failOnInlineComponentsMessage, Abort); + QVERIFY(base); + + const QSet<QString> propertyDefs = base.field(QQmlJS::Dom::Fields::propertyDefs).keys(); + expectedPropertyName.subtract(propertyDefs); + QVERIFY2(expectedPropertyName.empty(), + u"Incorrect baseType found: it is missing following marker properties: "_s + .append(printSet(expectedPropertyName)) + .toLatin1()); + unExpectedPropertyName.intersect(propertyDefs); + QVERIFY2(unExpectedPropertyName.empty(), + u"Incorrect baseType found: it has an unexpected marker properties: "_s + .append(printSet(unExpectedPropertyName)) + .toLatin1()); +} + +/*! \internal + \brief Wrapper for findUsages data. +*/ +struct UsageData +{ + QString testFileName; + QQmlLSUtils::Usages expectedUsages; +}; + +void tst_qmlls_utils::findUsages_data() +{ + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + QTest::addColumn<UsageData>("data"); + + const auto readFileContent = [](const QString &testFileName) { + QFile file(testFileName); + if (file.open(QIODeviceBase::ReadOnly)) + return QString::fromUtf8(file.readAll()); + return QString{}; + }; + + const auto makeUsages = [](const QString &fileName, QList<QQmlLSUtils::Location> &locations) { + UsageData data; + std::sort(locations.begin(), locations.end()); + data.expectedUsages = { locations, {} }; + data.testFileName = fileName; + return data; + }; + + { + QList<QQmlLSUtils::Location> expectedUsages; + const auto testFileName = testFile("findUsages/jsIdentifier/jsIdentifier.qml"); + const auto testFileContent = readFileContent(testFileName); + { + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 13, + strlen("sum")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 13, + strlen("sum")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 19, + strlen("sum")); + const auto sumUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findSumFromDeclaration") << 8 << 13 << sumUsages; + QTest::addRow("findSumFromUsage") << 10 << 20 << sumUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 17, + strlen("i")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 24, + strlen("i")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 32, + strlen("i")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 36, + strlen("i")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 25, + strlen("i")); + const auto iUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findIFromDeclaration") << 9 << 17 << iUsages; + QTest::addRow("findIFromUsage") << 9 << 24 << iUsages; + QTest::addRow("findIFromUsage2") << 10 << 25 << iUsages; + } + } + { + const auto testFileName = testFile("findUsages/property/property.qml"); + const auto otherFile = testFile("findUsages/property/PropertyFromAnotherFile.qml"); + const auto testFileContent = readFileContent(testFileName); + const auto otherFileContent = readFileContent(otherFile); + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 18, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 13, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 29, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 9, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 21, 9, + strlen("helloPropertyChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 5, + strlen("onHelloPropertyChanged")); + const auto helloPropertyUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findPropertyFromDeclaration") << 8 << 18 << helloPropertyUsages; + QTest::addRow("findPropertyFromUsage") << 13 << 13 << helloPropertyUsages; + QTest::addRow("findPropertyFromUsage2") << 13 << 29 << helloPropertyUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 36, 20, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 38, 25, + strlen("helloProperty")); + const auto subItemHelloPropertyUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findPropertyFromDeclarationInSubItem") << 38 << 25 << subItemHelloPropertyUsages; + QTest::addRow("findPropertyFromUsageInSubItem") << 36 << 20 << subItemHelloPropertyUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 22, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 29, 20, + strlen("helloProperty")); + const auto ICHelloPropertyUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findPropertyFromDeclarationInIC") << 27 << 22 << ICHelloPropertyUsages; + QTest::addRow("findPropertyFromUsageInIC") << 29 << 20 << ICHelloPropertyUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(otherFile, otherFileContent, 4, 18, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 42, 9, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 44, 20, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 46, 9, + strlen("OnHelloPropertyChanged")); + const auto helloPropertyUsages = makeUsages(testFileName, expectedUsages); + + QTest::addRow("findPropertyFromOtherFile") << 42 << 13 << helloPropertyUsages; + } + } + { + const auto testFileName = testFile("findUsages/propertyInNested/propertyInNested.qml"); + const auto testFileContent = readFileContent(testFileName); + + const auto componentFileName = + testFile("findUsages/propertyInNested/NestedComponentInFile.qml"); + const auto componentFileContent = readFileContent(componentFileName); + + const auto componentFileName3 = + testFile("findUsages/propertyInNested/NestedComponentInFile3.qml"); + const auto componentFileContent3 = readFileContent(componentFileName); + + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 7, 18, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 31, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 37, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 43, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 36, 49, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 42, 26, + strlen("p2")); + const auto p2Usages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findPropertyFromDeclaration2") << 7 << 18 << p2Usages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 29, 13, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 32, 17, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 17, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 17, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 17, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 36, 17, + strlen("myNested")); + const auto nestedUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findIdFromUsage") << 36 << 20 << nestedUsages; + QTest::addRow("findIdFromDefinition") << 29 << 17 << nestedUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 35, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 32, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 36, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 16, 9, + strlen("inner")); + const auto nestedComponent3Usages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findPropertyFromUsageInFieldMemberExpression") + << 36 << 34 << nestedComponent3Usages; + + QTest::addRow("findFieldMemberExpressionUsageFromPropertyDefinition") + << 14 << 38 << nestedComponent3Usages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(componentFileName, componentFileContent, + 4, 37, strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 50, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 52, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 53, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 54, 32, + strlen("inner")); + const auto nestedComponent3Usages = makeUsages(testFileName, expectedUsages); + const auto nestedComponent3UsagesFromOtherFile = + makeUsages(componentFileName, expectedUsages); + QTest::addRow("findPropertyFromUsageInFieldMemberExpressionFromOtherFile") + << 50 << 33 << nestedComponent3Usages; + + QTest::addRow("findFieldMemberExpressionUsageFromPropertyDefinitionFromOtherFile") + << 4 << 38 << nestedComponent3UsagesFromOtherFile; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 38, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 22, + strlen("p2")); + const auto nestedComponent3P2Usages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findProperty2FromUsageInFieldMemberExpression") + << 35 << 39 << nestedComponent3P2Usages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(componentFileName3, componentFileContent3, + 5, 18, strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 53, 44, + strlen("p2")); + const auto nestedComponent3P2Usages = makeUsages(testFileName, expectedUsages); + const auto nestedComponent3P2UsagesFromOtherFile = makeUsages(componentFileName3, expectedUsages); + QTest::addRow("findProperty2FromUsageInFieldMemberExpressionInOtherFile") + << 53 << 44 << nestedComponent3P2Usages; + QTest::addRow("findProperty2FromUsageInDefinitionInOtherFile") + << 5 << 19 << nestedComponent3P2UsagesFromOtherFile; + } + } + { + QList<QQmlLSUtils::Location> expectedUsages; + const auto testFileName = testFile("findUsages/idUsages/idUsages.qml"); + const auto testFileContent = readFileContent(testFileName); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 7, 9, + strlen("rootId")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 11, 17, + strlen("rootId")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 12, 20, + strlen("rootId")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 17, 9, + strlen("rootId")); + const auto rootIdUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findIdFromUsageInChild") << 12 << 20 << rootIdUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + const auto testFileName = testFile("findUsages/recursive/recursive.qml"); + const auto testFileContent = readFileContent(testFileName); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 14, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 24, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 34, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 51, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 68, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 12, 20, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 15, 34, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 19, 27, + strlen("recursive")); + const auto recursiveUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findFunctionUsage") << 10 << 30 << recursiveUsages; + QTest::addRow("findFunctionUsage2") << 12 << 24 << recursiveUsages; + QTest::addRow("findQualifiedFunctionUsage") << 19 << 31 << recursiveUsages; + QTest::addRow("findFunctionUsageFromDefinition") << 8 << 17 << recursiveUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + const auto testFileName = testFile("findUsages/recursive/recursive.qml"); + const auto testFileContent = readFileContent(testFileName); + const auto otherFileName = testFile("findUsages/recursive/RecursiveInOtherFile.qml"); + const auto otherFileContent = readFileContent(otherFileName); + + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 61, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 4, 14, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 6, 24, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 6, 34, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 6, 51, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 6, 68, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 8, 20, + strlen("recursive")); + + const auto recursiveUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findFunctionUsageFromOtherFile") << 27 << 64 << recursiveUsages; + const auto recursiveUsagesFromOtherFile = makeUsages(otherFileName, expectedUsages); + QTest::addRow("findFunctionUsageFromSameFile") << 6 << 39 << recursiveUsagesFromOtherFile; + QTest::addRow("findFunctionUsageFromDefinitionInOtherFile") + << 4 << 14 << recursiveUsagesFromOtherFile; + } + { + const auto testFileName = testFile("findUsages/signalsAndHandlers/signalsAndHandlers.qml"); + const auto testFileContent = readFileContent(testFileName); + + const auto otherFileName = testFile("findUsages/signalsAndHandlers/widthChangedInAnotherFile.qml"); + const auto otherFileContent = readFileContent(otherFileName); + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 12, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 11, 9, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 13, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 17, 17, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 21, 9, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 39, 5, + strlen("onHelloSignal")); + const auto helloSignalUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findQmlSignalUsageFromDefinition") << 8 << 17 << helloSignalUsages; + QTest::addRow("findQmlSignalUsageFromUsage") << 13 << 17 << helloSignalUsages; + QTest::addRow("findQmlSignalUsageFromHandler") << 39 << 11 << helloSignalUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 5, 5, + strlen("onWidthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 13, + strlen("widthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 17, + strlen("widthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 28, 20, + strlen("widthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 20, + strlen("widthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 13, + strlen("widthChanged")); + const auto widthChangedUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findCppSignalUsageFromUsage") << 27 << 23 << widthChangedUsages; + QTest::addRow("findCppSignalUsageFromQualifiedUsage") << 28 << 23 << widthChangedUsages; + QTest::addRow("findCppSignalUsageFromQualifiedUsage2") << 34 << 24 << widthChangedUsages; + } + } + { + const auto testFileName = testFile("findUsages/binding/binding.qml"); + const auto testFileContent = readFileContent(testFileName); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 18, + strlen("helloPropertyBinding")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 5, + strlen("helloPropertyBinding")); + const auto helloPropertyBindingUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findBindingUsagesFromDefinition") << 9 << 21 << helloPropertyBindingUsages; + QTest::addRow("findBindingUsagesFromBinding") << 10 << 19 << helloPropertyBindingUsages; + } + { + const auto testFileName = testFile("findUsages/signalsAndHandlers/signalAndHandlers2.qml"); + const auto testFileContent = readFileContent(testFileName); + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 7, 14, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 20, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 29, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 15, 24, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 17, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 24, 24, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 21, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 19, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 42, 29, + strlen("myHelloHandler")); + const auto myHelloHandlerUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findJSMethodFromUsageInBinding") << 8 << 27 << myHelloHandlerUsages; + QTest::addRow("findJSMethodFromDefinition") << 7 << 22 << myHelloHandlerUsages; + QTest::addRow("findJSMethodFromDefinition2") << 7 << 9 << myHelloHandlerUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 18, + strlen("checkHandlers")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 5, + strlen("onCheckHandlersChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 17, 9, + strlen("checkHandlersChanged")); + const auto checkHandlersUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findQmlPropertyHandlerFromDefinition") << 13 << 18 << checkHandlersUsages; + QTest::addRow("findQmlPropertyHandlerFromHandler") << 14 << 5 << checkHandlersUsages; + QTest::addRow("findQmlPropertyHandlerFromSignalCall") << 17 << 9 << checkHandlersUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 15, 5, + strlen("onChildrenChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 18, 9, + strlen("childrenChanged")); + const auto checkCppHandlersUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findCppPropertyHandlerFromHandler") << 15 << 5 << checkCppHandlersUsages; + QTest::addRow("findCppPropertyHandlerFromSignalCall") << 18 << 9 << checkCppHandlersUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 18, + strlen("_")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 5, + strlen("on_Changed")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 9, + strlen("_Changed")); + const auto checkHandlersUsages2 = makeUsages(testFileName, expectedUsages); + QTest::addRow("findQmlPropertyHandler2FromDefinition") << 20 << 18 << checkHandlersUsages2; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 21, 18, + strlen("______42")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 24, 5, + strlen("on______42Changed")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 28, 9, + strlen("______42Changed")); + const auto checkHandlersUsages3 = makeUsages(testFileName, expectedUsages); + QTest::addRow("findQmlPropertyHandler3FromDefinition") << 21 << 18 << checkHandlersUsages3; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 22, 18, + strlen("_123a")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 5, + strlen("on_123AChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 29, 9, + strlen("_123aChanged")); + const auto checkHandlersUsages4 = makeUsages(testFileName, expectedUsages); + QTest::addRow("findQmlPropertyHandler4FromDefinition") << 22 << 18 << checkHandlersUsages4; + } + } + { + QList<QQmlLSUtils::Location> expectedUsages; + const auto testFileName = testFile("findUsages/connections/connections.qml"); + const auto testFileContent = readFileContent(testFileName); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 9, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 17, 23, + strlen("clicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 15, + strlen("clicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 16, 22, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 15, + strlen("clicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 18, 23, + strlen("clicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 28, 9, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 15, + strlen("clicked")); + const auto signalInConnection = makeUsages(testFileName, expectedUsages); + QTest::addRow("findSignalsInConnectionFromSignal") << 33 << 15 << signalInConnection; + QTest::addRow("findSignalsInConnectionFromHandler") << 9 << 9 << signalInConnection; + QTest::addRow("findSignalsInConnectionFromFunction") << 16 << 22 << signalInConnection; + } + { + const auto testFileName = + testFile("findUsages/parametersAndDeconstruction/parametersAndDeconstruction.qml"); + const auto testFileContent = readFileContent(testFileName); + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 30, + strlen("a")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 16, + strlen("a")); + const auto aParamUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findMethodParameterA") << 9 << 16 << aParamUsages; + QTest::addRow("findMethodParameterAFromUsage") << 8 << 30 << aParamUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 50, + strlen("x")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 28, + strlen("x")); + const auto xParamUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findMethodParameterXDeconstructed") << 8 << 50 << xParamUsages; + QTest::addRow("findMethodParameterXDeconstructedFromUsage") << 9 << 28 << xParamUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 53, + strlen("y")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 32, + strlen("y")); + const auto yParamUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findMethodParameterYDeconstructed") << 8 << 53 << yParamUsages; + QTest::addRow("findMethodParameterYDeconstructedFromUsage") << 9 << 32 << yParamUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 59, + strlen("z")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 36, + strlen("z")); + const auto zParamUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("findMethodParameterZDeconstructed") << 8 << 59 << zParamUsages; + QTest::addRow("findMethodParameterZDeconstructedFromUsage") << 9 << 36 << zParamUsages; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 14, + strlen("a")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 17, + strlen("a")); + const auto deconstructedAUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("deconstructed") << 14 << 17 << deconstructedAUsages; + QTest::addRow("deconstructedFromDefinition") << 13 << 14 << deconstructedAUsages; + } + } + { + const auto testFileName = testFile("findUsages/groupPropertyUsage/groupPropertyUsage.qml"); + const auto testFileContent = readFileContent(testFileName); + const auto otherFileName = testFile("findUsages/groupPropertyUsage/fontFamilyUsage.qml"); + const auto otherFileContent = readFileContent(otherFileName); + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 5, 34, + strlen("family")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 17, + strlen("family")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 35, + strlen("family")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 10, + strlen("family")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 48, + strlen("family")); + const auto groupPropertyUsages1 = makeUsages(testFileName, expectedUsages); + QTest::addRow("groupPropertyUsages1") << 14 << 17 << groupPropertyUsages1; + const auto groupPropertyUsages1FromOtherFile = + makeUsages(otherFileName, expectedUsages); + QTest::addRow("groupPropertyUsages1FromOtherFile") + << 5 << 37 << groupPropertyUsages1FromOtherFile; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 5, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 24, 5, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 12, 13, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 30, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 32, 41, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 43, + strlen("font")); + const auto groupPropertyUsages2 = makeUsages(testFileName, expectedUsages); + QTest::addRow("groupPropertyUsages2") << 23 << 5 << groupPropertyUsages2; + } + } + { + const auto testFileName = + testFile("findUsages/attachedPropertyUsage/attachedPropertyUsage.qml"); + const auto testFileContent = readFileContent(testFileName); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 5, + strlen("Keys")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 12, 25, + strlen("Keys")); + const auto attachedPropertyUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("attachedPropertyUsages") << 12 << 25 << attachedPropertyUsages; + } + { + const auto testFileName = testFile("findUsages/inlineComponents/inlineComponents.qml"); + const auto testFileContent = readFileContent(testFileName); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 22, + strlen("foo")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 44, + strlen("foo")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 27, + strlen("foo")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 20, + strlen("foo")); + const auto inlineUsages = makeUsages(testFileName, expectedUsages); + QTest::addRow("inlineUsagesFromProperty") << 9 << 22 << inlineUsages; + QTest::addRow("inlineUsagesFromUsageOfBaseProperty") << 14 << 27 << inlineUsages; + QTest::addRow("inlineUsagesFromJsScope") << 20 << 20 << inlineUsages; + } + { + const auto testFileName = testFile("findUsages/propertyChanges/propertyChanges.qml"); + const auto testFileContent = readFileContent(testFileName); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 9, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 16, 21, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 19, 25, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 17, + strlen("onClicked")); + const auto propertyChanges = makeUsages(testFileName, expectedUsages); + QTest::addRow("propertyChanges1") << 16 << 21 << propertyChanges; + } + { + const auto testFileName = testFile("findUsages/bindings/bindings.qml"); + const auto testFileContent = readFileContent(testFileName); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 11, 23, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 27, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 21, 27, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 19, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 41, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 17, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 20, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 23, + strlen("\"patronChanged\"")); + const auto bindings = makeUsages(testFileName, expectedUsages); + QTest::addRow("propertyInBindingsFromDecl") << 11 << 23 << bindings; + QTest::addRow("generalizedGroupPropertyBindings") << 27 << 19 << bindings; + } + { + const auto testFileName = testFile("findUsages/enums/Enums.qml"); + const auto testFileContent = readFileContent(testFileName); + const auto otherFileName = testFile("findUsages/enums/EnumsFromAnotherFile.qml"); + const auto otherFileContent = readFileContent(otherFileName); + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 9, + strlen("Patron")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 22, 35, + strlen("Patron")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 34, + strlen("Patron")); + const auto enums = makeUsages(testFileName, expectedUsages); + QTest::addRow("enumValuesFromDeclaration") << 9 << 9 << enums; + QTest::addRow("enumValuesFromUsage") << 22 << 35 << enums; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 10, + strlen("Cats")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 22, 30, + strlen("Cats")); + const auto enums = makeUsages(testFileName, expectedUsages); + QTest::addRow("enumNameFromDeclaration") << 8 << 10 << enums; + QTest::addRow("enumNameFromUsage") << 22 << 30 << enums; + } + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 26, 46, + strlen("FromAnotherUniverse")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 4, 68, + strlen("FromAnotherUniverse")); + const auto enums = makeUsages(testFileName, expectedUsages); + QTest::addRow("enumNameFromDeclarationInOtherFile") << 26 << 50 << enums; + const auto enumsFromOtherFile = makeUsages(otherFileName, expectedUsages); + QTest::addRow("enumNameFromUsageFromOtherFile") << 4 << 81 << enumsFromOtherFile; + } + } + { + const auto testFileName = testFile("findUsages/inlineComponents/inlineComponents2.qml"); + const auto testFileContent = readFileContent(testFileName); + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 4, 15, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 5, 5, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 5, 12, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 5, 19, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 6, 19, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 6, 26, + strlen("MyIC")); + const auto inlineComponents = makeUsages(testFileName, expectedUsages); + QTest::addRow("findICUsagesFromDefinition") << 4 << 16 << inlineComponents; + QTest::addRow("findICUsagesFromDefinition2") << 4 << 9 << inlineComponents; + QTest::addRow("findICUsagesFromUsage") << 5 << 19 << inlineComponents; + QTest::addRow("findICUsagesFromTypeUsage") << 6 << 19 << inlineComponents; + } + } + { + const auto testFileName = testFile("findUsages/inlineComponents/inlineComponents.qml"); + const auto testFileContent = readFileContent(testFileName); + const auto providerFileName = + testFile("findUsages/inlineComponents/InlineComponentProvider.qml"); + const auto providerFileContent = readFileContent(providerFileName); + { + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(providerFileName, providerFileContent, 4, + 15, strlen("IC1")); + expectedUsages << QQmlLSUtils::Location::from(providerFileName, providerFileContent, 5, + 36, strlen("IC1")); + expectedUsages << QQmlLSUtils::Location::from(providerFileName, providerFileContent, 7, + 5, strlen("IC1")); + expectedUsages << QQmlLSUtils::Location::from(providerFileName, providerFileContent, 17, + 13, strlen("IC1")); + + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 38, + strlen("IC1")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 84, + strlen("IC1")); + + { + const auto usagesForTestFile = makeUsages(testFileName, expectedUsages); + QTest::addRow("findICUsagesFromTypeAnnotationInOtherFiles") + << 25 << 39 << usagesForTestFile; + QTest::addRow("findICUsagesFromInstantiationInOtherFiles") + << 25 << 84 << usagesForTestFile; + } + + { + const auto usagesInProviderFile = makeUsages(providerFileName, expectedUsages); + + QTest::addRow("findICUsagesFromDefinitionInOtherFiles") + << 4 << 16 << usagesInProviderFile; + QTest::addRow("findICUsagesFromInstantiationInOtherFiles2") + << 17 << 14 << usagesInProviderFile; + } + } + } +} + +void tst_qmlls_utils::findUsages() +{ + QFETCH(int, line); + QFETCH(int, character); + QFETCH(UsageData, data); + + { + auto usagesInFilename = data.expectedUsages.usagesInFilename(); + QVERIFY(std::is_sorted(usagesInFilename.begin(), usagesInFilename.end())); + auto usagesInFile = data.expectedUsages.usagesInFile(); + QVERIFY(std::is_sorted(usagesInFile.begin(), usagesInFile.end())); + } + + auto [env, file] = createEnvironmentAndLoadFile(data.testFileName); + + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1); + + if constexpr (enable_debug_output) { + if (locations.size() > 1) { + for (auto &x : locations) + qDebug() << x.domItem.toString(); + } + } + QCOMPARE(locations.size(), 1); + + auto usages = QQmlLSUtils::findUsagesOf(locations.front().domItem); + + if constexpr (enable_debug_output) { + if (usages != data.expectedUsages) { + qDebug() << "Got:\n"; + for (auto &x : usages.usagesInFile()) { + qDebug() << x.filename << "(" << x.sourceLocation.startLine << ", " + << x.sourceLocation.startColumn << "), " << x.sourceLocation.offset << "+" + << x.sourceLocation.length; + } + qDebug() << "with usages in filenames:" << usages.usagesInFilename(); + qDebug() << "But expected: \n"; + for (auto &x : data.expectedUsages.usagesInFile()) { + qDebug() << x.filename << "(" << x.sourceLocation.startLine << ", " + << x.sourceLocation.startColumn << "), " << x.sourceLocation.offset << "+" + << x.sourceLocation.length; + } + qDebug() << "with usages in filenames:" << data.expectedUsages.usagesInFilename(); + } + } + + QCOMPARE(usages, data.expectedUsages); +} + + +void tst_qmlls_utils::renameUsages_data() +{ + QTest::addColumn<QString>("filePath"); + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + QTest::addColumn<QString>("newName"); + QTest::addColumn<QQmlLSUtils::RenameUsages>("expectedRenames"); + QTest::addColumn<QString>("expectedError"); + + const QString testFileName = testFile(u"JSUsages.qml"_s); + const QString testFileNameFromAnotherFile = testFile(u"JSUsagesFromAnotherFile.qml"_s); + const QString testFileContent = readFileContent(testFileName); + const QString testFileFromAnotherFileContent = readFileContent(testFileNameFromAnotherFile); + + const QString noError; + const QQmlLSUtils::RenameUsages noRenames; + + QQmlLSUtils::RenameUsages methodFRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 72, 14, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 74, 24, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 74, 34, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 74, 51, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 74, 68, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 76, 20, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 79, 34, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 84, 27, strlen("recursive"), + u"newNameNewMe"_s), + }, + {} + }; + + QQmlLSUtils::RenameUsages JSIdentifierSumRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 8, 13, strlen("sum"), + u"sumsumsum123"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 10, 13, strlen("sum"), + u"sumsumsum123"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 10, 19, strlen("sum"), + u"sumsumsum123"_s), + }, + {} + }; + + QQmlLSUtils::RenameUsages qmlSignalRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 88, 12, + strlen("helloSignal"), u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 91, 9, strlen("helloSignal"), + u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 93, 13, + strlen("helloSignal"), u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 97, 17, + strlen("helloSignal"), u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 101, 9, + strlen("helloSignal"), u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 119, 5, + strlen("onHelloSignal"), u"onFinalSignal"_s), + }, + {} + }; + + QQmlLSUtils::RenameUsages helloPropertyRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 17, 18, + strlen("helloProperty"), u"freshPropertyName"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 24, 13, + strlen("helloProperty"), u"freshPropertyName"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 24, 29, + strlen("helloProperty"), u"freshPropertyName"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 65, 60, + strlen("helloProperty"), u"freshPropertyName"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 151, 9, + strlen("helloPropertyChanged"), + u"freshPropertyNameChanged"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 153, 5, + strlen("onHelloPropertyChanged"), + u"onFreshPropertyNameChanged"_s), + QQmlLSUtils::Edit::from(testFileNameFromAnotherFile, testFileFromAnotherFileContent, + 12, 16, strlen("helloProperty"), u"freshPropertyName"_s), + }, + {} + }; + + QQmlLSUtils::RenameUsages nestedComponentRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 42, 15, + strlen("NestedComponent"), u"SuperInlineComponent"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 61, 5, + strlen("NestedComponent"), u"SuperInlineComponent"_s), + }, + {} + }; + + QQmlLSUtils::RenameUsages myNestedIdRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 62, 13, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 65, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 66, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 67, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 68, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 69, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + }, + {} + }; + + const QString renameFileQml = testFile("renaming/main.qml"); + const QString renameFileQmlContent = readFileContent(renameFileQml); + const QQmlLSUtils::RenameUsages renameComponent1{ + { + QQmlLSUtils::Edit::from(renameFileQml, renameFileQmlContent, 4, 5, + strlen("RenameMe"), u"FreshNewComponentName"_s), + }, + { + { testFile("renaming/RenameMe.qml"), + testFile(u"renaming/FreshNewComponentName.qml"_s) }, + } + }; + const QQmlLSUtils::RenameUsages renameComponent2{ + { + QQmlLSUtils::Edit::from(renameFileQml, renameFileQmlContent, 5, 5, + strlen("RenameMe2"), u"AnotherOneThankYou"_s), + }, + { + { testFile("renaming/RenameMe2.ui.qml"), + testFile(u"renaming/AnotherOneThankYou.ui.qml"_s) }, + } + }; + const QQmlLSUtils::RenameUsages renameComponentNamedByQmldir{ + { + QQmlLSUtils::Edit::from(renameFileQml, renameFileQmlContent, 6, 5, + strlen("HelloWorld"), u"AnotherOneThankYou"_s), + }, + // make sure that the file itself does not get renamed + {} + }; + + const QString parserError = u"Invalid EcmaScript identifier!"_s; + + QTest::addRow("renameMethod") << testFileName << 72 << 19 << u"newNameNewMe"_s << methodFRename + << noError; + QTest::addRow("renameJSIdentifier") + << testFileName << 10 << 19 << u"sumsumsum123"_s << JSIdentifierSumRename << noError; + QTest::addRow("renameQmlSignal") + << testFileName << 93 << 19 << u"finalSignal"_s << qmlSignalRename << noError; + QTest::addRow("renameQmlSignalHandler") + << testFileName << 119 << 10 << u"onFinalSignal"_s << qmlSignalRename << noError; + + QTest::addRow("renameQmlProperty") + << testFileName << 17 << 20 << u"freshPropertyName"_s << helloPropertyRename << noError; + QTest::addRow("renameQmlPropertyChanged") + << testFileName << 151 << 18 << u"freshPropertyNameChanged"_s << helloPropertyRename + << noError; + QTest::addRow("renameQmlPropertyChangedHandler") + << testFileName << 153 << 22 << u"onFreshPropertyNameChanged"_s << helloPropertyRename + << noError; + + QTest::addRow("renameQmlObjectId") << testFileName << 65 << 21 << u"freshNewIdForMyNested"_s + << myNestedIdRename << noError; + + // rename forbidden stuff + QTest::addRow("renameCPPDefinedItem") << testFileName << 144 << 13 << u"onHelloWorld"_s + << noRenames << u"defined in non-QML files."_s; + QTest::addRow("renameFunctionKeyword") << testFileName << 8 << 10 << u"HelloWorld"_s + << noRenames << "Requested item cannot be renamed"; + QTest::addRow("invalidCharactersInIdentifier") + << testFileName << 12 << 22 << u"\""_s << noRenames << parserError; + QTest::addRow("invalidCharactersInIdentifier2") + << testFileName << 12 << 22 << u"hello world"_s << noRenames << parserError; + QTest::addRow("invalidCharactersInIdentifier3") + << testFileName << 12 << 22 << u"sum.sum.sum"_s << noRenames << parserError; + QTest::addRow("emptyIdentifier") + << testFileName << 12 << 22 << QString() << noRenames << parserError; + QTest::addRow("usingKeywordAsIdentifier") + << testFileName << 12 << 22 << u"function"_s << noRenames << parserError; + + QTest::addRow("changedSignalHandlerMissingOnChanged") + << testFileName << 134 << 9 << u"___"_s << noRenames + << u"Invalid name for a property changed handler identifier"_s; + QTest::addRow("changedSignalHandlerMissingChanged") + << testFileName << 134 << 9 << u"on___"_s << noRenames + << u"Invalid name for a property changed handler identifier"_s; + QTest::addRow("changedSignalHandlerMissingOn") + << testFileName << 134 << 9 << u"___Changed"_s << noRenames + << u"Invalid name for a property changed handler identifier"_s; + QTest::addRow("changedSignalHandlerTypoInChanged") + << testFileName << 134 << 9 << u"on___Chänged"_s << noRenames + << u"Invalid name for a property changed handler identifier"_s; + + QTest::addRow("signalHandlerMissingOn") + << testFileName << 119 << 10 << u"helloSuperSignal"_s << noRenames + << u"Invalid name for a signal handler identifier"_s; + QTest::addRow("signalHandlerMissingCapitalization") + << testFileName << 119 << 10 << u"onhelloSuperSignal"_s << noRenames + << u"Invalid name for a signal handler identifier"_s; + + QTest::addRow("JSIdentifierStartsWithNumber") + << testFileName << 67 << 13 << u"123"_s << noRenames << parserError; + + QTest::addRow("renameQmlFile") << testFile(u"renaming/main.qml"_s) << 4 << 9 + << u"FreshNewComponentName"_s << renameComponent1 << noError; + + QTest::addRow("renameUiQmlFile") << testFile(u"renaming/main.qml"_s) << 5 << 9 + << u"AnotherOneThankYou"_s << renameComponent2 << noError; + + QTest::addRow("renameQmlFileRenamedByQmldir") + << testFile(u"renaming/main.qml"_s) << 6 << 8 << u"AnotherOneThankYou"_s + << renameComponentNamedByQmldir << noError; +} + +void tst_qmlls_utils::renameUsages() +{ + // findAndRenameUsages() already tests if all usages will be renamed + // now test that the new name is correctly passed + QFETCH(QString, filePath); + QFETCH(int, line); + QFETCH(int, character); + QFETCH(QString, newName); + QFETCH(QQmlLSUtils::RenameUsages, expectedRenames); + QFETCH(QString, expectedError); + + { + const auto renameInFile = expectedRenames.renameInFile(); + QVERIFY(std::is_sorted(renameInFile.constBegin(), renameInFile.constEnd())); + const auto renameInFilename = expectedRenames.renameInFilename(); + QVERIFY(std::is_sorted(renameInFilename.begin(), renameInFilename.end())); + } + + auto [env, file] = createEnvironmentAndLoadFile(filePath); + + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1); + + if constexpr (enable_debug_output) { + if (locations.size() > 1) { + for (auto &x : locations) + qDebug() << x.domItem.toString(); + } + } + QCOMPARE(locations.size(), 1); + + if (auto errors = QQmlLSUtils::checkNameForRename(locations.front().domItem, newName)) { + if constexpr (enable_debug_output) { + if (expectedError.isEmpty()) + qDebug() << "Expected no error but got" << errors->message; + if (!errors->message.contains(expectedError)) + qDebug() << "Cannot find" << expectedError << "in" << errors->message; + } + QVERIFY(!expectedError.isEmpty()); + QVERIFY(errors->message.contains(expectedError)); + return; + } + auto edits = QQmlLSUtils::renameUsagesOf(locations.front().domItem, newName); + + if constexpr (enable_debug_output) { + if (edits != expectedRenames) { + qDebug() << "Got:\n"; + for (auto &x : edits.renameInFile()) { + qDebug() << x.replacement << x.location.filename << "(" + << x.location.sourceLocation.startLine << ", " + << x.location.sourceLocation.startColumn << "), " + << x.location.sourceLocation.offset << "+" + << x.location.sourceLocation.length; + } + qDebug() << "with renames in filenames:"; + for (auto &x : edits.renameInFilename()) { + qDebug() << x.oldFilename << "->" << x.newFilename; + } + qDebug() << "But expected: \n"; + for (auto &x : expectedRenames.renameInFile()) { + qDebug() << x.replacement << x.location.filename << "(" + << x.location.sourceLocation.startLine << ", " + << x.location.sourceLocation.startColumn << "), " + << x.location.sourceLocation.offset << "+" + << x.location.sourceLocation.length; + } + qDebug() << "with renames in filenames:"; + for (auto &x : expectedRenames.renameInFilename()) { + qDebug() << x.oldFilename << "->" << x.newFilename; + } + } + } + QCOMPARE(edits, expectedRenames); +} + +void tst_qmlls_utils::findDefinitionFromLocation_data() +{ + QTest::addColumn<QString>("filePath"); + // keep in mind that line and character are starting at 1! + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + + QTest::addColumn<QString>("expectedFilePath"); + // set to -1 when unchanged from above line and character. 0-based. + QTest::addColumn<int>("expectedLine"); + QTest::addColumn<int>("expectedCharacter"); + QTest::addColumn<size_t>("expectedLength"); + + const QString JSDefinitionsQml = testFile(u"JSDefinitions.qml"_s); + const QString BaseTypeQml = testFile(u"BaseType.qml"_s); + + QTest::addRow("JSIdentifierX") + << JSDefinitionsQml << 14 << 11 << JSDefinitionsQml << 13 << 13 << strlen("x"); + QTest::addRow("JSIdentifierX2") + << JSDefinitionsQml << 15 << 11 << JSDefinitionsQml << 13 << 13 << strlen("x"); + QTest::addRow("propertyI") << JSDefinitionsQml << 14 << 14 << JSDefinitionsQml << 9 << 18 + << strlen("i"); + QTest::addRow("qualifiedPropertyI") + << JSDefinitionsQml << 15 << 21 << JSDefinitionsQml << 9 << 18 << strlen("i"); + QTest::addRow("inlineComponentProperty") + << JSDefinitionsQml << 62 << 21 << JSDefinitionsQml << 54 << 22 << strlen("data"); + + QTest::addRow("parameterA") << JSDefinitionsQml << 10 << 16 << JSDefinitionsQml << 10 << 16 + << strlen("a"); + QTest::addRow("parameterAUsage") + << JSDefinitionsQml << 10 << 39 << JSDefinitionsQml << -1 << 16 << strlen("a"); + + QTest::addRow("parameterB") << JSDefinitionsQml << 10 << 28 << JSDefinitionsQml << 10 << 28 + << strlen("b"); + QTest::addRow("parameterBUsage") + << JSDefinitionsQml << 10 << 86 << JSDefinitionsQml << -1 << 28 << strlen("b"); + + QTest::addRow("comment") << JSDefinitionsQml << 10 << 21 << noResultExpected << -1 << -1 + << size_t{}; + + QTest::addRow("scopedX") << JSDefinitionsQml << 22 << 18 << JSDefinitionsQml << 21 << 17 + << strlen("scoped"); + QTest::addRow("scopedX2") << JSDefinitionsQml << 25 << 22 << JSDefinitionsQml << 21 << 17 + << strlen("scoped"); + QTest::addRow("scopedX3") << JSDefinitionsQml << 28 << 14 << JSDefinitionsQml << 19 << 13 + << strlen("scoped"); + + QTest::addRow("normalI") << JSDefinitionsQml << 22 << 23 << JSDefinitionsQml << 9 << 18 + << strlen("i"); + QTest::addRow("scopedI") << JSDefinitionsQml << 25 << 27 << JSDefinitionsQml << 24 << 32 + << strlen("i"); + + QTest::addRow("shadowingProperty") + << JSDefinitionsQml << 37 << 21 << JSDefinitionsQml << 34 << 22 << strlen("i"); + QTest::addRow("shadowingQualifiedProperty") + << JSDefinitionsQml << 37 << 35 << JSDefinitionsQml << 34 << 22 << strlen("i"); + QTest::addRow("shadowedProperty") + << JSDefinitionsQml << 37 << 49 << JSDefinitionsQml << 9 << 18 << strlen("i"); + + QTest::addRow("propertyInBinding") + << JSDefinitionsQml << 64 << 37 << JSDefinitionsQml << 9 << 18 << strlen("i"); + QTest::addRow("propertyInBinding2") + << JSDefinitionsQml << 65 << 38 << JSDefinitionsQml << 9 << 18 << strlen("i"); + QTest::addRow("propertyInBinding3") + << JSDefinitionsQml << 66 << 51 << JSDefinitionsQml << 9 << 18 << strlen("i"); + + QTest::addRow("propertyFromDifferentFile") + << JSDefinitionsQml << 72 << 20 << BaseTypeQml << 24 << 18 << strlen("helloProperty"); + + QTest::addRow("id") << JSDefinitionsQml << 15 << 17 << JSDefinitionsQml << 7 << 9 + << strlen("rootId"); + QTest::addRow("onId") << JSDefinitionsQml << 32 << 16 << JSDefinitionsQml << 32 << 13 + << strlen("nested"); + QTest::addRow("parentId") << JSDefinitionsQml << 37 << 44 << JSDefinitionsQml << 7 << 9 + << strlen("rootId"); + QTest::addRow("currentId") << JSDefinitionsQml << 37 << 30 << JSDefinitionsQml << 32 << 13 + << strlen("nested"); + QTest::addRow("inlineComponentId") + << JSDefinitionsQml << 56 << 35 << JSDefinitionsQml << 52 << 13 << strlen("helloIC"); + + QTest::addRow("recursiveFunction") + << JSDefinitionsQml << 39 << 28 << JSDefinitionsQml << 36 << 18 << strlen("f"); + QTest::addRow("recursiveFunction2") + << JSDefinitionsQml << 39 << 39 << JSDefinitionsQml << 36 << 18 << strlen("f"); + QTest::addRow("functionFromFunction") + << JSDefinitionsQml << 44 << 20 << JSDefinitionsQml << 36 << 18 << strlen("f"); + QTest::addRow("qualifiedFunctionName") + << JSDefinitionsQml << 48 << 23 << JSDefinitionsQml << 36 << 18 << strlen("f"); + + QTest::addRow("functionInParent") + << JSDefinitionsQml << 44 << 37 << JSDefinitionsQml << 18 << 14 << strlen("ffff"); + QTest::addRow("functionFromDifferentFile") + << JSDefinitionsQml << 72 << 47 << BaseTypeQml << 25 << 14 << strlen("helloFunction"); + QTest::addRow("componentFromFile") + << JSDefinitionsQml << 68 << 28 << BaseTypeQml << 6 << 1 << strlen("Item"); + QTest::addRow("inlineComponentFromDifferentFile") + << JSDefinitionsQml << 75 << 27 << BaseTypeQml << 9 << 38 << strlen("Item"); +} + +void tst_qmlls_utils::findDefinitionFromLocation() +{ + QFETCH(QString, filePath); + QFETCH(int, line); + QFETCH(int, character); + QFETCH(QString, expectedFilePath); + QFETCH(int, expectedLine); + QFETCH(int, expectedCharacter); + QFETCH(size_t, expectedLength); + + if (expectedLine == -1) + expectedLine = line; + if (expectedCharacter == -1) + expectedCharacter = character; + + // they all start at 1. + Q_ASSERT(line > 0); + Q_ASSERT(character > 0); + Q_ASSERT(expectedLine > 0); + Q_ASSERT(expectedCharacter > 0); + + auto [env, file] = createEnvironmentAndLoadFile(filePath); + + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1); + + QCOMPARE(locations.size(), 1); + + auto definition = QQmlLSUtils::findDefinitionOf(locations.front().domItem); + + // if expectedFilePath is empty, we probably just want to make sure that it does + // not crash + if (expectedFilePath == noResultExpected) { + QVERIFY(!definition); + return; + } + + QVERIFY(definition); + + QCOMPARE(definition->filename, expectedFilePath); + + QCOMPARE(definition->sourceLocation.startLine, quint32(expectedLine)); + QCOMPARE(definition->sourceLocation.startColumn, quint32(expectedCharacter)); + QCOMPARE(definition->sourceLocation.length, quint32(expectedLength)); +} + +void tst_qmlls_utils::resolveExpressionType_data() +{ + QTest::addColumn<QString>("filePath"); + // keep in mind that line and character are starting at 1! + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + QTest::addColumn<QQmlLSUtils::ResolveOptions>("resolveOption"); + QTest::addColumn<QString>("expectedFile"); + // startline of the owners definition + QTest::addColumn<int>("expectedLine"); + QTest::addColumn<QQmlLSUtils::IdentifierType>("expectedType"); + + using namespace QQmlLSUtils; + + const int noLine = -1; + const QString noFile; + + { + const QString JSDefinitionsQml = testFile(u"JSDefinitions.qml"_s); + const int parentLine = 6; + const int childLine = 31; + + QTest::addRow("id") << JSDefinitionsQml << 15 << 17 << ResolveOwnerType << JSDefinitionsQml + << parentLine << QmlObjectIdIdentifier; + QTest::addRow("childIddInChild") << JSDefinitionsQml << 37 << 30 << ResolveOwnerType + << JSDefinitionsQml << childLine << QmlObjectIdIdentifier; + QTest::addRow("parentIdInChild") << JSDefinitionsQml << 37 << 43 << ResolveOwnerType + << JSDefinitionsQml << parentLine << QmlObjectIdIdentifier; + + QTest::addRow("propertyI") << JSDefinitionsQml << 14 << 14 << ResolveOwnerType + << JSDefinitionsQml << parentLine << PropertyIdentifier; + QTest::addRow("qualifiedPropertyI") << JSDefinitionsQml << 15 << 21 << ResolveOwnerType + << JSDefinitionsQml << parentLine << PropertyIdentifier; + QTest::addRow("propertyIInChild") << JSDefinitionsQml << 37 << 21 << ResolveOwnerType + << JSDefinitionsQml << childLine << PropertyIdentifier; + QTest::addRow("qualifiedChildPropertyIInChild") + << JSDefinitionsQml << 37 << 35 << ResolveOwnerType << JSDefinitionsQml + << childLine << PropertyIdentifier; + QTest::addRow("qualifiedParentPropertyIInChild") + << JSDefinitionsQml << 37 << 49 << ResolveOwnerType << JSDefinitionsQml + << parentLine << PropertyIdentifier; + + QTest::addRow("childMethod") << JSDefinitionsQml << 48 << 23 << ResolveOwnerType + << JSDefinitionsQml << childLine << MethodIdentifier; + QTest::addRow("childMethod2") << JSDefinitionsQml << 44 << 20 << ResolveOwnerType + << JSDefinitionsQml << childLine << MethodIdentifier; + QTest::addRow("parentMethod") << JSDefinitionsQml << 14 << 9 << ResolveOwnerType + << JSDefinitionsQml << parentLine << MethodIdentifier; + } + + { + const QString bindingsOnDeferredQml = + testFile(u"resolveExpressionType/BindingsOnDeferred.qml"_s); + const QString qQuickControl = u"private/qquickcontrol_p.h"_s; + const QString qQuickKeysAttachedType = u"private/qquickitem_p.h"_s; + QTest::addRow("bindingOnId") << bindingsOnDeferredQml << 12 << 14 << ResolveOwnerType + << bindingsOnDeferredQml << 8 << QmlObjectIdIdentifier; + QTest::addRow("bindingOnQualifiedDeferredProperty") + << bindingsOnDeferredQml << 12 << 24 << ResolveOwnerType << qQuickControl << noLine + << PropertyIdentifier; + QTest::addRow("groupedPropertyBindingOnId") + << bindingsOnDeferredQml << 14 << 14 << ResolveOwnerType << bindingsOnDeferredQml + << 8 << QmlObjectIdIdentifier; + QTest::addRow("someDeferredProperty") + << bindingsOnDeferredQml << 15 << 22 << ResolveOwnerType << qQuickControl << noLine + << PropertyIdentifier; + } + + { + const QString JSUsagesQml = testFile(u"JSUsages.qml"_s); + const int rootLine = 6; + const int nestedComponent2Line = 46; + const int nestedComponent3Line = 51; + const int nestedComponent4Line = 57; + QTest::addRow("propertyAccess:inner.inner") << JSUsagesQml << 68 << 34 << ResolveOwnerType + << JSUsagesQml << nestedComponent2Line << PropertyIdentifier; + QTest::addRow("propertyAccess:inner.inner2") << JSUsagesQml << 69 << 34 << ResolveOwnerType + << JSUsagesQml << nestedComponent2Line << PropertyIdentifier; + QTest::addRow("propertyAccess:inner.inner.inner") + << JSUsagesQml << 69 << 40 << ResolveOwnerType << JSUsagesQml + << nestedComponent3Line << PropertyIdentifier; + QTest::addRow("propertyAccess:inner.inner.inner.p2") + << JSUsagesQml << 69 << 44 << ResolveOwnerType << JSUsagesQml + << nestedComponent4Line << PropertyIdentifier; + + QTest::addRow("propertyAccess:helloProperty") + << JSUsagesQml << 65 << 68 << ResolveOwnerType << JSUsagesQml << rootLine << PropertyIdentifier; + QTest::addRow("propertyAccess:nestedHelloProperty") + << JSUsagesQml << 65 << 46 << ResolveOwnerType << JSUsagesQml + << nestedComponent4Line << PropertyIdentifier; + } + + { + const QString derivedType = testFile(u"resolveExpressionType/DerivedType.qml"_s); + const QString derived2Type = testFile(u"resolveExpressionType/Derived2.qml"_s); + const QString baseType = testFile(u"resolveExpressionType/BaseType.qml"_s); + const QString qQuickValueTypes = u"private/qquickvaluetypes_p.h"_s; + const QString qQuickKeysAttachedType = u"private/qquickitem_p.h"_s; + + const int baseTypeLine = 6; + const int derivedTypeLine = 6; + const int keysLine = 29; + + QTest::addRow("ownerOfMethod") + << derivedType << 9 << 13 << ResolveOwnerType << baseType << baseTypeLine << MethodIdentifier; + QTest::addRow("ownerOfMethod2") + << derivedType << 15 << 33 << ResolveOwnerType << baseType << baseTypeLine << MethodIdentifier; + QTest::addRow("ownerOfQualifiedMethod") + << derivedType << 22 << 46 << ResolveOwnerType << baseType << baseTypeLine << MethodIdentifier; + + QTest::addRow("ownerOfProperty") + << derivedType << 10 << 22 << ResolveOwnerType << baseType << baseTypeLine << PropertyIdentifier; + QTest::addRow("ownerOfProperty2") + << derivedType << 16 << 37 << ResolveOwnerType << baseType << baseTypeLine << PropertyIdentifier; + QTest::addRow("ownerOfQualifiedProperty") + << derivedType << 23 << 46 << ResolveOwnerType << baseType << baseTypeLine << PropertyIdentifier; + + QTest::addRow("ownerOfOwnProperty") + << derivedType << 16 << 23 << ResolveOwnerType << derivedType << derivedTypeLine << PropertyIdentifier; + + QTest::addRow("ownerOfSignal") + << derivedType << 11 << 13 << ResolveOwnerType << baseType << baseTypeLine << SignalIdentifier; + QTest::addRow("ownerOfSignal2") + << derivedType << 18 << 37 << ResolveOwnerType << baseType << baseTypeLine << SignalIdentifier; + QTest::addRow("ownerOfSignalHandler") + << derivedType << 19 << 10 << ResolveOwnerType << baseType << baseTypeLine << SignalHandlerIdentifier; + QTest::addRow("ownerOfQualifiedSignal") + << derivedType << 25 << 22 << ResolveOwnerType << baseType << baseTypeLine << SignalIdentifier; + + QTest::addRow("ownerOfGroupedProperty") + << derivedType << 28 << 7 << ResolveOwnerType << baseType << baseTypeLine << GroupedPropertyIdentifier; + QTest::addRow("ownerOfGroupedProperty2") + << derivedType << 28 << 17 << ResolveOwnerType << qQuickValueTypes << noLine + << PropertyIdentifier; + + QTest::addRow("ownerOfAttachedProperty") + << derivedType << 29 << 6 << ResolveOwnerType << derivedType << keysLine << AttachedTypeIdentifier; + QTest::addRow("ownerOfAttachedProperty2") + << derivedType << 29 << 14 << ResolveOwnerType << qQuickKeysAttachedType << noLine + << SignalHandlerIdentifier; + + QTest::addRow("actualTypeOfAttachedProperty") + << derivedType << 29 << 14 << ResolveActualTypeForFieldMemberExpression << noFile + << noLine << SignalHandlerIdentifier; + + QTest::addRow("id") + << derivedType << 7 << 10 << ResolveOwnerType << derivedType << 6 << QmlObjectIdIdentifier; + QTest::addRow("propertyBinding") + << derivedType << 31 << 13 << ResolveOwnerType << baseType << baseTypeLine << PropertyIdentifier; + + QTest::addRow("qmlObject") + << derivedType << 6 << 4 << ResolveOwnerType << derived2Type << 4 << QmlComponentIdentifier; + } +} + +void tst_qmlls_utils::resolveExpressionType() +{ + QFETCH(QString, filePath); + QFETCH(int, line); + QFETCH(int, character); + QFETCH(QQmlLSUtils::ResolveOptions, resolveOption); + QFETCH(QString, expectedFile); + QFETCH(int, expectedLine); + QFETCH(QQmlLSUtils::IdentifierType, expectedType); + + // they all start at 1. + Q_ASSERT(line > 0); + Q_ASSERT(character > 0); + + + auto [env, file] = createEnvironmentAndLoadFile(filePath); + + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1); + + QCOMPARE(locations.size(), 1); + + auto definition = QQmlLSUtils::resolveExpressionType(locations.front().domItem, resolveOption); + + QVERIFY(definition); + if (!expectedFile.isEmpty()) { + QVERIFY(definition->semanticScope); + QCOMPARE(definition->semanticScope->filePath(), expectedFile); + + if (expectedLine != -1) { + QQmlJS::SourceLocation location = definition->semanticScope->sourceLocation(); + QCOMPARE((int)location.startLine, expectedLine); + } + } else { + QVERIFY(!definition->semanticScope); + } + QCOMPARE(definition->type, expectedType); +} + +void tst_qmlls_utils::isValidEcmaScriptIdentifier_data() +{ + QTest::addColumn<QString>("identifier"); + QTest::addColumn<bool>("isValid"); + + QTest::addRow("f") << u"f"_s << true; + QTest::addRow("f-unicode") << u"\\u0046"_s << true; + QTest::addRow("starts-with-digit") << u"8helloWorld"_s << false; + QTest::addRow("starts-with-unicode-digit") << u"\\u0038helloWorld"_s << false; // \u0038 == '8' + QTest::addRow("keyword") << u"return"_s << false; + QTest::addRow("not-keyword") << u"returny"_s << true; +} + +void tst_qmlls_utils::isValidEcmaScriptIdentifier() +{ + QFETCH(QString, identifier); + QFETCH(bool, isValid); + + QCOMPARE(QQmlLSUtils::isValidEcmaScriptIdentifier(identifier), isValid); +} + +using namespace QLspSpecification; + +enum InsertOption { None, InsertColon }; + +void tst_qmlls_utils::completions_data() +{ + QTest::addColumn<QString>("filePath"); + QTest::addColumn<int>("line"); + QTest::addColumn<int>("character"); + QTest::addColumn<ExpectedCompletions>("expected"); + QTest::addColumn<QStringList>("notExpected"); + + const QString file = testFile(u"Yyy.qml"_s); + const QString emptyFile = testFile(u"emptyFile.qml"_s); + const QString pragmaFile = testFile(u"pragmas.qml"_s); + + const QString singletonName = u"SystemInformation"_s; + const QString attachedTypeName = u"Component"_s; + const QString attachedTypeName2 = u"Keys"_s; + const auto attachedTypes = ExpectedCompletions({ + { attachedTypeName, CompletionItemKind::Class }, + { attachedTypeName2, CompletionItemKind::Class }, + }); + + const auto keywords = ExpectedCompletions({ + { u"function"_s, CompletionItemKind::Keyword }, + { u"required"_s, CompletionItemKind::Keyword }, + { u"enum"_s, CompletionItemKind::Keyword }, + { u"component"_s, CompletionItemKind::Keyword }, + }); + + const auto mixedTypes = ExpectedCompletions({ + { u"Zzz"_s, CompletionItemKind::Class }, + { u"Item"_s, CompletionItemKind::Class }, + { u"int"_s, CompletionItemKind::Class }, + { u"date"_s, CompletionItemKind::Class }, + }); + const auto constructorTypes = ExpectedCompletions({ + { u"Rectangle"_s, CompletionItemKind::Constructor }, + { u"MyRectangle"_s, CompletionItemKind::Constructor }, + { u"Zzz"_s, CompletionItemKind::Constructor }, + { u"Item"_s, CompletionItemKind::Constructor }, + { u"QtObject"_s, CompletionItemKind::Constructor }, + }); + const auto rectangleTypes = ExpectedCompletions({ + { u"Rectangle"_s, CompletionItemKind::Constructor }, + { u"MyRectangle"_s, CompletionItemKind::Constructor }, + }); + + QTest::newRow("objEmptyLine") << file << 9 << 1 + << ExpectedCompletions({ + { u"Rectangle"_s, CompletionItemKind::Constructor }, + { u"width"_s, CompletionItemKind::Property }, + }) + << QStringList({ u"QtQuick"_s, u"vector4d"_s }); + + const QString propertyCompletion = u"property type name: value;"_s; + const QString functionCompletion = u"function name(args...): returnType { statements...}"_s; + + const ExpectedCompletions quickSnippetsWithQualifier{ + { u"QQ.BorderImage snippet"_s, CompletionItemKind::Snippet, + u"QQ.BorderImage {\n" + u"\tid: ${1:name}\n" + u"\tsource: \"${2:file}\"\n" + u"\twidth: ${3:100}; height: ${4:100}\n" + u"\tborder.left: ${5: 5}; border.top: ${5}\n" + u"\tborder.right: ${5}; border.bottom: ${5}\n" + u"}"_s }, + { u"QQ.ColorAnimation snippet"_s, CompletionItemKind::Snippet, + u"QQ.ColorAnimation {\n" + u"\tfrom: \"${1:white}\"\n" + u"\tto: \"${2:black}\"\n" + u"\tduration: ${3:200}\n" + u"}"_s }, + { u"QQ.Image snippet"_s, CompletionItemKind::Snippet, + u"QQ.Image {\n" + u"\tid: ${1:name}\n" + u"\tsource: \"${2:file}\"\n" + u"}"_s }, + { u"QQ.Item snippet"_s, CompletionItemKind::Snippet, + u"QQ.Item {\n" + u"\tid: ${1:name}\n" + u"}"_s }, + { u"QQ.NumberAnimation snippet"_s, CompletionItemKind::Snippet, + u"QQ.NumberAnimation {\n" + u"\ttarget: ${1:object}\n" + u"\tproperty: \"${2:name}\"\n" + u"\tduration: ${3:200}\n" + u"\teasing.type: QQ.Easing.${4:InOutQuad}\n" + u"}"_s }, + { u"QQ.NumberAnimation with targets snippet"_s, CompletionItemKind::Snippet, + u"QQ.NumberAnimation {\n" + u"\ttargets: [${1:object}]\n" + u"\tproperties: \"${2:name}\"\n" + u"\tduration: ${3:200}\n" + u"}"_s }, + { u"QQ.PauseAnimation snippet"_s, CompletionItemKind::Snippet, + u"QQ.PauseAnimation {\n" + u"\tduration: ${1:200}\n" + u"}"_s }, + { u"QQ.PropertyAction snippet"_s, CompletionItemKind::Snippet, + u"QQ.PropertyAction {\n" + u"\ttarget: ${1:object}\n" + u"\tproperty: \"${2:name}\"\n" + "}"_s }, + { u"QQ.PropertyAction with targets snippet"_s, CompletionItemKind::Snippet, + u"QQ.PropertyAction {\n" + u"\ttargets: [${1:object}]\n" + u"\tproperties: \"${2:name}\"\n" + u"}"_s }, + { u"QQ.PropertyChanges snippet"_s, CompletionItemKind::Snippet, + u"QQ.PropertyChanges {\n" + u"\ttarget: ${1:object}\n" + u"}"_s }, + { u"QQ.State snippet"_s, CompletionItemKind::Snippet, + u"QQ.State {\n" + u"\tname: ${1:name}\n" + u"\tQQ.PropertyChanges {\n" + u"\t\ttarget: ${2:object}\n" + u"\t}\n" + u"}"_s }, + { u"QQ.Text snippet"_s, CompletionItemKind::Snippet, + u"QQ.Text {\n" + u"\tid: ${1:name}\n" + u"\ttext: qsTr(\"${2:text}\")\n" + u"}"_s }, + { u"QQ.Transition snippet"_s, CompletionItemKind::Snippet, + u"QQ.Transition {\n" + u"\tfrom: \"${1:fromState}\"\n" + u"\tto: \"${2:toState}\"\n" + u"}"_s }, + { u"states binding with PropertyChanges in State"_s, CompletionItemKind::Snippet, + u"states: [\n" + u"\tQQ.State {\n" + u"\t\tname: \"${1:name}\"\n" + u"\t\tQQ.PropertyChanges {\n" + u"\t\t\ttarget: ${2:object}\n" + u"\t\t}\n" + u"\t}\n" + u"]"_s }, + { u"transitions binding with Transition"_s, CompletionItemKind::Snippet, + u"transitions: [\n" + u"\tQQ.Transition {\n" + u"\t\tfrom: \"${1:fromState}\"\n" + u"\t\tto: \"${2:fromState}\"\n" + u"\t}\n" + u"]"_s } + }; + const ExpectedCompletions quickSnippetsWithoutQualifier{ + { { u"BorderImage snippet"_s, CompletionItemKind::Snippet, + u"BorderImage {\n" + u"\tid: ${1:name}\n" + u"\tsource: \"${2:file}\"\n" + u"\twidth: ${3:100}; height: ${4:100}\n" + u"\tborder.left: ${5: 5}; border.top: ${5}\n" + u"\tborder.right: ${5}; border.bottom: ${5}\n" + u"}"_s }, + { u"ColorAnimation snippet"_s, CompletionItemKind::Snippet, + u"ColorAnimation {\n" + u"\tfrom: \"${1:white}\"\n" + u"\tto: \"${2:black}\"\n" + u"\tduration: ${3:200}\n" + u"}"_s }, + { u"Image snippet"_s, CompletionItemKind::Snippet, + u"Image {\n" + u"\tid: ${1:name}\n" + u"\tsource: \"${2:file}\"\n" + u"}"_s }, + { u"Item snippet"_s, CompletionItemKind::Snippet, + u"Item {\n" + u"\tid: ${1:name}\n" + u"}"_s }, + { u"NumberAnimation snippet"_s, CompletionItemKind::Snippet, + u"NumberAnimation {\n" + u"\ttarget: ${1:object}\n" + u"\tproperty: \"${2:name}\"\n" + u"\tduration: ${3:200}\n" + u"\teasing.type: Easing.${4:InOutQuad}\n" + u"}"_s }, + { u"NumberAnimation with targets snippet"_s, CompletionItemKind::Snippet, + u"NumberAnimation {\n" + u"\ttargets: [${1:object}]\n" + u"\tproperties: \"${2:name}\"\n" + u"\tduration: ${3:200}\n" + u"}"_s }, + { u"PauseAnimation snippet"_s, CompletionItemKind::Snippet, + u"PauseAnimation {\n" + u"\tduration: ${1:200}\n" + u"}"_s }, + { u"PropertyAction snippet"_s, CompletionItemKind::Snippet, + u"PropertyAction {\n" + u"\ttarget: ${1:object}\n" + u"\tproperty: \"${2:name}\"\n" + "}"_s }, + { u"PropertyAction with targets snippet"_s, CompletionItemKind::Snippet, + u"PropertyAction {\n" + u"\ttargets: [${1:object}]\n" + u"\tproperties: \"${2:name}\"\n" + u"}"_s }, + { u"PropertyChanges snippet"_s, CompletionItemKind::Snippet, + u"PropertyChanges {\n" + u"\ttarget: ${1:object}\n" + u"}"_s }, + { u"State snippet"_s, CompletionItemKind::Snippet, + u"State {\n" + u"\tname: ${1:name}\n" + u"\tPropertyChanges {\n" + u"\t\ttarget: ${2:object}\n" + u"\t}\n" + u"}"_s }, + { u"Text snippet"_s, CompletionItemKind::Snippet, + u"Text {\n" + u"\tid: ${1:name}\n" + u"\ttext: qsTr(\"${2:text}\")\n" + u"}"_s }, + { u"Transition snippet"_s, CompletionItemKind::Snippet, + u"Transition {\n" + u"\tfrom: \"${1:fromState}\"\n" + u"\tto: \"${2:toState}\"\n" + u"}"_s } } + }; + const ExpectedCompletions quickSnippetsWithoutQualifierWithBindings = ExpectedCompletions{ + { { u"states binding with PropertyChanges in State"_s, CompletionItemKind::Snippet, + u"states: [\n" + u"\tState {\n" + u"\t\tname: \"${1:name}\"\n" + u"\t\tPropertyChanges {\n" + u"\t\t\ttarget: ${2:object}\n" + u"\t\t}\n" + u"\t}\n" + u"]"_s }, + { u"transitions binding with Transition"_s, CompletionItemKind::Snippet, + u"transitions: [\n" + u"\tTransition {\n" + u"\t\tfrom: \"${1:fromState}\"\n" + u"\t\tto: \"${2:fromState}\"\n" + u"\t}\n" + u"]"_s } } + } += quickSnippetsWithoutQualifier; + QTest::newRow("objEmptyLineSnippets") + << file << 9 << 1 + << (ExpectedCompletions({ + { propertyCompletion, CompletionItemKind::Snippet, + u"property ${1:type} ${2:name}: ${0:value};"_s }, + { u"readonly property type name: value;"_s, CompletionItemKind::Snippet, + u"readonly property ${1:type} ${2:name}: ${0:value};"_s }, + { u"default property type name: value;"_s, CompletionItemKind::Snippet, + u"default property ${1:type} ${2:name}: ${0:value};"_s }, + { u"default required property type name: value;"_s, + CompletionItemKind::Snippet, + u"default required property ${1:type} ${2:name}: ${0:value};"_s }, + { u"required default property type name: value;"_s, + CompletionItemKind::Snippet, + u"required default property ${1:type} ${2:name}: ${0:value};"_s }, + { u"required property type name: value;"_s, CompletionItemKind::Snippet, + u"required property ${1:type} ${2:name}: ${0:value};"_s }, + { u"property type name;"_s, CompletionItemKind::Snippet, + u"property ${1:type} ${0:name};"_s }, + { u"required property type name;"_s, CompletionItemKind::Snippet, + u"required property ${1:type} ${0:name};"_s }, + { u"default property type name;"_s, CompletionItemKind::Snippet, + u"default property ${1:type} ${0:name};"_s }, + { u"default required property type name;"_s, CompletionItemKind::Snippet, + u"default required property ${1:type} ${0:name};"_s }, + { u"required default property type name;"_s, CompletionItemKind::Snippet, + u"required default property ${1:type} ${0:name};"_s }, + { u"signal name(arg1:type1, ...)"_s, CompletionItemKind::Snippet, + u"signal ${1:name}($0)"_s }, + { u"signal name;"_s, CompletionItemKind::Snippet, u"signal ${0:name};"_s }, + { u"required name;"_s, CompletionItemKind::Snippet, + u"required ${0:name};"_s }, + { functionCompletion, CompletionItemKind::Snippet, + u"function ${1:name}($2): ${3:returnType} {\n\t$0\n}"_s }, + { u"enum name { Values...}"_s, CompletionItemKind::Snippet, + u"enum ${1:name} {\n\t${0:values}\n}"_s }, + { u"component Name: BaseType { ... }"_s, CompletionItemKind::Snippet, + u"component ${1:name}: ${2:baseType} {\n\t$0\n}"_s }, + }) += quickSnippetsWithoutQualifierWithBindings) + // not allowed because required properties need an initializer + << QStringList({ u"readonly property type name;"_s }); + + QTest::newRow("quickSnippetsForQualifiedQuickImport") + << testFile("qualifiedModule.qml") << 5 << 1 + << quickSnippetsWithQualifier + // not allowed because required properties need an initializer + << QStringList({ u"readonly property type name;"_s }); + + QTest::newRow("quickSnippetsForQualifiedQuickImportBeforeDot") + << testFile("qualifiedModule.qml") << 5 << 7 + << quickSnippetsWithQualifier + // not allowed because required properties need an initializer + << QStringList({ u"readonly property type name;"_s }); + + QTest::newRow("quickSnippetsForQualifiedQuickImportAfterDot") + << testFile("qualifiedModule.qml") << 5 << 8 + << quickSnippetsWithoutQualifier + // not allowed because required properties need an initializer + << QStringList({ u"readonly property type name;"_s, + u"states binding with PropertyChanges in State"_s, + u"transitions binding with Transition"_s }); + + QTest::newRow("quickSnippetsForQualifiedQuickImportBeforeDotInBinding") + << testFile("qualifiedModule.qml") << 4 << 33 << quickSnippetsWithQualifier + << QStringList(); + + QTest::newRow("quickSnippetsForQualifiedQuickImportAfterDotInBinding") + << testFile("qualifiedModule.qml") << 4 << 34 << quickSnippetsWithoutQualifier + << QStringList({ u"states binding with PropertyChanges in State"_s, + u"transitions binding with Transition"_s }); + + // forbid transitions and states because QtObject does not inherit from Item + QTest::newRow("qtObjectEmptyLineSnippets") + << file << 141 << 8 + << ExpectedCompletions{ { u"Item"_s, CompletionItemKind::Constructor } } + << QStringList({ u"transitions binding with Transition"_s, + u"states binding with PropertyChanges in State"_s }); + + QTest::newRow("handlers") << file << 5 << 1 + << ExpectedCompletions{ { + { u"onHandleMe"_s, CompletionItemKind::Method }, + { u"onDefaultPropertyChanged"_s, + CompletionItemKind::Method }, + } } + << QStringList({ u"QtQuick"_s, u"vector4d"_s }); + + QTest::newRow("attachedTypes") + << file << 9 << 1 << attachedTypes << QStringList{ u"QtQuick"_s, u"vector4d"_s }; + + QTest::newRow("attachedTypesInScript") + << file << 6 << 12 << attachedTypes << QStringList{ u"QtQuick"_s, u"vector4d"_s }; + QTest::newRow("attachedTypesInLongScript") + << file << 10 << 16 << attachedTypes << QStringList{ u"QtQuick"_s, u"vector4d"_s }; + + QTest::newRow("completionFromRootId") << file << 10 << 21 + << ExpectedCompletions({ + { u"width"_s, CompletionItemKind::Property }, + { u"lala"_s, CompletionItemKind::Method }, + { u"foo"_s, CompletionItemKind::Property }, + }) + << QStringList{ u"QtQuick"_s, u"vector4d"_s }; + + QTest::newRow("attachedProperties") << file << 89 << 15 + << ExpectedCompletions({ + { u"onCompleted"_s, CompletionItemKind::Method }, + }) + << QStringList{ u"QtQuick"_s, + u"vector4d"_s, + attachedTypeName, + u"Rectangle"_s, + u"property"_s, + u"foo"_s, + u"onActiveFocusOnTabChanged"_s }; + + QTest::newRow("inBindingLabel") << file << 6 << 10 + << ExpectedCompletions({ + { u"Rectangle"_s, CompletionItemKind::Constructor }, + { u"width"_s, CompletionItemKind::Property }, + }) + << QStringList({ u"QtQuick"_s, u"vector4d"_s, u"property"_s }); + + QTest::newRow("afterBinding") << file << 6 << 11 + << (ExpectedCompletions({ + { u"height"_s, CompletionItemKind::Property }, + { u"width"_s, CompletionItemKind::Property }, + { u"Rectangle"_s, CompletionItemKind::Constructor }, + { singletonName, CompletionItemKind::Class }, + }) + + attachedTypes) + << QStringList({ u"QtQuick"_s, u"property"_s, u"vector4d"_s }); + + QTest::newRow("jsGlobals") << file << 6 << 11 + << ExpectedCompletions{ { + { u"console"_s, CompletionItemKind::Property }, + { u"Math"_s, CompletionItemKind::Property }, + } } + << QStringList({ u"QtQuick"_s, u"property"_s, u"vector4d"_s }); + + QTest::newRow("jsGlobals2") << file << 100 << 32 + << ExpectedCompletions{ { + { u"abs"_s, CompletionItemKind::Method }, + { u"log"_s, CompletionItemKind::Method }, + { u"E"_s, CompletionItemKind::Property }, + } } + << QStringList({ u"QtQuick"_s, u"property"_s, u"vector4d"_s, + u"foo"_s, u"lala"_s }); + + QTest::newRow("afterLongBinding") + << file << 10 << 16 + << ExpectedCompletions({ + { u"height"_s, CompletionItemKind::Property }, + { u"width"_s, CompletionItemKind::Property }, + { u"Rectangle"_s, CompletionItemKind::Constructor }, + }) + << QStringList({ u"QtQuick"_s, u"property"_s, u"vector4d"_s }); + + QTest::newRow("afterId") << file << 5 << 8 << ExpectedCompletions({}) + << QStringList({ + u"QtQuick"_s, + u"property"_s, + u"Rectangle"_s, + u"width"_s, + u"vector4d"_s, + u"import"_s, + }); + + QTest::newRow("emptyFile") << emptyFile << 1 << 1 + << ExpectedCompletions({ + { u"import"_s, CompletionItemKind::Keyword }, + { u"pragma"_s, CompletionItemKind::Keyword }, + }) + << QStringList({ u"QtQuick"_s, u"vector4d"_s, u"width"_s }); + + QTest::newRow("importImport") << file << 1 << 4 + << ExpectedCompletions({ + { u"import"_s, CompletionItemKind::Keyword }, + }) + << QStringList({ u"QtQuick"_s, u"vector4d"_s, u"width"_s, + u"Rectangle"_s }); + + QTest::newRow("importModuleStart") + << file << 1 << 8 + << ExpectedCompletions({ + { u"QtQuick"_s, CompletionItemKind::Module }, + }) + << QStringList({ u"vector4d"_s, u"width"_s, u"Rectangle"_s, u"import"_s }); + + QTest::newRow("importVersionStart") + << file << 1 << 16 + << ExpectedCompletions({ + { u"2"_s, CompletionItemKind::Constant }, + { u"as"_s, CompletionItemKind::Keyword }, + }) + << QStringList({ u"Rectangle"_s, u"import"_s, u"vector4d"_s, u"width"_s }); + + // QTest::newRow("importVersionMinor") + // << uri << 1 << 18 + // << ExpectedCompletions({ + // { u"15"_s, CompletionItemKind::Constant }, + // }) + // << QStringList({ u"as"_s, u"Rectangle"_s, u"import"_s, u"vector4d"_s, u"width"_s }); + + QTest::newRow("expandBase1") << file << 10 << 24 + << ExpectedCompletions({ + { u"width"_s, CompletionItemKind::Property }, + { u"foo"_s, CompletionItemKind::Property }, + }) + << QStringList({ u"import"_s, u"Rectangle"_s }); + + QTest::newRow("expandBase2") << file << 11 << 30 + << ExpectedCompletions({ + { u"width"_s, CompletionItemKind::Property }, + { u"color"_s, CompletionItemKind::Property }, + }) + << QStringList({ u"foo"_s, u"import"_s, u"Rectangle"_s }); + + QTest::newRow("qualifiedTypeCompletionBeforeDot") + << testFile(u"qualifiedModule.qml"_s) << 4 << 31 + << ExpectedCompletions({ + { u"QQ.Rectangle"_s, CompletionItemKind::Constructor }, + }) + << QStringList({ u"foo"_s, u"import"_s, u"lala"_s, }); + + QTest::newRow("qualifiedTypeCompletionAfterDot") + << testFile(u"qualifiedModule.qml"_s) << 4 << 35 + << ExpectedCompletions({ + { u"Rectangle"_s, CompletionItemKind::Constructor }, + }) + << QStringList({ u"foo"_s, u"import"_s, u"lala"_s, u"width"_s }); + + QTest::newRow("qualifiedTypeCompletionBeforeDotInDefaultBinding") + << testFile(u"qualifiedModule.qml"_s) << 5 << 5 + << ExpectedCompletions({ + { u"QQ.Rectangle"_s, CompletionItemKind::Constructor }, + }) + << QStringList({ u"foo"_s, u"import"_s, u"lala"_s }); + + QTest::newRow("qualifiedTypeCompletionAfterDotInDefaultBinding") + << testFile(u"qualifiedModule.qml"_s) << 5 << 8 + << ExpectedCompletions({ + { u"Rectangle"_s, CompletionItemKind::Constructor }, + }) + << QStringList({ u"foo"_s, u"import"_s, u"lala"_s, u"width"_s }); + + QTest::newRow("parameterCompletion") + << file << 36 << 24 + << ExpectedCompletions({ + { u"helloWorld"_s, CompletionItemKind::Variable }, + { u"helloMe"_s, CompletionItemKind::Variable }, + }) + << QStringList(); + + QTest::newRow("inMethodName") << file << 15 << 14 << ExpectedCompletions({}) + << QStringList{ u"QtQuick"_s, u"vector4d"_s, u"foo"_s, + u"root"_s, u"Item"_s, singletonName }; + + QTest::newRow("inMethodReturnType") << file << 17 << 54 << mixedTypes + << QStringList{ + u"QtQuick"_s, + u"foo"_s, + u"root"_s, + }; + + QTest::newRow("letStatement") << file << 95 << 13 << ExpectedCompletions({}) + << QStringList{ u"QtQuick"_s, u"vector4d"_s, u"root"_s }; + + QTest::newRow("inParameterCompletion") << file << 35 << 39 << ExpectedCompletions({}) + << QStringList{ + u"helloWorld"_s, + u"helloMe"_s, + }; + + QTest::newRow("parameterTypeCompletion") << file << 35 << 55 << mixedTypes + << QStringList{ + u"helloWorld"_s, + u"helloMe"_s, + }; + + QTest::newRow("propertyTypeCompletion") << file << 16 << 14 << mixedTypes + << QStringList{ + u"helloWorld"_s, + u"helloMe"_s, + }; + QTest::newRow("propertyTypeCompletion2") << file << 16 << 23 << mixedTypes + << QStringList{ + u"helloWorld"_s, + u"helloMe"_s, + }; + QTest::newRow("propertyNameCompletion") + << file << 16 << 24 << ExpectedCompletions({}) + << QStringList{ + u"helloWorld"_s, u"helloMe"_s, u"Zzz"_s, u"Item"_s, u"int"_s, u"date"_s, + }; + QTest::newRow("propertyNameCompletion2") + << file << 16 << 25 << ExpectedCompletions({}) + << QStringList{ + u"helloWorld"_s, u"helloMe"_s, u"Zzz"_s, u"Item"_s, u"int"_s, u"date"_s, + }; + + QTest::newRow("propertyDefinitionBinding") + << file << 90 << 27 + << (ExpectedCompletions({ + { u"lala"_s, CompletionItemKind::Method }, + { u"createRectangle"_s, CompletionItemKind::Method }, + { u"createItem"_s, CompletionItemKind::Method }, + { u"createAnything"_s, CompletionItemKind::Method }, + }) += constructorTypes) + << QStringList{ + u"helloWorld"_s, + u"helloMe"_s, + u"int"_s, + u"date"_s, + }; + + QTest::newRow("ignoreNonRelatedTypesForPropertyDefinitionBinding") + << file << 16 << 28 + << (ExpectedCompletions({ + { u"createRectangle"_s, CompletionItemKind::Method }, + { u"createItem"_s, CompletionItemKind::Method }, + { u"createAnything"_s, CompletionItemKind::Method }, + }) += rectangleTypes) + << QStringList{ + u"Item"_s, u"Zzz"_s, u"helloWorld"_s, u"helloMe"_s, + u"int"_s, u"date"_s, u"Item"_s, u"QtObject"_s, + }; + + QTest::newRow("inBoundObject") + << file << 16 << 40 + << (ExpectedCompletions({ + { u"objectName"_s, CompletionItemKind::Property }, + { u"width"_s, CompletionItemKind::Property }, + { propertyCompletion, CompletionItemKind::Snippet }, + { functionCompletion, CompletionItemKind::Snippet }, + }) += constructorTypes) + << QStringList{ + u"helloWorld"_s, u"helloMe"_s, u"int"_s, u"date"_s, u"QtQuick"_s, u"vector4d"_s, + }; + + QTest::newRow("qualifiedIdentifierCompletion") + << file << 37 << 36 + << ExpectedCompletions({ + { u"helloProperty"_s, CompletionItemKind::Property }, + { u"childAt"_s, CompletionItemKind::Method }, + }) + << QStringList{ u"helloVar"_s, u"someItem"_s, u"color"_s, u"helloWorld"_s, + u"propertyOfZZZ"_s }; + + QTest::newRow("scriptExpressionCompletion") + << file << 60 << 16 + << ExpectedCompletions({ + // parameters + { u"jsParameterInChild"_s, CompletionItemKind::Variable }, + // own properties + { u"jsIdentifierInChild"_s, CompletionItemKind::Variable }, + { u"functionInChild"_s, CompletionItemKind::Method }, + { u"propertyInChild"_s, CompletionItemKind::Property }, + // inherited properties from QML + { u"functionInBase"_s, CompletionItemKind::Method }, + { u"propertyInBase"_s, CompletionItemKind::Property }, + // inherited properties (transitive) from C++ + { u"objectName"_s, CompletionItemKind::Property }, + { u"someItem"_s, CompletionItemKind::Value }, + { u"true"_s, CompletionItemKind::Value }, + { u"false"_s, CompletionItemKind::Value }, + { u"null"_s, CompletionItemKind::Value }, + }) + << QStringList{ + u"helloVar"_s, + u"color"_s, + u"helloWorld"_s, + u"propertyOfZZZ"_s, + u"propertyInDerived"_s, + u"functionInDerived"_s, + u"jsIdentifierInDerived"_s, + u"jsIdentifierInBase"_s, + u"lala"_s, + u"foo"_s, + u"jsParameterInBase"_s, + u"jsParameterInDerived"_s, + }; + + QTest::newRow("qualifiedScriptExpressionCompletion") + << file << 60 << 34 + << ExpectedCompletions({ + // own properties + { u"helloProperty"_s, CompletionItemKind::Property }, + // inherited properties (transitive) from C++ + { u"width"_s, CompletionItemKind::Property }, + }) + << QStringList{ + u"helloVar"_s, + u"color"_s, + u"helloWorld"_s, + u"propertyOfZZZ"_s, + u"propertyInDerived"_s, + u"functionInDerived"_s, + u"jsIdentifierInDerived"_s, + u"jsIdentifierInBase"_s, + u"jsIdentifierInChild"_s, + u"lala"_s, + u"foo"_s, + u"jsParameterInBase"_s, + u"jsParameterInDerived"_s, + u"jsParameterInChild"_s, + u"functionInChild"_s, + }; + + QTest::newRow("pragma") << pragmaFile << 1 << 8 + << ExpectedCompletions({ + { u"NativeMethodBehavior"_s, CompletionItemKind::Value }, + { u"ComponentBehavior"_s, CompletionItemKind::Value }, + { u"ListPropertyAssignBehavior"_s, + CompletionItemKind::Value }, + { u"Singleton"_s, CompletionItemKind::Value }, + // note: only complete the Addressible/Inaddressible part of + // ValueTypeBehavior! + { u"ValueTypeBehavior"_s, CompletionItemKind::Value }, + }) + << QStringList{ + u"int"_s, + u"Rectangle"_s, + u"FunctionSignatureBehavior"_s, + u"Strict"_s, + }; + + QTest::newRow("pragmaValue") << pragmaFile << 2 << 30 + << ExpectedCompletions({ + { u"AcceptThisObject"_s, CompletionItemKind::Value }, + { u"RejectThisObject"_s, CompletionItemKind::Value }, + }) + << QStringList{ + u"int"_s, + u"Rectangle"_s, + u"FunctionSignatureBehavior"_s, + u"Strict"_s, + u"NativeMethodBehavior"_s, + u"ComponentBehavior"_s, + u"ListPropertyAssignBehavior"_s, + u"Singleton"_s, + u"ValueTypeBehavior"_s, + u"Unbound"_s, + }; + + QTest::newRow("pragmaMultiValue") + << pragmaFile << 3 << 43 + << ExpectedCompletions({ + { u"ReplaceIfNotDefault"_s, CompletionItemKind::Value }, + { u"Append"_s, CompletionItemKind::Value }, + { u"Replace"_s, CompletionItemKind::Value }, + }) + << QStringList{ + u"int"_s, + u"Rectangle"_s, + u"FunctionSignatureBehavior"_s, + u"Strict"_s, + u"NativeMethodBehavior"_s, + u"ComponentBehavior"_s, + u"ListPropertyAssignBehavior"_s, + u"Singleton"_s, + u"ValueTypeBehavior"_s, + u"Unbound"_s, + }; + + QTest::newRow("pragmaWithoutValue") + << pragmaFile << 1 << 17 + << ExpectedCompletions({ + { u"NativeMethodBehavior"_s, CompletionItemKind::Value }, + { u"ComponentBehavior"_s, CompletionItemKind::Value }, + { u"ListPropertyAssignBehavior"_s, CompletionItemKind::Value }, + { u"Singleton"_s, CompletionItemKind::Value }, + // note: only complete the Addressible/Inaddressible part of + // ValueTypeBehavior! + { u"ValueTypeBehavior"_s, CompletionItemKind::Value }, + }) + << QStringList{ + u"int"_s, + u"Rectangle"_s, + u"FunctionSignatureBehavior"_s, + u"Strict"_s, + }; + + QTest::newRow("non-block-scoped-variable") + << file << 69 << 21 + << ExpectedCompletions({ + { u"helloVarVariable"_s, CompletionItemKind::Variable }, + }) + << QStringList{}; + QTest::newRow("block-scoped-variable") + << file << 76 << 21 << ExpectedCompletions{ { u"test2"_s, CompletionItemKind::Method } } + << QStringList{ u"helloLetVariable"_s, u"helloVarVariable"_s }; + + QTest::newRow("singleton") << file << 78 << 33 + << ExpectedCompletions({ + { singletonName, CompletionItemKind::Class }, + }) + << QStringList{}; + + QTest::newRow("singletonPropertyAndEnums") + << file << 78 << 52 + << ExpectedCompletions({ + { u"byteOrder"_s, CompletionItemKind::Property }, + { u"Little"_s, CompletionItemKind::EnumMember }, + { u"Endian"_s, CompletionItemKind::Enum }, + }) + << QStringList{ + u"int"_s, + u"Rectangle"_s, + u"foo"_s, + }; + + QTest::newRow("enumsFromItem") << file << 86 << 33 + << ExpectedCompletions({ + { u"World"_s, CompletionItemKind::EnumMember }, + { u"ValueOne"_s, CompletionItemKind::EnumMember }, + { u"ValueTwo"_s, CompletionItemKind::EnumMember }, + { u"Hello"_s, CompletionItemKind::Enum }, + { u"MyEnum"_s, CompletionItemKind::Enum }, + }) + << QStringList{ + u"int"_s, + u"Rectangle"_s, + }; + + QTest::newRow("enumsFromEnumName") + << file << 87 << 40 + << ExpectedCompletions({ + { u"World"_s, CompletionItemKind::EnumMember }, + }) + << QStringList{ + u"int"_s, u"Rectangle"_s, u"foo"_s, u"ValueOne"_s, + u"ValueTwo"_s, u"Hello"_s, u"MyEnum"_s, + }; + + QTest::newRow("requiredProperty") + << file << 97 << 14 + << ExpectedCompletions({ + { u"property"_s, CompletionItemKind::Keyword }, + { u"default"_s, CompletionItemKind::Keyword }, + }) + << QStringList{ + u"readonly"_s, u"required"_s, u"int"_s, u"Rectangle"_s, u"foo"_s, + u"ValueOne"_s, u"ValueTwo"_s, u"Hello"_s, u"MyEnum"_s, + }; + + QTest::newRow("readonlyProperty") + << file << 98 << 13 + << ExpectedCompletions({ + { u"property"_s, CompletionItemKind::Keyword }, + { u"default"_s, CompletionItemKind::Keyword }, + }) + << QStringList{ + u"required"_s, u"readonly"_s, u"int"_s, u"Rectangle"_s, u"foo"_s, + u"ValueOne"_s, u"ValueTwo"_s, u"Hello"_s, u"MyEnum"_s, + }; + + QTest::newRow("defaultProperty") + << file << 99 << 12 + << ExpectedCompletions({ + { u"property"_s, CompletionItemKind::Keyword }, + { u"readonly"_s, CompletionItemKind::Keyword }, + { u"required"_s, CompletionItemKind::Keyword }, + }) + << QStringList{ + u"default"_s, u"int"_s, u"Rectangle"_s, u"foo"_s, + u"ValueOne"_s, u"ValueTwo"_s, u"Hello"_s, u"MyEnum"_s, + }; + + QTest::newRow("defaultProperty2") + << file << 99 << 20 + << ExpectedCompletions({ + { u"property"_s, CompletionItemKind::Keyword }, + { u"readonly"_s, CompletionItemKind::Keyword }, + { u"required"_s, CompletionItemKind::Keyword }, + }) + << QStringList{ + u"default"_s, u"int"_s, u"Rectangle"_s, u"foo"_s, + u"ValueOne"_s, u"ValueTwo"_s, u"Hello"_s, u"MyEnum"_s, + }; + + QTest::newRow("defaultProperty3") + << file << 99 << 21 << ExpectedCompletions{ { u"int"_s, CompletionItemKind::Class } } + << QStringList{ + u"property"_s, + u"readonly"_s, + u"required"_s, + }; + + const QString forStatementCompletion = u"for (initializer; condition; increment) { statements... }"_s; + const QString ifStatementCompletion = u"if (condition) statement"_s; + const QString letStatementCompletion = u"let variable = value;"_s; + const QString constStatementCompletion = u"const variable = value;"_s; + const QString varStatementCompletion = u"var variable = value;"_s; + + // for the for loop + const QString letStatementCompletionWithoutSemicolon = letStatementCompletion.chopped(1); + const QString constStatementCompletionWithoutSemicolon = constStatementCompletion.chopped(1); + const QString varStatementCompletionWithoutSemicolon = varStatementCompletion.chopped(1); + + const QString caseStatementCompletion = u"case value: statements..."_s; + const QString caseStatement2Completion = u"case value: { statements... }"_s; + const QString defaultStatementCompletion = u"default: statements..."_s; + const QString defaultStatement2Completion = u"default: { statements... }"_s; + + // warning: the completion strings in the test below were all tested by hand in VS Code to + // make sure they are easy to use. Make sure to check the code snippets by hand before changing + // them. + QTest::newRow("jsStatements") + << file << 104 << 1 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet, + u"let ${1:variable} = $0;"_s }, + { u"const variable = value;"_s, CompletionItemKind::Snippet, + u"const ${1:variable} = $0;"_s }, + { u"var variable = value;"_s, CompletionItemKind::Snippet, + u"var ${1:variable} = $0;"_s }, + { u"{ statements... }"_s, CompletionItemKind::Snippet, + u"{\n\t$0\n}"_s }, + { u"if (condition) { statements }"_s, + CompletionItemKind::Snippet, u"if ($1) {\n\t$0\n}"_s }, + { u"do { statements } while (condition);"_s, + CompletionItemKind::Snippet, u"do {\n\t$1\n} while ($0);"_s }, + { u"while (condition) { statements...}"_s, + CompletionItemKind::Snippet, u"while ($1) {\n\t$0\n}"_s }, + { forStatementCompletion, + CompletionItemKind::Snippet, u"for ($1;$2;$3) {\n\t$0\n}"_s }, + { u"try { statements... } catch(error) { statements... }"_s, + CompletionItemKind::Snippet, u"try {\n\t$1\n} catch($2) {\n\t$0\n}"_s }, + { u"try { statements... } finally { statements... }"_s, + CompletionItemKind::Snippet, u"try {\n\t$1\n} finally {\n\t$0\n}"_s }, + { u"try { statements... } catch(error) { statements... } finally { statements... }"_s, + CompletionItemKind::Snippet, u"try {\n\t$1\n} catch($2) {\n\t$3\n} finally {\n\t$0\n}"_s }, + { u"for (property in object) { statements... }"_s, + CompletionItemKind::Snippet, u"for ($1 in $2) {\n\t$0\n}"_s }, + { u"for (element of array) { statements... }"_s, + CompletionItemKind::Snippet, u"for ($1 of $2) {\n\t$0\n}"_s }, + { u"continue"_s, CompletionItemKind::Keyword }, + { u"break"_s, CompletionItemKind::Keyword }, + } + << QStringList{ caseStatementCompletion, + caseStatement2Completion, + defaultStatementCompletion, + defaultStatement2Completion, + }; + + QTest::newRow("forStatementLet") + << file << 103 << 13 + << ExpectedCompletions{ { letStatementCompletionWithoutSemicolon, + CompletionItemKind::Snippet, u"let ${1:variable} = $0"_s }, + { constStatementCompletionWithoutSemicolon, + CompletionItemKind::Snippet, u"const ${1:variable} = $0"_s }, + { varStatementCompletionWithoutSemicolon, + CompletionItemKind::Snippet, u"var ${1:variable} = $0"_s }, + { u"helloJSStatements"_s, CompletionItemKind::Method } } + << QStringList{ u"property"_s, + u"readonly"_s, + u"required"_s, + forStatementCompletion, + ifStatementCompletion, + letStatementCompletion, + constStatementCompletion, + varStatementCompletion }; + + QTest::newRow("forStatementCondition") + << file << 103 << 25 + << ExpectedCompletions{ + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"i"_s, CompletionItemKind::Variable }, + } + << QStringList{ u"property"_s, u"readonly"_s, u"required"_s, + forStatementCompletion, ifStatementCompletion, varStatementCompletion, + letStatementCompletion, constStatementCompletion, } + ; + + QTest::newRow("forStatementIncrement") + << file << 103 << 30 + << ExpectedCompletions{ + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"i"_s, CompletionItemKind::Variable }, + } + << QStringList{ u"property"_s, u"readonly"_s, u"required"_s, + forStatementCompletion, ifStatementCompletion, varStatementCompletion, + letStatementCompletion, constStatementCompletion, } + ; + + QTest::newRow("forStatementIncrement2") + << file << 103 << 33 + << ExpectedCompletions{ { u"helloJSStatements"_s, CompletionItemKind::Method } } + << QStringList{ + u"property"_s, u"readonly"_s, + u"required"_s, forStatementCompletion, + ifStatementCompletion, varStatementCompletion, + letStatementCompletion, constStatementCompletion, + }; + + QTest::newRow("forStatementWithoutBlock") + << file << 107 << 12 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"j"_s, CompletionItemKind::Variable }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion }; + + QTest::newRow("blockStatementBeforeBracket") + << file << 103 << 36 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"i"_s, CompletionItemKind::Variable }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion }; + + QTest::newRow("blockStatementAfterBracket") + << file << 103 << 37 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion }; + + QTest::newRow("ifStatementCondition") + << file << 110 << 15 + << ExpectedCompletions{ + { u"hello"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion, constStatementCompletion } + ; + + QTest::newRow("ifStatementConsequence") + << file << 111 << 12 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"hello"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion } + ; + + QTest::newRow("ifStatementAlternative") + << file << 113 << 12 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"hello"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion } + ; + + QTest::newRow("binaryExpressionCompletionInsideStatement") + << file << 113 << 21 + << ExpectedCompletions{ { u"hello"_s, CompletionItemKind::Variable }, } + << QStringList{ propertyCompletion, forStatementCompletion } + ; + + QTest::newRow("elseIfStatement") + << file << 121 << 18 + << ExpectedCompletions{ { u"hello"_s, CompletionItemKind::Variable }, } + << QStringList{ propertyCompletion, letStatementCompletion, ifStatementCompletion } + ; + QTest::newRow("returnStatement") + << file << 125 << 16 + << ExpectedCompletions{ { u"hello"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + QTest::newRow("returnStatement2") + << testFile("completions/returnStatement.qml") << 8 << 15 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("whileCondition") + << file << 128 << 16 + << ExpectedCompletions{ { u"hello"_s, CompletionItemKind::Variable }, } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("whileConsequence") + << file << 128 << 22 + << ExpectedCompletions{ { u"hello"_s, CompletionItemKind::Variable }, + { letStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion }; + + QTest::newRow("doWhileCondition") + << file << 131 << 30 + << ExpectedCompletions{ { u"hello"_s, CompletionItemKind::Variable }, } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("doWhileConsequence") + << file << 131 << 12 + << ExpectedCompletions{ { u"hello"_s, CompletionItemKind::Variable }, + { letStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion }; + + QTest::newRow("forInStatementLet") + << file << 134 << 13 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method } } + << QStringList{ + u"property"_s, u"readonly"_s, u"required"_s, + forStatementCompletion, ifStatementCompletion, + }; + + QTest::newRow("forOfStatementLet") + << file << 135 << 13 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method } } + << QStringList{ + u"property"_s, u"readonly"_s, u"required"_s, + forStatementCompletion, ifStatementCompletion, + }; + + QTest::newRow("forInStatementTarget") + << file << 134 << 25 + << ExpectedCompletions{ + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"hello"_s, CompletionItemKind::Variable }, + } + << QStringList{ u"property"_s, u"readonly"_s, u"required"_s, + forStatementCompletion, ifStatementCompletion, varStatementCompletion, + letStatementCompletion, constStatementCompletion, } + ; + + QTest::newRow("forOfStatementTarget") + << file << 135 << 24 + << ExpectedCompletions{ + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"hello"_s, CompletionItemKind::Variable }, + } + << QStringList{ u"property"_s, u"readonly"_s, u"required"_s, + forStatementCompletion, ifStatementCompletion, varStatementCompletion, + letStatementCompletion, constStatementCompletion, } + ; + + QTest::newRow("forInStatementConsequence") + << file << 134 << 31 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"hello"_s, CompletionItemKind::Variable }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion }; + + QTest::newRow("forOfStatementConsequence") + << file << 135 << 30 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"hello"_s, CompletionItemKind::Variable }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion }; + + QTest::newRow("binaryExpressionRHS") << file << 138 << 17 + << ExpectedCompletions{ + { u"log"_s, CompletionItemKind::Method }, + { u"error"_s, CompletionItemKind::Method }, + } + << QStringList{ propertyCompletion, u"helloVarVariable"_s, + u"test1"_s, u"width"_s, + u"height"_s, u"layer"_s, + u"left"_s, forStatementCompletion } + ; + QTest::newRow("binaryExpressionLHS") << file << 138 << 12 + << ExpectedCompletions{ + { u"qualifiedScriptIdentifiers"_s, CompletionItemKind::Method }, + { u"width"_s, CompletionItemKind::Property }, + { u"layer"_s, CompletionItemKind::Property }, + } + << QStringList{ u"log"_s, u"error"_s, forStatementCompletion} + ; + + const QString missingRHSFile = testFile(u"completions/missingRHS.qml"_s); + QTest::newRow("binaryExpressionMissingRHS") << missingRHSFile << 12 << 25 + << ExpectedCompletions{ + { u"good"_s, CompletionItemKind::Property }, + } + << QStringList{ propertyCompletion, u"bad"_s } + ; + QTest::newRow("binaryExpressionMissingRHSWithDefaultProperty") << missingRHSFile << 14 << 33 + << ExpectedCompletions{ + { u"good"_s, CompletionItemKind::Property }, + } + << QStringList{ propertyCompletion, u"bad"_s, u"helloSubItem"_s } + ; + + QTest::newRow("binaryExpressionMissingRHSWithSemicolon") + << testFile(u"completions/missingRHS.parserfail.qml"_s) + << 5 << 22 + << ExpectedCompletions{ + { u"good"_s, CompletionItemKind::Property }, + } + << QStringList{ propertyCompletion, u"bad"_s, u"helloSubItem"_s } + ; + + QTest::newRow("binaryExpressionMissingRHSWithStatement") << + testFile(u"completions/missingRHS.parserfail.qml"_s) + << 6 << 22 + << ExpectedCompletions{ + { u"good"_s, CompletionItemKind::Property }, + } + << QStringList{ propertyCompletion, u"bad"_s, u"helloSubItem"_s } + ; + + QTest::newRow("tryStatements") + << testFile(u"completions/tryStatements.qml"_s) << 5 << 14 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{}; + + QTest::newRow("tryStatementsCatchParameter") + << testFile(u"completions/tryStatements.qml"_s) << 5 << 23 << ExpectedCompletions{} + << QStringList{ letStatementCompletion, forStatementCompletion }; + + QTest::newRow("tryStatementsCatchBlock") + << testFile(u"completions/tryStatements.qml"_s) << 5 << 27 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{}; + + QTest::newRow("tryStatementsFinallyBlock") + << testFile(u"completions/tryStatements.qml"_s) << 5 << 39 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{}; + + QTest::newRow("inSwitchExpression") + << testFile(u"completions/switchStatements.qml"_s) << 10 << 16 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ + letStatementCompletion, + propertyCompletion, + }; + + QTest::newRow("beforeCaseStatement") + << testFile(u"completions/switchStatements.qml"_s) << 11 << 1 + << ExpectedCompletions{ { caseStatementCompletion, CompletionItemKind::Snippet }, + { caseStatement2Completion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatement2Completion, CompletionItemKind::Snippet } } + << QStringList{ letStatementCompletion, propertyCompletion, u"x"_s, u"myProperty"_s }; + QTest::newRow("inCaseExpression") + << testFile(u"completions/switchStatements.qml"_s) << 12 << 14 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ letStatementCompletion, propertyCompletion, caseStatementCompletion }; + QTest::newRow("inCaseStatementList") + << testFile(u"completions/switchStatements.qml"_s) << 13 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"break"_s, CompletionItemKind::Keyword }, + { u"return"_s, CompletionItemKind::Keyword }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ propertyCompletion }; + QTest::newRow("inDefaultStatementList") + << testFile(u"completions/switchStatements.qml"_s) << 24 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"break"_s, CompletionItemKind::Keyword }, + { u"return"_s, CompletionItemKind::Keyword }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ propertyCompletion }; + QTest::newRow("inMoreCasesStatementList") + << testFile(u"completions/switchStatements.qml"_s) << 26 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"break"_s, CompletionItemKind::Keyword }, + { u"return"_s, CompletionItemKind::Keyword }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ propertyCompletion }; + + QTest::newRow("inCaseBeforeBlock") + << testFile(u"completions/switchStatements.qml"_s) << 14 << 23 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ + propertyCompletion, + }; + QTest::newRow("inCaseBeforeBlock2") + << testFile(u"completions/switchStatements.qml"_s) << 14 << 24 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ + propertyCompletion, + }; + + QTest::newRow("inCaseNestedStatement") + << testFile(u"completions/switchStatements.qml"_s) << 16 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ propertyCompletion, caseStatementCompletion, + defaultStatementCompletion }; + + QTest::newRow("inCaseAfterBlock") + << testFile(u"completions/switchStatements.qml"_s) << 22 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ + propertyCompletion, + }; + + QTest::newRow("inCaseBeforeDefault") + << testFile(u"completions/switchStatements.qml"_s) << 23 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ + propertyCompletion, + }; + + QTest::newRow("inCaseAfterDefault") + << testFile(u"completions/switchStatements.qml"_s) << 25 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ + propertyCompletion, + }; + + QTest::newRow("beforeAnyCase") + << testFile(u"completions/switchStatements.qml"_s) << 20 << 1 + << ExpectedCompletions{ { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion, letStatementCompletion, u"myProperty"_s, u"x"_s, + u"f"_s }; + + QTest::newRow("beforeAnyDefault") + << testFile(u"completions/switchStatements.qml"_s) << 32 << 1 + << ExpectedCompletions{ { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion, letStatementCompletion, u"myProperty"_s, u"x"_s, + u"f"_s }; + QTest::newRow("inDefaultAfterDefault") + << testFile(u"completions/switchStatements.qml"_s) << 33 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { caseStatementCompletion, CompletionItemKind::Snippet }, + { defaultStatementCompletion, CompletionItemKind::Snippet }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { u"myProperty"_s, CompletionItemKind::Property } } + << QStringList{ + propertyCompletion, + }; + + // variableDeclaration.qml tests for let/const/var statements + destructuring + + QTest::newRow("letStatement") << testFile(u"completions/variableDeclaration.qml"_s) << 7 << 13 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, + u"x"_s, u"data"_s }; + + QTest::newRow("letStatement2") + << testFile(u"completions/variableDeclaration.qml"_s) << 7 << 26 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("letStatementBehindEqual") + << testFile(u"completions/variableDeclaration.qml"_s) << 7 << 28 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("letStatementBehindEqual2") + << testFile(u"completions/variableDeclaration.qml"_s) << 7 << 33 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("constStatement") + << testFile(u"completions/variableDeclaration.qml"_s) << 8 << 19 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("constStatementBehindEqual") + << testFile(u"completions/variableDeclaration.qml"_s) << 8 << 32 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("varStatement") << testFile(u"completions/variableDeclaration.qml"_s) << 9 << 17 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, + u"x"_s, u"data"_s }; + + QTest::newRow("varStatementBehindEqual") + << testFile(u"completions/variableDeclaration.qml"_s) << 9 << 28 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("objectDeconstruction") + << testFile(u"completions/variableDeclaration.qml"_s) << 13 << 20 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("objectDeconstructionAloneBehindEqual") + << testFile(u"completions/variableDeclaration.qml"_s) << 14 << 51 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("objectDeconstructionAloneBehindEqual2") + << testFile(u"completions/variableDeclaration.qml"_s) << 14 << 58 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("objectDeconstruction2BehindEqual") + << testFile(u"completions/variableDeclaration.qml"_s) << 15 << 83 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("objectDeconstruction2BehindEqual2") + << testFile(u"completions/variableDeclaration.qml"_s) << 15 << 90 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("objectDeconstruction3BehindEqual") + << testFile(u"completions/variableDeclaration.qml"_s) << 15 << 140 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("objectDeconstructionBehindComma") + << testFile(u"completions/variableDeclaration.qml"_s) << 15 << 143 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("objectDeconstructionBetweenObjects") + << testFile(u"completions/variableDeclaration.qml"_s) << 15 << 50 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("objectDeconstructionBetweenDeconstructions") + << testFile(u"completions/variableDeclaration.qml"_s) << 15 << 97 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("arrayDeconstructionAlone") + << testFile(u"completions/variableDeclaration.qml"_s) << 19 << 24 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("arrayDeconstructionAloneBehindEqual") + << testFile(u"completions/variableDeclaration.qml"_s) << 19 << 33 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("arrayDeconstruction2") + << testFile(u"completions/variableDeclaration.qml"_s) << 21 << 71 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("arrayDeconstruction2BehindEqual") + << testFile(u"completions/variableDeclaration.qml"_s) << 21 << 83 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + QTest::newRow("arrayDeconstruction3") + << testFile(u"completions/variableDeclaration.qml"_s) << 21 << 125 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("arrayDeconstruction3BehindEqual") + << testFile(u"completions/variableDeclaration.qml"_s) << 21 << 139 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("arrayDeconstructionIn_Wildcard") + << testFile(u"completions/variableDeclaration.qml"_s) << 25 << 64 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("arrayDeconstructionBehind+") + << testFile(u"completions/variableDeclaration.qml"_s) << 25 << 132 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("objectDeconstructionForNeedle") + << testFile(u"completions/variableDeclaration.qml"_s) << 29 << 111 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("arrayInObjectDeconstructionInObjectInitializer") + << testFile(u"completions/variableDeclaration.qml"_s) << 33 << 44 + << ExpectedCompletions{ {u"x"_s, CompletionItemKind::Variable}, + {u"data"_s, CompletionItemKind::Method}, + } + << QStringList{ propertyCompletion, letStatementCompletion } + ; + + QTest::newRow("arrayInObjectDeconstructionInObjectPropertyName") + << testFile(u"completions/variableDeclaration.qml"_s) << 33 << 26 + << ExpectedCompletions{} + << QStringList{ propertyCompletion, letStatementCompletion, u"x"_s, u"data"_s }; + + QTest::newRow("throwStatement") + << testFile(u"completions/throwStatement.qml"_s) << 8 << 15 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method } } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("throwStatement2") + << testFile(u"completions/throwStatement.qml"_s) << 9 << 20 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method } } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("labelledStatement") + << testFile(u"completions/labelledStatement.qml"_s) << 5 << 16 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { forStatementCompletion, CompletionItemKind::Snippet }, + } + << QStringList{ propertyCompletion, }; + + QTest::newRow("nestedLabel") + << testFile(u"completions/labelledStatement.qml"_s) << 7 << 22 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { forStatementCompletion, CompletionItemKind::Snippet }, + } + << QStringList{ propertyCompletion, }; + + QTest::newRow("nestedLabel2") + << testFile(u"completions/labelledStatement.qml"_s) << 8 << 26 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { forStatementCompletion, CompletionItemKind::Snippet }, + } + << QStringList{ propertyCompletion, }; + + QTest::newRow("multiLabel") + << testFile(u"completions/labelledStatement.qml"_s) << 15 << 21 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { forStatementCompletion, CompletionItemKind::Snippet }, + } + << QStringList{ propertyCompletion, }; + + QTest::newRow("multiLabel2") + << testFile(u"completions/labelledStatement.qml"_s) << 16 << 21 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"f"_s, CompletionItemKind::Method }, + { letStatementCompletion, CompletionItemKind::Snippet }, + { forStatementCompletion, CompletionItemKind::Snippet }, + } + << QStringList{ propertyCompletion, }; + + QTest::newRow("continueNested") + << testFile(u"completions/continueAndBreakStatement.qml"_s) << 12 << 26 + << ExpectedCompletions{ { u"nestedLabel1"_s, CompletionItemKind::Value }, + { u"nestedLabel2"_s, CompletionItemKind::Value }, } + << QStringList{ propertyCompletion, u"x"_s, u"f"_s, u"multiLabel1"_s }; + + QTest::newRow("breakNested") + << testFile(u"completions/continueAndBreakStatement.qml"_s) << 13 << 23 + << ExpectedCompletions{ { u"nestedLabel1"_s, CompletionItemKind::Value }, + { u"nestedLabel2"_s, CompletionItemKind::Value }, } + << QStringList{ propertyCompletion, u"x"_s, u"f"_s, u"multiLabel1"_s }; + + QTest::newRow("continueMulti") + << testFile(u"completions/continueAndBreakStatement.qml"_s) << 20 << 22 + << ExpectedCompletions{ { u"multiLabel1"_s, CompletionItemKind::Value }, + { u"multiLabel2"_s, CompletionItemKind::Value }, } + << QStringList{ propertyCompletion, u"x"_s, u"f"_s, u"nestedLabel1"_s }; + + QTest::newRow("breakMulti") + << testFile(u"completions/continueAndBreakStatement.qml"_s) << 21 << 19 + << ExpectedCompletions{ { u"multiLabel1"_s, CompletionItemKind::Value }, + { u"multiLabel2"_s, CompletionItemKind::Value }, } + << QStringList{ propertyCompletion, u"x"_s, u"f"_s, u"nestedLabel1"_s }; + + QTest::newRow("continueNoLabel") << testFile(u"completions/continueAndBreakStatement.qml"_s) + << 25 << 22 << ExpectedCompletions{} + << QStringList{ propertyCompletion, u"x"_s, u"f"_s, + u"nestedLabel1"_s, u"multiLabel1"_s }; + + QTest::newRow("breakNoLabel") << testFile(u"completions/continueAndBreakStatement.qml"_s) << 26 + << 19 << ExpectedCompletions{} + << QStringList{ propertyCompletion, u"x"_s, u"f"_s, + u"nestedLabel1"_s, u"multiLabel1"_s }; + + QTest::newRow("insideMethodBody") + << testFile(u"completions/functionBody.qml"_s) << 5 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ propertyCompletion }; + + QTest::newRow("insideMethodBody2") + << testFile(u"completions/functionBody.qml"_s) << 11 << 11 + << ExpectedCompletions{ { u"helloProperty"_s, CompletionItemKind::Property }, } + << QStringList{ u"badProperty"_s, forStatementCompletion }; + + QTest::newRow("insideMethodBodyStart") + << testFile(u"completions/functionBody.qml"_s) << 11 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ u"helloProperty"_s }; + + QTest::newRow("insideMethodBodyEnd") + << testFile(u"completions/functionBody.qml"_s) << 12 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { forStatementCompletion, CompletionItemKind::Snippet } } + << QStringList{ u"helloProperty"_s }; + + QTest::newRow("noBreakInMethodBody") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 8 << 8 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable } } + << QStringList{ u"break"_s, u"continue"_s }; + + QTest::newRow("breakAndContinueInForLoop") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 11 << 12 + << ExpectedCompletions{ { u"break"_s, CompletionItemKind::Keyword }, + { u"continue"_s, CompletionItemKind::Keyword }, + } + << QStringList{}; + + QTest::newRow("noBreakInSwitch") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 15 << 12 + << ExpectedCompletions{ { caseStatementCompletion, CompletionItemKind::Snippet }, } + << QStringList{ u"continue"_s, u"break"_s }; + + QTest::newRow("breakInSwitchCase") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 17 << 12 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"break"_s, CompletionItemKind::Keyword }, + } + << QStringList{ u"continue"_s }; + + QTest::newRow("breakInSwitchDefault") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 19 << 12 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"break"_s, CompletionItemKind::Keyword }, + } + << QStringList{ u"continue"_s }; + + QTest::newRow("breakInSwitchSecondCase") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 21 << 12 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"break"_s, CompletionItemKind::Keyword }, + } + << QStringList{ u"continue"_s }; + + QTest::newRow("breakInLabel") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 25 << 12 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"break"_s, CompletionItemKind::Keyword }, + } + << QStringList{ u"continue"_s }; + + QTest::newRow("forLoopInsideOfLabel") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 33 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"break"_s, CompletionItemKind::Keyword }, + { u"continue"_s, CompletionItemKind::Keyword }, + } + << QStringList{ }; + + QTest::newRow("switchInsideForLoopInsideOfLabel") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 36 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"break"_s, CompletionItemKind::Keyword }, + { u"continue"_s, CompletionItemKind::Keyword }, + } + << QStringList{ }; + + QTest::newRow("switchInsideOfLabel") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 45 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"break"_s, CompletionItemKind::Keyword }, + } + << QStringList{ u"continue"_s }; + + QTest::newRow("forLoopInSwitchInsideOfLabel") + << testFile(u"completions/suggestContinueAndBreak.qml"_s) << 47 << 1 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + { u"break"_s, CompletionItemKind::Keyword }, + { u"continue"_s, CompletionItemKind::Keyword }, + } + << QStringList{ }; + + QTest::newRow("commaExpression") + << testFile(u"completions/commaExpression.qml"_s) << 5 << 18 + << ExpectedCompletions{ { u"a"_s, CompletionItemKind::Variable }, + { u"b"_s, CompletionItemKind::Variable }, + { u"c"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion }; + + QTest::newRow("conditionalExpressionConsequence") + << testFile(u"completions/conditionalExpression.qml"_s) << 5 << 17 + << ExpectedCompletions{ { u"a"_s, CompletionItemKind::Variable }, + { u"b"_s, CompletionItemKind::Variable }, + { u"c"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("conditionalExpressionAlternative") + << testFile(u"completions/conditionalExpression.qml"_s) << 5 << 30 + << ExpectedCompletions{ { u"a"_s, CompletionItemKind::Variable }, + { u"b"_s, CompletionItemKind::Variable }, + { u"c"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("unaryMinus") + << testFile(u"completions/unaryExpression.qml"_s) << 5 << 10 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("unaryPlus") + << testFile(u"completions/unaryExpression.qml"_s) << 6 << 10 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("unaryTilde") + << testFile(u"completions/unaryExpression.qml"_s) << 7 << 10 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("unaryNot") + << testFile(u"completions/unaryExpression.qml"_s) << 8 << 10 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("typeof") + << testFile(u"completions/unaryExpression.qml"_s) << 9 << 16 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("delete") + << testFile(u"completions/unaryExpression.qml"_s) << 10 << 16 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("void") + << testFile(u"completions/unaryExpression.qml"_s) << 11 << 14 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("postDecrement") + << testFile(u"completions/unaryExpression.qml"_s) << 12 << 9 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("postIncrement") + << testFile(u"completions/unaryExpression.qml"_s) << 13 << 9 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("preDecrement") + << testFile(u"completions/unaryExpression.qml"_s) << 14 << 11 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("preIncrement") + << testFile(u"completions/unaryExpression.qml"_s) << 15 << 11 + << ExpectedCompletions{ { u"x"_s, CompletionItemKind::Variable }, + } + << QStringList{ propertyCompletion, letStatementCompletion }; + + QTest::newRow("attachedPropertyAfterDot") + << testFile("completions/attachedAndGroupedProperty.qml") << 8 << 15 + << ExpectedCompletions({ + { u"onCompleted"_s, CompletionItemKind::Method }, + }) + << QStringList{ u"QtQuick"_s, u"vector4d"_s, attachedTypeName, u"Rectangle"_s, + u"bad"_s }; + + QTest::newRow("groupedPropertyAfterDot") + << testFile("completions/attachedAndGroupedProperty.qml") << 10 << 15 + << ExpectedCompletions({ + { u"family"_s, CompletionItemKind::Property }, + }) + << QStringList{ u"QtQuick"_s, u"vector4d"_s, attachedTypeName, u"Rectangle"_s, + u"bad"_s, u"onCompleted"_s }; + + QTest::newRow("attachedPropertyAfterDotMissingRHS") + << testFile("completions/attachedPropertyMissingRHS.qml") << 7 << 17 + << ExpectedCompletions({ + { u"onCompleted"_s, CompletionItemKind::Method }, + }) + << QStringList{ u"QtQuick"_s, u"vector4d"_s, attachedTypeName, u"Rectangle"_s, + u"bad"_s }; + + QTest::newRow("groupedPropertyAfterDotMissingRHS") + << testFile("completions/groupedPropertyMissingRHS.qml") << 7 << 11 + << ExpectedCompletions({ + { u"family"_s, CompletionItemKind::Property }, + }) + << QStringList{ u"QtQuick"_s, u"vector4d"_s, attachedTypeName, u"Rectangle"_s, + u"bad"_s, u"onCompleted"_s }; + + QTest::newRow("dotFollowedByDefaultBinding") + << testFile("completions/afterDots.qml") << 11 << 31 + << ExpectedCompletions({ + { u"good"_s, CompletionItemKind::Property }, + }) + << QStringList{ u"bad"_s, u"QtQuick"_s, u"vector4d"_s, + attachedTypeName, u"Rectangle"_s, u"onCompleted"_s }; + + QTest::newRow("dotFollowedByBinding") + << testFile("completions/afterDots.qml") << 13 << 32 + << ExpectedCompletions({ + { u"good"_s, CompletionItemKind::Property }, + }) + << QStringList{ u"bad"_s, u"QtQuick"_s, u"vector4d"_s, + attachedTypeName, u"Rectangle"_s, u"onCompleted"_s }; + + QTest::newRow("dotFollowedByForStatement") + << testFile("completions/afterDots.qml") << 16 << 17 + << ExpectedCompletions({ + { u"good"_s, CompletionItemKind::Property }, + }) + << QStringList{ + u"bad"_s, u"QtQuick"_s, u"vector4d"_s, attachedTypeName, + u"Rectangle"_s, u"onCompleted"_s, forStatementCompletion + }; + + QTest::newRow("qualifiedTypeCompletionWithoutQualifier") + << testFile("completions/quickcontrols_and_quicktemplates/qualifiedTypesCompletion.qml") + << 9 << 5 + << ExpectedCompletions({ + { u"T.Button"_s, CompletionItemKind::Constructor }, + { u"Button"_s, CompletionItemKind::Constructor }, + { u"Rectangle"_s, CompletionItemKind::Constructor }, + }) + << QStringList{ u"QtQuick"_s, u"vector4d"_s, u"bad"_s, u"onCompleted"_s }; + + QTest::newRow("qualifiedTypeCompletionWithoutQualifier2") + << testFile("completions/quickcontrols_and_quicktemplates/qualifiedTypesCompletion.qml") + << 10 << 19 + << ExpectedCompletions({ + { u"T.Button"_s, CompletionItemKind::Class }, + { u"Button"_s, CompletionItemKind::Class }, + { u"Rectangle"_s, CompletionItemKind::Class }, + }) + << QStringList{ u"QtQuick"_s, u"bad"_s, u"onCompleted"_s }; + + QTest::newRow("qualifiedTypeCompletionWithQualifier") + << testFile("completions/quickcontrols_and_quicktemplates/qualifiedTypesCompletion.qml") + << 9 << 7 + << ExpectedCompletions({ + { u"Button"_s, CompletionItemKind::Constructor }, + }) + << QStringList{ u"QtQuick"_s, u"vector4d"_s, attachedTypeName, u"Rectangle"_s, + u"bad"_s, u"onCompleted"_s, u"T.Button"_s }; + + QTest::newRow("qualifiedTypeCompletionWithQualifier2") + << testFile("completions/quickcontrols_and_quicktemplates/qualifiedTypesCompletion.qml") + << 10 << 21 + << ExpectedCompletions({ + { u"Button"_s, CompletionItemKind::Class }, + }) + << QStringList{ u"QtQuick"_s, attachedTypeName, u"Rectangle"_s, + u"bad"_s, u"onCompleted"_s, u"T.Button"_s }; + + QTest::newRow("parenthesizedExpression") + << testFile("completions/parenthesizedExpression.qml") << 8 << 10 + << ExpectedCompletions({ + { u"x"_s, CompletionItemKind::Variable }, + }) + << QStringList{ u"QtQuick"_s, u"Rectangle"_s, forStatementCompletion }; + + QTest::newRow("behindParenthesizedExpression") + << testFile("completions/parenthesizedExpression.qml") << 8 << 16 + << ExpectedCompletions({}) + << QStringList{ u"QtQuick"_s, attachedTypeName, u"Rectangle"_s, forStatementCompletion, + u"x"_s }; + + QTest::newRow("assumeBoundComponentsIdFromParent") + << testFile("completions/boundComponents.qml") << 14 << 33 + << ExpectedCompletions{ { u"rootId"_s, CompletionItemKind::Value } } + << QStringList{ u"inRoot"_s }; + + QTest::newRow("assumeBoundComponentsPropertyFromParent") + << testFile("completions/boundComponents.qml") << 14 << 40 + << ExpectedCompletions{ { u"inRoot"_s, CompletionItemKind::Property } } + << QStringList{ u"root"_s }; +} + +void tst_qmlls_utils::completions() +{ + QFETCH(QString, filePath); + QFETCH(int, line); + QFETCH(int, character); + QFETCH(ExpectedCompletions, expected); + QFETCH(QStringList, notExpected); + + auto [env, file] = createEnvironmentAndLoadFile(filePath); + + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1); + + QEXPECT_FAIL("binaryExpressionMissingRHSWithSemicolon", + "Current parser cannot recover from this error yet!", Abort); + QEXPECT_FAIL("binaryExpressionMissingRHSWithStatement", + "Current parser cannot recover from this error yet!", Abort); + QCOMPARE(locations.size(), 1); + + QString code; + { + QFile file(filePath); + QVERIFY(file.open(QIODeviceBase::ReadOnly)); + code = QString::fromUtf8(file.readAll()); + } + + qsizetype pos = QQmlLSUtils::textOffsetFrom(code, line - 1, character - 1); + CompletionContextStrings ctxt{ code, pos }; + QQmlLSCompletion completionEngine(m_pluginLoader); + QList<CompletionItem> completions = + completionEngine.completions(locations.front().domItem, ctxt); + + if (expected.isEmpty()) { + if constexpr (enable_debug_output) { + if (!completions.isEmpty()) { + QStringList unexpected; + for (const auto ¤t : completions) { + unexpected << current.label; + } + qDebug() << "Received unexpected completions:" << unexpected.join(u", "); + } + } + QEXPECT_FAIL("singleton", "completion not implemented yet!", Abort); + QVERIFY(completions.isEmpty()); + return; + } + + QSet<QString> labels; + QStringList sortedLabels; + QDuplicateTracker<QByteArray> modulesTracker; + QDuplicateTracker<QByteArray> keywordsTracker; + QDuplicateTracker<QByteArray> classesTracker; + QDuplicateTracker<QByteArray> fieldsTracker; + QDuplicateTracker<QByteArray> propertiesTracker; + QDuplicateTracker<QByteArray> snippetTracker; + + // avoid QEXPECT_FAIL tests to XPASS when completion order changes + std::sort(completions.begin(), completions.end(), + [](const CompletionItem&a, const CompletionItem&b) {return a.label < b.label;}); + + for (const CompletionItem &c : completions) { + // explicitly forbid marker structs created by QQmlJSImporter + QVERIFY(!c.label.contains("$internal$.")); + QVERIFY(!c.label.contains("$module$.")); + QVERIFY(!c.label.contains("$anonymous$.")); + + if (c.kind->toInt() == int(CompletionItemKind::Module)) { + QVERIFY2(!modulesTracker.hasSeen(c.label), "Duplicate module: " + c.label); + } else if (c.kind->toInt() == int(CompletionItemKind::Keyword)) { + QVERIFY2(!keywordsTracker.hasSeen(c.label), "Duplicate keyword: " + c.label); + } else if (c.kind->toInt() == int(CompletionItemKind::Class)) { + QVERIFY2(!classesTracker.hasSeen(c.label), "Duplicate class: " + c.label); + } else if (c.kind->toInt() == int(CompletionItemKind::Field)) { + QVERIFY2(!fieldsTracker.hasSeen(c.label), "Duplicate field: " + c.label); + } else if (c.kind->toInt() == int(CompletionItemKind::Snippet)) { + QVERIFY2(!snippetTracker.hasSeen(c.label), "Duplicate field: " + c.label); + if (c.insertText->contains('\n') || c.insertText->contains('\r')) { + QCOMPARE(c.insertTextMode, InsertTextMode::AdjustIndentation); + } + } else if (c.kind->toInt() == int(CompletionItemKind::Property)) { + QVERIFY2(!propertiesTracker.hasSeen(c.label), "Duplicate property: " + c.label); + QCOMPARE(c.insertText, std::nullopt); + } + labels << c.label; + sortedLabels << c.label; + } + const QString labelsForPrinting = sortedLabels.join(u", "_s); + + for (const ExpectedCompletion &exp : expected) { + QEXPECT_FAIL("letStatementAfterEqual", "Completion not implemented yet!", Abort); + + QVERIFY2(labels.contains(exp.label), + u"no %1 in %2"_s.arg(exp.label, labelsForPrinting).toUtf8()); + if (labels.contains(exp.label)) { + + bool foundEntry = false; + bool hasCorrectKind = false; + CompletionItemKind foundKind; + for (const CompletionItem &c : completions) { + if (c.label == exp.label) { + foundKind = static_cast<CompletionItemKind>(c.kind->toInt()); + foundEntry = true; + if (foundKind == exp.kind) { + hasCorrectKind = true; + if (!exp.snippet.isEmpty()) { + QCOMPARE(QString::fromUtf8(c.insertText.value_or(QByteArray())), + exp.snippet); + } + break; + } + } + } + + // Ignore QVERIFY for those completions not in the expected list. + if (!foundEntry) + continue; + + QVERIFY2(hasCorrectKind, + qPrintable(QString::fromLatin1("Completion item '%1' has wrong kind '%2'") + .arg(exp.label) + .arg(QMetaEnum::fromType<CompletionItemKind>().valueToKey( + int(foundKind))))); + } + } + for (const QString &nexp : notExpected) { + QEXPECT_FAIL("ignoreNonRelatedTypesForPropertyDefinitionBinding", + "Filtering by Type not implemented yet, for example to avoid proposing " + "binding Items to Rectangle properties.", + Abort); + QVERIFY2(!labels.contains(nexp), u"found unexpected completion %1"_s.arg(nexp).toUtf8()); + } +} + +void tst_qmlls_utils::cmakeBuildCommand() +{ + const QString path = u"helloWorldPath"_s; + const QPair<QString, QStringList> expected{ + u"cmake"_s, { u"--build"_s, path, u"-t"_s, u"all_qmltyperegistrations"_s } + }; + QCOMPARE(QQmlLSUtils::cmakeBuildCommand(path), expected); +} + +QTEST_MAIN(tst_qmlls_utils) |