2444 lines
120 KiB
C++
2444 lines
120 KiB
C++
// Copyright (C) 2016 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sergio Martins <sergio.martins@kdab.com>
|
||
// Copyright (C) 2021 The Qt Company Ltd.
|
||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||
|
||
#include <QtTest/QtTest>
|
||
#include <QProcess>
|
||
#include <QString>
|
||
#include <QtQuickTestUtils/private/qmlutils_p.h>
|
||
#include <QtQmlCompiler/private/qqmljslinter_p.h>
|
||
#include <QtQmlCompiler/private/qqmlsa_p.h>
|
||
#include <QtCore/qplugin.h>
|
||
#include <QtCore/qcomparehelpers.h>
|
||
|
||
Q_IMPORT_PLUGIN(LintPlugin)
|
||
|
||
using namespace Qt::StringLiterals;
|
||
|
||
class TestQmllint: public QQmlDataTest
|
||
{
|
||
Q_OBJECT
|
||
|
||
public:
|
||
TestQmllint();
|
||
|
||
struct Message
|
||
{
|
||
QString text = QString();
|
||
quint32 line = 0, column = 0;
|
||
QtMsgType severity = QtWarningMsg;
|
||
};
|
||
|
||
struct Result
|
||
{
|
||
enum Flag { ExitsNormally = 0x1, NoMessages = 0x2, AutoFixable = 0x4, HasDuplicates = 0x8 };
|
||
|
||
Q_DECLARE_FLAGS(Flags, Flag)
|
||
|
||
static Result clean() { return Result { {}, {}, {}, { NoMessages, ExitsNormally } }; }
|
||
|
||
QList<Message> expectedMessages = {};
|
||
QList<Message> badMessages = {};
|
||
QList<Message> expectedReplacements = {};
|
||
|
||
Flags flags = {};
|
||
};
|
||
|
||
struct Environment : public QList<QPair<QString, QString>>
|
||
{
|
||
using QList<QPair<QString, QString>>::QList;
|
||
};
|
||
|
||
private Q_SLOTS:
|
||
void initTestCase() override;
|
||
|
||
void testUnqualified();
|
||
void testUnqualified_data();
|
||
|
||
void cleanQmlCode_data();
|
||
void cleanQmlCode();
|
||
|
||
void dirtyQmlCode_data();
|
||
void dirtyQmlCode();
|
||
|
||
void compilerWarnings_data();
|
||
void compilerWarnings();
|
||
|
||
void testUnknownCausesFail();
|
||
|
||
void directoryPassedAsQmlTypesFile();
|
||
void oldQmltypes();
|
||
|
||
void qmltypes_data();
|
||
void qmltypes();
|
||
|
||
#ifdef QT_QMLJSROOTGEN_PRESENT
|
||
void verifyJsRoot();
|
||
#endif
|
||
|
||
void autoqmltypes();
|
||
void resources();
|
||
|
||
void multiDirectory();
|
||
|
||
void requiredProperty();
|
||
|
||
void settingsFile();
|
||
|
||
void additionalImplicitImport();
|
||
|
||
void qrcUrlImport();
|
||
|
||
void incorrectImportFromHost_data();
|
||
void incorrectImportFromHost();
|
||
|
||
void attachedPropertyReuse();
|
||
|
||
void missingBuiltinsNoCrash();
|
||
void absolutePath();
|
||
|
||
void importMultipartUri();
|
||
|
||
void lintModule_data();
|
||
void lintModule();
|
||
|
||
void testLineEndings();
|
||
void valueTypesFromString();
|
||
|
||
void ignoreSettingsNotCommandLineOptions();
|
||
void backslashedQmldirPath();
|
||
|
||
void environment_data();
|
||
void environment();
|
||
|
||
void maxWarnings();
|
||
|
||
#if QT_CONFIG(library)
|
||
void testPlugin();
|
||
void quickPlugin();
|
||
#endif
|
||
|
||
#if QT_CONFIG(process)
|
||
void importRelScript();
|
||
#endif
|
||
|
||
private:
|
||
enum DefaultImportOption { NoDefaultImports, UseDefaultImports };
|
||
enum ContainOption { StringNotContained, StringContained };
|
||
enum ReplacementOption {
|
||
NoReplacementSearch,
|
||
DoReplacementSearch,
|
||
};
|
||
|
||
enum LintType { LintFile, LintModule };
|
||
|
||
static QStringList warningsShouldFailArgs() {
|
||
static QStringList args {"-W", "0"};
|
||
return args;
|
||
}
|
||
|
||
QString runQmllint(const QString &fileToLint, std::function<void(QProcess &)> handleResult,
|
||
const QStringList &extraArgs = QStringList(), bool ignoreSettings = true,
|
||
bool addImportDirs = true, bool absolutePath = true,
|
||
const Environment &env = {});
|
||
QString runQmllint(const QString &fileToLint, bool shouldSucceed,
|
||
const QStringList &extraArgs = QStringList(), bool ignoreSettings = true,
|
||
bool addImportDirs = true, bool absolutePath = true,
|
||
const Environment &env = {});
|
||
void callQmllint(const QString &fileToLint, bool shouldSucceed, QJsonArray *warnings = nullptr,
|
||
QStringList importDirs = {}, QStringList qmltypesFiles = {},
|
||
QStringList resources = {},
|
||
DefaultImportOption defaultImports = UseDefaultImports,
|
||
QList<QQmlJS::LoggerCategory> *categories = nullptr, bool autoFixable = false,
|
||
LintType type = LintFile);
|
||
|
||
void searchWarnings(const QJsonArray &warnings, const QString &string,
|
||
QtMsgType type = QtWarningMsg, quint32 line = 0, quint32 column = 0,
|
||
ContainOption shouldContain = StringContained,
|
||
ReplacementOption searchReplacements = NoReplacementSearch);
|
||
|
||
template<typename ExpectedMessageFailureHandler, typename BadMessageFailureHandler,
|
||
typename ReplacementFailureHandler>
|
||
void checkResult(const QJsonArray &warnings, const Result &result,
|
||
ExpectedMessageFailureHandler onExpectedMessageFailures,
|
||
BadMessageFailureHandler onBadMessageFailures,
|
||
ReplacementFailureHandler onReplacementFailures);
|
||
|
||
void checkResult(const QJsonArray &warnings, const Result &result)
|
||
{
|
||
checkResult(
|
||
warnings, result, [] {}, [] {}, [] {});
|
||
}
|
||
|
||
void runTest(const QString &testFile, const Result &result, QStringList importDirs = {},
|
||
QStringList qmltypesFiles = {}, QStringList resources = {},
|
||
DefaultImportOption defaultImports = UseDefaultImports,
|
||
QList<QQmlJS::LoggerCategory> *categories = nullptr);
|
||
|
||
QString m_qmllintPath;
|
||
QString m_qmljsrootgenPath;
|
||
QString m_qmltyperegistrarPath;
|
||
|
||
QStringList m_defaultImportPaths;
|
||
QQmlJSLinter m_linter;
|
||
};
|
||
|
||
Q_DECLARE_METATYPE(TestQmllint::Result)
|
||
|
||
TestQmllint::TestQmllint()
|
||
: QQmlDataTest(QT_QMLTEST_DATADIR),
|
||
m_defaultImportPaths({ QLibraryInfo::path(QLibraryInfo::QmlImportsPath), dataDirectory() }),
|
||
m_linter(m_defaultImportPaths)
|
||
|
||
{
|
||
}
|
||
|
||
void TestQmllint::initTestCase()
|
||
{
|
||
QQmlDataTest::initTestCase();
|
||
m_qmllintPath = QLibraryInfo::path(QLibraryInfo::BinariesPath) + QLatin1String("/qmllint");
|
||
m_qmljsrootgenPath = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath)
|
||
+ QLatin1String("/qmljsrootgen");
|
||
m_qmltyperegistrarPath = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath)
|
||
+ QLatin1String("/qmltyperegistrar");
|
||
#ifdef Q_OS_WIN
|
||
m_qmllintPath += QLatin1String(".exe");
|
||
m_qmljsrootgenPath += QLatin1String(".exe");
|
||
m_qmltyperegistrarPath += QLatin1String(".exe");
|
||
#endif
|
||
if (!QFileInfo(m_qmllintPath).exists()) {
|
||
QString message = QStringLiteral("qmllint executable not found (looked for %0)").arg(m_qmllintPath);
|
||
QFAIL(qPrintable(message));
|
||
}
|
||
|
||
#ifdef QT_QMLJSROOTGEN_PRESENT
|
||
if (!QFileInfo(m_qmljsrootgenPath).exists()) {
|
||
QString message = QStringLiteral("qmljsrootgen executable not found (looked for %0)").arg(m_qmljsrootgenPath);
|
||
QFAIL(qPrintable(message));
|
||
}
|
||
if (!QFileInfo(m_qmltyperegistrarPath).exists()) {
|
||
QString message = QStringLiteral("qmltypesregistrar executable not found (looked for %0)").arg(m_qmltyperegistrarPath);
|
||
QFAIL(qPrintable(message));
|
||
}
|
||
#endif
|
||
}
|
||
|
||
void TestQmllint::testUnqualified()
|
||
{
|
||
QFETCH(QString, filename);
|
||
QFETCH(Result, result);
|
||
|
||
runTest(filename, result);
|
||
}
|
||
|
||
void TestQmllint::testUnqualified_data()
|
||
{
|
||
QTest::addColumn<QString>("filename");
|
||
QTest::addColumn<Result>("result");
|
||
|
||
// id from nowhere (as with setContextProperty)
|
||
QTest::newRow("IdFromOuterSpace")
|
||
<< QStringLiteral("IdFromOuterSpace.qml")
|
||
<< Result { { Message { QStringLiteral("Unqualified access"), 4, 8 },
|
||
Message { QStringLiteral("Unqualified access"), 7, 21 } } };
|
||
// access property of root object
|
||
QTest::newRow("FromRootDirect")
|
||
<< QStringLiteral("FromRoot.qml")
|
||
<< Result {
|
||
{
|
||
Message { QStringLiteral("Unqualified access"), 9, 16 }, // new property
|
||
Message { QStringLiteral("Unqualified access"), 13,
|
||
33 } // builtin property
|
||
},
|
||
{},
|
||
{ { Message { u"root."_s, 9, 16 } }, { Message { u"root."_s, 13, 33 } } }
|
||
};
|
||
// access injected name from signal
|
||
QTest::newRow("SignalHandler")
|
||
<< QStringLiteral("SignalHandler.qml")
|
||
<< Result { {
|
||
Message { QStringLiteral("Unqualified access"), 5, 21 },
|
||
Message { QStringLiteral("Unqualified access"), 10, 21 },
|
||
Message { QStringLiteral("Unqualified access"), 8, 29 },
|
||
Message { QStringLiteral("Unqualified access"), 12, 34 },
|
||
},
|
||
{},
|
||
{
|
||
Message { QStringLiteral("function(mouse)"), 4, 22 },
|
||
Message { QStringLiteral("function(mouse)"), 9, 24 },
|
||
Message { QStringLiteral("(mouse) => "), 8, 16 },
|
||
Message { QStringLiteral("(mouse) => "), 12, 21 },
|
||
} };
|
||
// access catch identifier outside catch block
|
||
QTest::newRow("CatchStatement")
|
||
<< QStringLiteral("CatchStatement.qml")
|
||
<< Result { { Message { QStringLiteral("Unqualified access"), 6, 21 } } };
|
||
QTest::newRow("NonSpuriousParent")
|
||
<< QStringLiteral("nonSpuriousParentWarning.qml")
|
||
<< Result { {
|
||
Message { QStringLiteral("Unqualified access"), 6, 25 },
|
||
},
|
||
{},
|
||
{ { Message { u"<id>."_s, 6, 25 } } } };
|
||
|
||
QTest::newRow("crashConnections")
|
||
<< QStringLiteral("crashConnections.qml")
|
||
<< Result { { Message { QStringLiteral("Unqualified access"), 4, 13 } } };
|
||
|
||
QTest::newRow("delegateContextProperties")
|
||
<< QStringLiteral("delegateContextProperties.qml")
|
||
<< Result { { Message { QStringLiteral("Unqualified access"), 6, 14 },
|
||
Message { QStringLiteral("Unqualified access"), 7, 15 },
|
||
Message { QStringLiteral("model is implicitly injected into this "
|
||
"delegate. Add a required property instead.") },
|
||
Message {
|
||
QStringLiteral("index is implicitly injected into this delegate. "
|
||
"Add a required property instead.") } } };
|
||
QTest::newRow("storeSloppy")
|
||
<< QStringLiteral("UnqualifiedInStoreSloppy.qml")
|
||
<< Result{ { Message{ QStringLiteral("Unqualified access"), 9, 26} } };
|
||
QTest::newRow("storeStrict")
|
||
<< QStringLiteral("UnqualifiedInStoreStrict.qml")
|
||
<< Result{ { Message{ QStringLiteral("Unqualified access"), 9, 52} } };
|
||
}
|
||
|
||
void TestQmllint::testUnknownCausesFail()
|
||
{
|
||
runTest("unknownElement.qml",
|
||
Result { { Message {
|
||
QStringLiteral("Unknown was not found. Did you add all import paths?"), 4, 5,
|
||
QtWarningMsg } } });
|
||
runTest("TypeWithUnknownPropertyType.qml",
|
||
Result { { Message {
|
||
QStringLiteral("Something was not found. Did you add all import paths?"), 4, 5,
|
||
QtWarningMsg } } });
|
||
}
|
||
|
||
void TestQmllint::directoryPassedAsQmlTypesFile()
|
||
{
|
||
runTest("unknownElement.qml",
|
||
Result { { Message { QStringLiteral("QML types file cannot be a directory: ")
|
||
+ dataDirectory() } } },
|
||
{}, { dataDirectory() });
|
||
}
|
||
|
||
void TestQmllint::oldQmltypes()
|
||
{
|
||
runTest("oldQmltypes.qml",
|
||
Result { {
|
||
Message { QStringLiteral("typeinfo not declared in qmldir file") },
|
||
Message {
|
||
QStringLiteral("Found deprecated dependency specifications") },
|
||
Message { QStringLiteral(
|
||
"Meta object revision and export version differ.") },
|
||
Message { QStringLiteral(
|
||
"Revision 0 corresponds to version 0.0; it should be 1.0.") },
|
||
},
|
||
{ Message { QStringLiteral(
|
||
"QQuickItem was not found. Did you add all import paths?") } } });
|
||
}
|
||
|
||
void TestQmllint::qmltypes_data()
|
||
{
|
||
QTest::addColumn<QString>("file");
|
||
|
||
const QString importsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
|
||
QDirIterator it(importsPath, { "*.qmltypes" },
|
||
QDir::Files, QDirIterator::Subdirectories);
|
||
while (it.hasNext())
|
||
QTest::addRow("%s", qPrintable(it.next().mid(importsPath.size()))) << it.filePath();
|
||
}
|
||
|
||
void TestQmllint::qmltypes()
|
||
{
|
||
QFETCH(QString, file);
|
||
// pass the warnings in, so that callQmllint() would show errors if any
|
||
QJsonArray warnings;
|
||
callQmllint(file, true, &warnings);
|
||
}
|
||
|
||
#ifdef QT_QMLJSROOTGEN_PRESENT
|
||
void TestQmllint::verifyJsRoot()
|
||
{
|
||
QProcess process;
|
||
|
||
const QString importsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
|
||
QDirIterator it(importsPath, { "jsroot.qmltypes" },
|
||
QDir::Files, QDirIterator::Subdirectories);
|
||
|
||
QVERIFY(it.hasNext());
|
||
|
||
QString currentJsRootPath = it.next();
|
||
|
||
QTemporaryDir dir;
|
||
|
||
QProcess jsrootProcess;
|
||
connect(&jsrootProcess, &QProcess::errorOccurred, [&](QProcess::ProcessError error) {
|
||
qWarning() << error << jsrootProcess.errorString();
|
||
});
|
||
jsrootProcess.setWorkingDirectory(dir.path());
|
||
jsrootProcess.start(m_qmljsrootgenPath, {"jsroot.json"});
|
||
|
||
jsrootProcess.waitForFinished();
|
||
|
||
QCOMPARE(jsrootProcess.exitStatus(), QProcess::NormalExit);
|
||
QCOMPARE(jsrootProcess.exitCode(), 0);
|
||
|
||
|
||
QProcess typeregistrarProcess;
|
||
typeregistrarProcess.setWorkingDirectory(dir.path());
|
||
typeregistrarProcess.start(m_qmltyperegistrarPath, {"jsroot.json", "--generate-qmltypes", "jsroot.qmltypes"});
|
||
|
||
typeregistrarProcess.waitForFinished();
|
||
|
||
QCOMPARE(typeregistrarProcess.exitStatus(), QProcess::NormalExit);
|
||
QCOMPARE(typeregistrarProcess.exitCode(), 0);
|
||
|
||
QString currentJsRootContent, generatedJsRootContent;
|
||
|
||
QFile currentJsRoot(currentJsRootPath);
|
||
QVERIFY(currentJsRoot.open(QFile::ReadOnly | QIODevice::Text));
|
||
currentJsRootContent = QString::fromUtf8(currentJsRoot.readAll());
|
||
currentJsRoot.close();
|
||
|
||
QFile generatedJsRoot(dir.path() + QDir::separator() + "jsroot.qmltypes");
|
||
QVERIFY(generatedJsRoot.open(QFile::ReadOnly | QIODevice::Text));
|
||
generatedJsRootContent = QString::fromUtf8(generatedJsRoot.readAll());
|
||
generatedJsRoot.close();
|
||
|
||
// If any of the following asserts fail you need to update jsroot.qmltypes using the following commands:
|
||
//
|
||
// qmljsrootgen jsroot.json
|
||
// qmltyperegistrar jsroot.json --generate-qmltypes src/imports/builtins/jsroot.qmltypes
|
||
QStringList currentLines = currentJsRootContent.split(QLatin1Char('\n'));
|
||
QStringList generatedLines = generatedJsRootContent.split(QLatin1Char('\n'));
|
||
|
||
QCOMPARE(currentLines.size(), generatedLines.size());
|
||
|
||
for (qsizetype i = 0; i < currentLines.size(); i++) {
|
||
QCOMPARE(currentLines[i], generatedLines[i]);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
void TestQmllint::autoqmltypes()
|
||
{
|
||
QProcess process;
|
||
process.setWorkingDirectory(testFile("autoqmltypes"));
|
||
process.start(m_qmllintPath, warningsShouldFailArgs() << QStringLiteral("test.qml") );
|
||
|
||
process.waitForFinished();
|
||
|
||
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
|
||
QVERIFY(process.exitCode() != 0);
|
||
|
||
QVERIFY(process.readAllStandardError()
|
||
.contains("is not a qmldir file. Assuming qmltypes"));
|
||
QVERIFY(process.readAllStandardOutput().isEmpty());
|
||
|
||
{
|
||
QProcess bare;
|
||
bare.setWorkingDirectory(testFile("autoqmltypes"));
|
||
bare.start(m_qmllintPath, warningsShouldFailArgs() << QStringLiteral("--bare") << QStringLiteral("test.qml") );
|
||
bare.waitForFinished();
|
||
|
||
const QByteArray errors = bare.readAllStandardError();
|
||
QVERIFY(!errors.contains("is not a qmldir file. Assuming qmltypes"));
|
||
QVERIFY(errors.contains("Failed to import TestTest."));
|
||
QVERIFY(bare.readAllStandardOutput().isEmpty());
|
||
|
||
QCOMPARE(bare.exitStatus(), QProcess::NormalExit);
|
||
QVERIFY(bare.exitCode() != 0);
|
||
}
|
||
}
|
||
|
||
void TestQmllint::resources()
|
||
{
|
||
{
|
||
// We need to clear the import cache before we add a qrc file with different
|
||
// contents for the same paths.
|
||
const auto guard = qScopeGuard([this]() { m_linter.clearCache(); });
|
||
|
||
callQmllint(testFile("resource.qml"), true, nullptr, {}, {}, { testFile("resource.qrc") });
|
||
callQmllint(testFile("badResource.qml"), false, nullptr, {}, {}, { testFile("resource.qrc") });
|
||
}
|
||
|
||
|
||
callQmllint(testFile("resource.qml"), false);
|
||
callQmllint(testFile("badResource.qml"), true);
|
||
|
||
{
|
||
const auto guard = qScopeGuard([this]() { m_linter.clearCache(); });
|
||
callQmllint(testFile("T/b.qml"), true, nullptr, {}, {}, { testFile("T/a.qrc") });
|
||
}
|
||
|
||
{
|
||
const auto guard = qScopeGuard([this]() { m_linter.clearCache(); });
|
||
callQmllint(testFile("relPathQrc/Foo/Thing.qml"), true, nullptr, {}, {},
|
||
{ testFile("relPathQrc/resources.qrc") });
|
||
}
|
||
}
|
||
|
||
void TestQmllint::multiDirectory()
|
||
{
|
||
callQmllint(
|
||
testFile("MultiDirectory/qml/Inner.qml"), true, nullptr,
|
||
{}, {}, { testFile("MultiDirectory/multi.qrc") });
|
||
|
||
callQmllint(
|
||
testFile("MultiDirectory/qml/pages/Page.qml"), true, nullptr,
|
||
{}, {}, { testFile("MultiDirectory/multi.qrc") });
|
||
}
|
||
|
||
void TestQmllint::dirtyQmlCode_data()
|
||
{
|
||
QTest::addColumn<QString>("filename");
|
||
QTest::addColumn<Result>("result");
|
||
|
||
QTest::newRow("Invalid_syntax_QML")
|
||
<< QStringLiteral("failure1.qml")
|
||
<< Result { { Message { QStringLiteral("Expected token `:'"), 4, 8, QtCriticalMsg } } };
|
||
QTest::newRow("Invalid_syntax_JS") << QStringLiteral("failure1.js")
|
||
<< Result { { Message { QStringLiteral("Expected token `;'"),
|
||
4, 12, QtCriticalMsg } } };
|
||
QTest::newRow("AutomatchedSignalHandler")
|
||
<< QStringLiteral("AutomatchedSignalHandler.qml")
|
||
<< Result { { Message { QStringLiteral("Unqualified access"), 12, 36 } } };
|
||
QTest::newRow("AutomatchedSignalHandler2")
|
||
<< QStringLiteral("AutomatchedSignalHandler.qml")
|
||
<< Result{ { Message{
|
||
QStringLiteral(
|
||
"Implicitly defining \"onClicked\" as signal handler in Connections "
|
||
"is deprecated. "
|
||
"Create a function instead: \"function onClicked() { ... }\"")} } };
|
||
QTest::newRow("MemberNotFound")
|
||
<< QStringLiteral("memberNotFound.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Member \"foo\" not found on type \"QtObject\""), 6,
|
||
31 } } };
|
||
QTest::newRow("UnknownJavascriptMethd")
|
||
<< QStringLiteral("unknownJavascriptMethod.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Member \"foo2\" not found on type \"Methods\""), 5,
|
||
25 } } };
|
||
QTest::newRow("badAlias")
|
||
<< QStringLiteral("badAlias.qml")
|
||
<< Result { { Message { QStringLiteral("Cannot resolve alias \"wrong\""), 3, 1 } } };
|
||
QTest::newRow("badAliasProperty1")
|
||
<< QStringLiteral("badAliasProperty.qml")
|
||
<< Result { { Message { QStringLiteral("Cannot resolve alias \"wrong\""), 3, 1 } } };
|
||
QTest::newRow("badAliasExpression")
|
||
<< QStringLiteral("badAliasExpression.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Invalid alias expression. Only IDs and field member "
|
||
"expressions can be aliased"),
|
||
5, 26 } } };
|
||
QTest::newRow("badAliasNotAnExpression")
|
||
<< QStringLiteral("badAliasNotAnExpression.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Invalid alias expression. Only IDs and field member "
|
||
"expressions can be aliased"),
|
||
4, 30 } } };
|
||
QTest::newRow("aliasCycle1") << QStringLiteral("aliasCycle.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Alias \"b\" is part of an alias cycle"),
|
||
3, 1 } } };
|
||
QTest::newRow("aliasCycle2") << QStringLiteral("aliasCycle.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Alias \"a\" is part of an alias cycle"),
|
||
3, 1 } } };
|
||
QTest::newRow("invalidAliasTarget1") << QStringLiteral("invalidAliasTarget.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Invalid alias expression – an initalizer is needed."),
|
||
6, 18 } } };
|
||
QTest::newRow("invalidAliasTarget2") << QStringLiteral("invalidAliasTarget.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Invalid alias expression. Only IDs and field member expressions can be aliased"),
|
||
7, 30 } } };
|
||
QTest::newRow("invalidAliasTarget3") << QStringLiteral("invalidAliasTarget.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Invalid alias expression. Only IDs and field member expressions can be aliased"),
|
||
9, 34 } } };
|
||
QTest::newRow("badParent")
|
||
<< QStringLiteral("badParent.qml")
|
||
<< Result { { Message { QStringLiteral("Member \"rrr\" not found on type \"Item\""),
|
||
5, 34 } } };
|
||
QTest::newRow("parentIsComponent")
|
||
<< QStringLiteral("parentIsComponent.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Member \"progress\" not found on type \"QQuickItem\""), 7,
|
||
39 } } };
|
||
QTest::newRow("badTypeAssertion")
|
||
<< QStringLiteral("badTypeAssertion.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Member \"rrr\" not found on type \"QQuickItem\""), 5,
|
||
39 } } };
|
||
QTest::newRow("incompleteQmltypes")
|
||
<< QStringLiteral("incompleteQmltypes.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Type \"QPalette\" of property \"palette\" not found"), 5,
|
||
26 } } };
|
||
QTest::newRow("incompleteQmltypes2")
|
||
<< QStringLiteral("incompleteQmltypes2.qml")
|
||
<< Result { { Message { QStringLiteral("Member \"weDontKnowIt\" "
|
||
"not found on type \"CustomPalette\""),
|
||
5, 35 } } };
|
||
QTest::newRow("incompleteQmltypes3")
|
||
<< QStringLiteral("incompleteQmltypes3.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Type \"QPalette\" of property \"palette\" not found"), 5,
|
||
21 } } };
|
||
QTest::newRow("inheritanceCycle")
|
||
<< QStringLiteral("Cycle1.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cycle1 is part of an inheritance cycle: Cycle2 -> Cycle3 "
|
||
"-> Cycle1 -> Cycle2"),
|
||
2, 1 } } };
|
||
QTest::newRow("badQmldirImportAndDepend")
|
||
<< QStringLiteral("qmldirImportAndDepend/bad.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Item was not found. Did you add all import paths?"), 3,
|
||
1 } } };
|
||
QTest::newRow("javascriptMethodsInModule")
|
||
<< QStringLiteral("javascriptMethodsInModuleBad.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Member \"unknownFunc\" not found on type \"Foo\""), 5,
|
||
21 } } };
|
||
QTest::newRow("badEnumFromQtQml")
|
||
<< QStringLiteral("badEnumFromQtQml.qml")
|
||
<< Result { { Message { QStringLiteral("Member \"Linear123\" not "
|
||
"found on type \"QQmlEasingEnums\""),
|
||
4, 30 } } };
|
||
QTest::newRow("anchors3")
|
||
<< QStringLiteral("anchors3.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Cannot assign binding of type QQuickItem to QQuickAnchorLine") } } };
|
||
QTest::newRow("nanchors1") << QStringLiteral("nanchors1.qml")
|
||
<< Result{ { Message{ QStringLiteral(
|
||
"unknown grouped property scope nanchors.") } },
|
||
{},
|
||
{},
|
||
Result::HasDuplicates };
|
||
QTest::newRow("nanchors2") << QStringLiteral("nanchors2.qml")
|
||
<< Result{ { Message{ QStringLiteral(
|
||
"unknown grouped property scope nanchors.") } },
|
||
{},
|
||
{},
|
||
Result::HasDuplicates };
|
||
QTest::newRow("nanchors3") << QStringLiteral("nanchors3.qml")
|
||
<< Result{ { Message{ QStringLiteral(
|
||
"unknown grouped property scope nanchors.") } },
|
||
{},
|
||
{},
|
||
Result::HasDuplicates };
|
||
QTest::newRow("badAliasObject")
|
||
<< QStringLiteral("badAliasObject.qml")
|
||
<< Result { { Message { QStringLiteral("Member \"wrongwrongwrong\" not "
|
||
"found on type \"QtObject\""),
|
||
8, 40 } } };
|
||
QTest::newRow("badScript") << QStringLiteral("badScript.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral(
|
||
"Member \"stuff\" not found on type \"Empty\""),
|
||
5, 21 } } };
|
||
QTest::newRow("badScriptOnAttachedProperty")
|
||
<< QStringLiteral("badScript.attached.qml")
|
||
<< Result { { Message { QStringLiteral("Unqualified access"), 3, 26 } } };
|
||
QTest::newRow("brokenNamespace")
|
||
<< QStringLiteral("brokenNamespace.qml")
|
||
<< Result { { Message { QStringLiteral("Type not found in namespace"), 4, 19 } } };
|
||
QTest::newRow("segFault (bad)")
|
||
<< QStringLiteral("SegFault.bad.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Member \"foobar\" not found on type \"QQuickScreenAttached\"") } } };
|
||
QTest::newRow("VariableUsedBeforeDeclaration")
|
||
<< QStringLiteral("useBeforeDeclaration.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Variable \"argq\" is used here before its declaration. "
|
||
"The declaration is at 6:13."),
|
||
5, 9 } } };
|
||
QTest::newRow("SignalParameterMismatch")
|
||
<< QStringLiteral("namedSignalParameters.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Parameter 1 to signal handler for \"onSig\" is called \"argarg\". "
|
||
"The signal has a parameter of the same name in position 2.") } },
|
||
{ Message { QStringLiteral("onSig2") } } };
|
||
QTest::newRow("TooManySignalParameters")
|
||
<< QStringLiteral("tooManySignalParameters.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Signal handler for \"onSig\" has more formal parameters "
|
||
"than the signal it handles.") } } };
|
||
QTest::newRow("OnAssignment") << QStringLiteral("onAssignment.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Member \"loops\" not found on type \"bool\"") } } };
|
||
QTest::newRow("BadAttached") << QStringLiteral("badAttached.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"unknown attached property scope WrongAttached.") } } };
|
||
QTest::newRow("BadBinding") << QStringLiteral("badBinding.qml")
|
||
<< Result{ { Message{ QStringLiteral(
|
||
"Property \"doesNotExist\" does not exist.") } } };
|
||
QTest::newRow("bad template literal (simple)")
|
||
<< QStringLiteral("badTemplateStringSimple.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cannot assign literal of type string to int") } } };
|
||
QTest::newRow("bad constant number to string")
|
||
<< QStringLiteral("numberToStringProperty.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Cannot assign literal of type double to QString") } } };
|
||
QTest::newRow("bad unary minus to string")
|
||
<< QStringLiteral("unaryMinusToStringProperty.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Cannot assign literal of type double to QString") } } };
|
||
QTest::newRow("bad tranlsation binding (qsTr)") << QStringLiteral("bad_qsTr.qml") << Result {};
|
||
QTest::newRow("bad string binding (QT_TR_NOOP)")
|
||
<< QStringLiteral("bad_QT_TR_NOOP.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cannot assign literal of type string to int") } } };
|
||
QTest::newRow("BadScriptBindingOnGroup")
|
||
<< QStringLiteral("badScriptBinding.group.qml")
|
||
<< Result{ { Message{ QStringLiteral("Property \"bogusProperty\" does not exist."), 3,
|
||
10 } } };
|
||
QTest::newRow("BadScriptBindingOnAttachedType")
|
||
<< QStringLiteral("badScriptBinding.attached.qml")
|
||
<< Result{ { Message{ QStringLiteral("Property \"bogusProperty\" does not exist."), 5,
|
||
12 } } };
|
||
QTest::newRow("BadScriptBindingOnAttachedSignalHandler")
|
||
<< QStringLiteral("badScriptBinding.attachedSignalHandler.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("no matching signal found for handler \"onBogusSignal\""), 3,
|
||
10 } } };
|
||
QTest::newRow("BadPropertyType")
|
||
<< QStringLiteral("badPropertyType.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"No type found for property \"bad\". This may be due to a missing "
|
||
"import statement or incomplete qmltypes files.") } } };
|
||
QTest::newRow("Deprecation (Property, with reason)")
|
||
<< QStringLiteral("deprecatedPropertyReason.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Property \"deprecated\" is deprecated (Reason: Test)") } } };
|
||
QTest::newRow("Deprecation (Property, no reason)")
|
||
<< QStringLiteral("deprecatedProperty.qml")
|
||
<< Result { { Message { QStringLiteral("Property \"deprecated\" is deprecated") } } };
|
||
QTest::newRow("Deprecation (Property binding, with reason)")
|
||
<< QStringLiteral("deprecatedPropertyBindingReason.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Binding on deprecated property \"deprecatedReason\" (Reason: Test)") } } };
|
||
QTest::newRow("Deprecation (Property binding, no reason)")
|
||
<< QStringLiteral("deprecatedPropertyBinding.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Binding on deprecated property \"deprecated\"") } } };
|
||
QTest::newRow("Deprecation (Type, with reason)")
|
||
<< QStringLiteral("deprecatedTypeReason.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Type \"TypeDeprecatedReason\" is deprecated (Reason: Test)") } } };
|
||
QTest::newRow("Deprecation (Type, no reason)")
|
||
<< QStringLiteral("deprecatedType.qml")
|
||
<< Result { { Message { QStringLiteral("Type \"TypeDeprecated\" is deprecated") } } };
|
||
QTest::newRow("MissingDefaultProperty")
|
||
<< QStringLiteral("defaultPropertyWithoutKeyword.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cannot assign to non-existent default property") } } };
|
||
QTest::newRow("MissingDefaultPropertyDefinedInTheSameType")
|
||
<< QStringLiteral("defaultPropertyWithinTheSameType.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cannot assign to non-existent default property") } } };
|
||
QTest::newRow("DoubleAssignToDefaultProperty")
|
||
<< QStringLiteral("defaultPropertyWithDoubleAssignment.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Cannot assign multiple objects to a default non-list property") } } };
|
||
QTest::newRow("DefaultPropertyWithWrongType(string)")
|
||
<< QStringLiteral("defaultPropertyWithWrongType.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Cannot assign to default property of incompatible type") } },
|
||
{ Message { QStringLiteral(
|
||
"Cannot assign to non-existent default property") } } };
|
||
QTest::newRow("MultiDefaultPropertyWithWrongType")
|
||
<< QStringLiteral("multiDefaultPropertyWithWrongType.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Cannot assign to default property of incompatible type") } },
|
||
{ Message { QStringLiteral(
|
||
"Cannot assign to non-existent default property") } } };
|
||
QTest::newRow("DefaultPropertyLookupInUnknownType")
|
||
<< QStringLiteral("unknownParentDefaultPropertyCheck.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Alien was not found. Did you add all import paths?") } } };
|
||
QTest::newRow("InvalidImport")
|
||
<< QStringLiteral("invalidImport.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Failed to import FooBar. Are your import paths set up properly?") } } };
|
||
QTest::newRow("Unused Import (simple)")
|
||
<< QStringLiteral("unused_simple.qml")
|
||
<< Result { { Message { QStringLiteral("Unused import"), 1, 1, QtInfoMsg } },
|
||
{},
|
||
{},
|
||
Result::ExitsNormally };
|
||
QTest::newRow("Unused Import (prefix)")
|
||
<< QStringLiteral("unused_prefix.qml")
|
||
<< Result { { Message { QStringLiteral("Unused import"), 1, 1, QtInfoMsg } },
|
||
{},
|
||
{},
|
||
Result::ExitsNormally };
|
||
QTest::newRow("TypePropertAccess") << QStringLiteral("typePropertyAccess.qml") << Result {};
|
||
QTest::newRow("badAttachedProperty")
|
||
<< QStringLiteral("badAttachedProperty.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Member \"progress\" not found on type \"TestTypeAttached\"")
|
||
} } };
|
||
QTest::newRow("badAttachedPropertyNested")
|
||
<< QStringLiteral("badAttachedPropertyNested.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Member \"progress\" not found on type \"QObject\""),
|
||
12, 41 } },
|
||
{ Message { QString("Member \"progress\" not found on type \"QObject\""),
|
||
6, 37 } } };
|
||
QTest::newRow("badAttachedPropertyTypeString")
|
||
<< QStringLiteral("badAttachedPropertyTypeString.qml")
|
||
<< Result{ { Message{ QStringLiteral("Cannot assign literal of type string to int") } },
|
||
{},
|
||
{},
|
||
Result::HasDuplicates };
|
||
QTest::newRow("badAttachedPropertyTypeQtObject")
|
||
<< QStringLiteral("badAttachedPropertyTypeQtObject.qml")
|
||
<< Result{ { Message{
|
||
QStringLiteral("Cannot assign object of type QtObject to int") } } };
|
||
// should succeed, but it does not:
|
||
QTest::newRow("attachedPropertyAccess")
|
||
<< QStringLiteral("goodAttachedPropertyAccess.qml") << Result::clean();
|
||
// should succeed, but it does not:
|
||
QTest::newRow("attachedPropertyNested")
|
||
<< QStringLiteral("goodAttachedPropertyNested.qml") << Result::clean();
|
||
QTest::newRow("deprecatedFunction")
|
||
<< QStringLiteral("deprecatedFunction.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Method \"deprecated(foobar)\" is deprecated (Reason: No particular "
|
||
"reason.)") } } };
|
||
QTest::newRow("deprecatedFunctionInherited")
|
||
<< QStringLiteral("deprecatedFunctionInherited.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Method \"deprecatedInherited(c, d)\" is deprecated (Reason: This "
|
||
"deprecation should be visible!)") } } };
|
||
|
||
QTest::newRow("duplicated id")
|
||
<< QStringLiteral("duplicateId.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Found a duplicated id. id root was first declared "), 0, 0,
|
||
QtCriticalMsg } } };
|
||
|
||
QTest::newRow("string as id") << QStringLiteral("stringAsId.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"ids do not need quotation marks") } } };
|
||
QTest::newRow("stringIdUsedInWarning")
|
||
<< QStringLiteral("stringIdUsedInWarning.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("i is a member of a parent element"),
|
||
} },
|
||
{},
|
||
{ Message { QStringLiteral("stringy.") } } };
|
||
QTest::newRow("Invalid_id_expression")
|
||
<< QStringLiteral("invalidId1.qml")
|
||
<< Result { { Message { QStringLiteral("Failed to parse id") } } };
|
||
QTest::newRow("Invalid_id_blockstatement")
|
||
<< QStringLiteral("invalidId2.qml")
|
||
<< Result { { Message { QStringLiteral("id must be followed by an identifier") } } };
|
||
QTest::newRow("multilineString")
|
||
<< QStringLiteral("multilineString.qml")
|
||
<< Result { { Message { QStringLiteral("String contains unescaped line terminator "
|
||
"which is deprecated."),
|
||
0, 0, QtInfoMsg } },
|
||
{},
|
||
{ Message { "`Foo\nmultiline\\`\nstring`", 4, 32 },
|
||
Message { "`another\\`\npart\nof it`", 6, 11 },
|
||
Message { R"(`
|
||
quote: " \\" \\\\"
|
||
ticks: \` \` \\\` \\\`
|
||
singleTicks: ' \' \\' \\\'
|
||
expression: \${expr} \${expr} \\\${expr} \\\${expr}`)",
|
||
10, 28 },
|
||
Message {
|
||
R"(`
|
||
quote: " \" \\" \\\"
|
||
ticks: \` \` \\\` \\\`
|
||
singleTicks: ' \\' \\\\'
|
||
expression: \${expr} \${expr} \\\${expr} \\\${expr}`)",
|
||
16, 27 } },
|
||
{ Result::ExitsNormally, Result::AutoFixable } };
|
||
QTest::addRow("multifix")
|
||
<< QStringLiteral("multifix.qml")
|
||
<< Result { {
|
||
Message { QStringLiteral("Unqualified access"), 7, 19, QtWarningMsg},
|
||
Message { QStringLiteral("Unqualified access"), 11, 19, QtWarningMsg},
|
||
}, {}, {
|
||
Message { QStringLiteral("pragma ComponentBehavior: Bound\n"), 1, 1 }
|
||
}, { Result::AutoFixable }};
|
||
QTest::newRow("unresolvedType")
|
||
<< QStringLiteral("unresolvedType.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"UnresolvedType was not found. Did you add all import paths?") } },
|
||
{ Message { QStringLiteral("incompatible type") } } };
|
||
QTest::newRow("invalidInterceptor")
|
||
<< QStringLiteral("invalidInterceptor.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"On-binding for property \"angle\" has wrong type \"Item\"") } } };
|
||
QTest::newRow("2Interceptors")
|
||
<< QStringLiteral("2interceptors.qml")
|
||
<< Result { { Message { QStringLiteral("Duplicate interceptor on property \"x\"") } } };
|
||
QTest::newRow("ValueSource+2Interceptors")
|
||
<< QStringLiteral("valueSourceBetween2interceptors.qml")
|
||
<< Result { { Message { QStringLiteral("Duplicate interceptor on property \"x\"") } } };
|
||
QTest::newRow("2ValueSources") << QStringLiteral("2valueSources.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Duplicate value source on property \"x\"") } } };
|
||
QTest::newRow("ValueSource+Value")
|
||
<< QStringLiteral("valueSource_Value.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Cannot combine value source and binding on property \"obj\"") } } };
|
||
QTest::newRow("ValueSource+ListValue")
|
||
<< QStringLiteral("valueSource_listValue.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Cannot combine value source and binding on property \"objs\"") } } };
|
||
QTest::newRow("NonExistentListProperty")
|
||
<< QStringLiteral("nonExistentListProperty.qml")
|
||
<< Result { { Message { QStringLiteral("Property \"objs\" does not exist") } } };
|
||
QTest::newRow("QtQuick.Window 2.0")
|
||
<< QStringLiteral("qtquickWindow20.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Member \"window\" not found on type \"QQuickWindow\"") } } };
|
||
QTest::newRow("unresolvedAttachedType")
|
||
<< QStringLiteral("unresolvedAttachedType.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"unknown attached property scope UnresolvedAttachedType.") } },
|
||
{ Message { QStringLiteral("Property \"property\" does not exist.") } } };
|
||
QTest::newRow("nestedInlineComponents")
|
||
<< QStringLiteral("nestedInlineComponents.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Nested inline components are not supported") } } };
|
||
QTest::newRow("inlineComponentNoComponent")
|
||
<< QStringLiteral("inlineComponentNoComponent.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Inline component declaration must be followed by a typename"),
|
||
3, 2 } } };
|
||
QTest::newRow("WithStatement") << QStringLiteral("WithStatement.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"with statements are strongly discouraged") } } };
|
||
QTest::newRow("BadLiteralBinding")
|
||
<< QStringLiteral("badLiteralBinding.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cannot assign literal of type string to int") } } };
|
||
QTest::newRow("BadLiteralBindingDate")
|
||
<< QStringLiteral("badLiteralBindingDate.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cannot assign binding of type QString to QDateTime") } } };
|
||
QTest::newRow("BadModulePrefix")
|
||
<< QStringLiteral("badModulePrefix.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cannot access singleton as a property of an object") } } };
|
||
QTest::newRow("BadModulePrefix2")
|
||
<< QStringLiteral("badModulePrefix2.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Cannot use a non-QObject type QRectF to access prefixed import") } } };
|
||
QTest::newRow("AssignToReadOnlyProperty")
|
||
<< QStringLiteral("assignToReadOnlyProperty.qml")
|
||
<< Result{
|
||
{ Message{ QStringLiteral("Cannot assign to read-only property activeFocus") } },
|
||
{},
|
||
{},
|
||
Result::HasDuplicates
|
||
};
|
||
QTest::newRow("AssignToReadOnlyProperty")
|
||
<< QStringLiteral("assignToReadOnlyProperty2.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cannot assign to read-only property activeFocus") } } };
|
||
QTest::newRow("cachedDependency")
|
||
<< QStringLiteral("cachedDependency.qml")
|
||
<< Result { { Message { QStringLiteral("Unused import"), 1, 1, QtInfoMsg } },
|
||
{ Message { QStringLiteral(
|
||
"Cannot assign binding of type QQuickItem to QObject") } },
|
||
{},
|
||
Result::ExitsNormally };
|
||
QTest::newRow("cycle in import")
|
||
<< QStringLiteral("cycleHead.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"MenuItem is part of an inheritance cycle: MenuItem -> MenuItem") } } };
|
||
QTest::newRow("badGeneralizedGroup1")
|
||
<< QStringLiteral("badGeneralizedGroup1.qml")
|
||
<< Result{ { Message{ QStringLiteral("Property \"aaaa\" does not exist.") } } };
|
||
QTest::newRow("badGeneralizedGroup2")
|
||
<< QStringLiteral("badGeneralizedGroup2.qml")
|
||
<< Result { { Message { QStringLiteral("unknown grouped property scope aself") } } };
|
||
QTest::newRow("missingQmltypes")
|
||
<< QStringLiteral("missingQmltypes.qml")
|
||
<< Result { { Message { QStringLiteral("QML types file does not exist") } } };
|
||
QTest::newRow("enumInvalid")
|
||
<< QStringLiteral("enumInvalid.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Member \"red\" not found on type \"QtObject\"") } } };
|
||
QTest::newRow("inaccessibleId")
|
||
<< QStringLiteral("inaccessibleId.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Member \"objectName\" not found on type \"int\"") } } };
|
||
QTest::newRow("inaccessibleId2")
|
||
<< QStringLiteral("inaccessibleId2.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Member \"objectName\" not found on type \"int\"") } } };
|
||
QTest::newRow("unknownTypeCustomParser")
|
||
<< QStringLiteral("unknownTypeCustomParser.qml")
|
||
<< Result { { Message { QStringLiteral("TypeDoesNotExist was not found.") } } };
|
||
QTest::newRow("nonNullStored")
|
||
<< QStringLiteral("nonNullStored.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Member \"objectName\" not found on type \"Foozle\"") } },
|
||
{ Message { QStringLiteral("Unqualified access") } } };
|
||
QTest::newRow("cppPropertyChangeHandlers-wrong-parameters-size-bindable")
|
||
<< QStringLiteral("badCppPropertyChangeHandlers1.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Signal handler for \"onAChanged\" has more formal parameters than "
|
||
"the signal it handles") } } };
|
||
QTest::newRow("cppPropertyChangeHandlers-wrong-parameters-size-notify")
|
||
<< QStringLiteral("badCppPropertyChangeHandlers2.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Signal handler for \"onBChanged\" has more formal parameters than "
|
||
"the signal it handles") } } };
|
||
QTest::newRow("cppPropertyChangeHandlers-no-property")
|
||
<< QStringLiteral("badCppPropertyChangeHandlers3.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("no matching signal found for handler \"onXChanged\"") } } };
|
||
QTest::newRow("cppPropertyChangeHandlers-not-a-signal")
|
||
<< QStringLiteral("badCppPropertyChangeHandlers4.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"no matching signal found for handler \"onWannabeSignal\"") } } };
|
||
QTest::newRow("didYouMean(binding)")
|
||
<< QStringLiteral("didYouMeanBinding.qml")
|
||
<< Result{ { Message{ QStringLiteral("Property \"witdh\" does not exist.") } },
|
||
{},
|
||
{ Message{ QStringLiteral("width") } } };
|
||
QTest::newRow("didYouMean(unqualified)")
|
||
<< QStringLiteral("didYouMeanUnqualified.qml")
|
||
<< Result { { Message { QStringLiteral("Unqualified access") } },
|
||
{},
|
||
{ Message { QStringLiteral("height") } } };
|
||
QTest::newRow("didYouMean(unqualifiedCall)")
|
||
<< QStringLiteral("didYouMeanUnqualifiedCall.qml")
|
||
<< Result { { Message { QStringLiteral("Unqualified access") } },
|
||
{},
|
||
{ Message { QStringLiteral("func") } } };
|
||
QTest::newRow("didYouMean(property)")
|
||
<< QStringLiteral("didYouMeanProperty.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Member \"hoight\" not found on type \"Rectangle\"") },
|
||
{},
|
||
{ Message { QStringLiteral("height") } } } };
|
||
QTest::newRow("didYouMean(propertyCall)")
|
||
<< QStringLiteral("didYouMeanPropertyCall.qml")
|
||
<< Result {
|
||
{ Message { QStringLiteral("Member \"lgg\" not found on type \"Console\"") },
|
||
{},
|
||
{ Message { QStringLiteral("log") } } }
|
||
};
|
||
QTest::newRow("didYouMean(component)")
|
||
<< QStringLiteral("didYouMeanComponent.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Itym was not found. Did you add all import paths?") },
|
||
{},
|
||
{ Message { QStringLiteral("Item") } } } };
|
||
QTest::newRow("didYouMean(enum)")
|
||
<< QStringLiteral("didYouMeanEnum.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Member \"Readx\" not found on type \"QQuickImage\"") },
|
||
{},
|
||
{ Message { QStringLiteral("Ready") } } } };
|
||
QTest::newRow("nullBinding") << QStringLiteral("nullBinding.qml")
|
||
<< Result{ { Message{ QStringLiteral(
|
||
"Cannot assign literal of type null to double") } } };
|
||
QTest::newRow("missingRequiredAlias")
|
||
<< QStringLiteral("missingRequiredAlias.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Component is missing required property requiredAlias from "
|
||
"RequiredWithRootLevelAlias") } } };
|
||
QTest::newRow("missingSingletonPragma")
|
||
<< QStringLiteral("missingSingletonPragma.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Type MissingPragma declared as singleton in qmldir but missing "
|
||
"pragma Singleton") } } };
|
||
QTest::newRow("missingSingletonQmldir")
|
||
<< QStringLiteral("missingSingletonQmldir.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Type MissingQmldirSingleton not declared as singleton in qmldir but using "
|
||
"pragma Singleton") } } };
|
||
QTest::newRow("jsVarDeclarationsWriteConst")
|
||
<< QStringLiteral("jsVarDeclarationsWriteConst.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Cannot assign to read-only property constProp") } } };
|
||
QTest::newRow("shadowedSignal")
|
||
<< QStringLiteral("shadowedSignal.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Signal \"pressed\" is shadowed by a property.") } } };
|
||
QTest::newRow("shadowedSignalWithId")
|
||
<< QStringLiteral("shadowedSignalWithId.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Signal \"pressed\" is shadowed by a property") } } };
|
||
QTest::newRow("shadowedSlot") << QStringLiteral("shadowedSlot.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Slot \"move\" is shadowed by a property") } } };
|
||
QTest::newRow("shadowedMethod") << QStringLiteral("shadowedMethod.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Method \"foo\" is shadowed by a property.") } } };
|
||
QTest::newRow("callVarProp")
|
||
<< QStringLiteral("callVarProp.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Property \"foo\" is a var property. It may or may not be a "
|
||
"method. Use a regular function instead.") } } };
|
||
QTest::newRow("callJSValue")
|
||
<< QStringLiteral("callJSValueProp.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"Property \"jsValue\" is a QJSValue property. It may or may not be "
|
||
"a method. Use a regular Q_INVOKABLE instead.") } } };
|
||
QTest::newRow("assignNonExistingTypeToVarProp")
|
||
<< QStringLiteral("assignNonExistingTypeToVarProp.qml")
|
||
<< Result { { Message { QStringLiteral(
|
||
"NonExistingType was not found. Did you add all import paths?") } } };
|
||
QTest::newRow("unboundComponents")
|
||
<< QStringLiteral("unboundComponents.qml")
|
||
<< Result { {
|
||
Message { QStringLiteral("Unqualified access"), 10, 25 },
|
||
Message { QStringLiteral("Unqualified access"), 14, 33 }
|
||
} };
|
||
QTest::newRow("badlyBoundComponents")
|
||
<< QStringLiteral("badlyBoundComponents.qml")
|
||
<< Result{ { Message{ QStringLiteral("Unqualified access"), 18, 36 } } };
|
||
QTest::newRow("NotScopedEnumCpp")
|
||
<< QStringLiteral("NotScopedEnumCpp.qml")
|
||
<< Result{ { Message{
|
||
QStringLiteral("You cannot access unscoped enum \"TheEnum\" from here."), 5,
|
||
49 } } };
|
||
|
||
QTest::newRow("unresolvedArrayBinding")
|
||
<< QStringLiteral("unresolvedArrayBinding.qml")
|
||
<< Result{ { Message{ QStringLiteral(u"Declaring an object which is not an Qml object"
|
||
" as a list member.") } },
|
||
{},
|
||
{},
|
||
Result::HasDuplicates };
|
||
QTest::newRow("duplicatedPropertyName")
|
||
<< QStringLiteral("duplicatedPropertyName.qml")
|
||
<< Result{ { Message{ QStringLiteral("Duplicated property name \"cat\"."), 5, 5 } } };
|
||
QTest::newRow("duplicatedSignalName")
|
||
<< QStringLiteral("duplicatedPropertyName.qml")
|
||
<< Result{ { Message{ QStringLiteral("Duplicated signal name \"clicked\"."), 8, 5 } } };
|
||
QTest::newRow("missingComponentBehaviorBound")
|
||
<< QStringLiteral("missingComponentBehaviorBound.qml")
|
||
<< Result {
|
||
{ Message{ QStringLiteral("Unqualified access"), 8, 31 } },
|
||
{},
|
||
{ Message{ QStringLiteral("Set \"pragma ComponentBehavior: Bound\" in "
|
||
"order to use IDs from outer components "
|
||
"in nested components."), 0, 0, QtInfoMsg } },
|
||
Result::AutoFixable
|
||
};
|
||
QTest::newRow("IsNotAnEntryOfEnum")
|
||
<< QStringLiteral("IsNotAnEntryOfEnum.qml")
|
||
<< Result{ {
|
||
Message {
|
||
QStringLiteral("Member \"Mode\" not found on type \"Item\""), 12,
|
||
29, QtWarningMsg },
|
||
Message{
|
||
QStringLiteral("\"Hour\" is not an entry of enum \"Mode\"."), 13,
|
||
62, QtInfoMsg }
|
||
},
|
||
{},
|
||
{ Message{ QStringLiteral("Hours") } }
|
||
};
|
||
|
||
QTest::newRow("StoreNameMethod")
|
||
<< QStringLiteral("storeNameMethod.qml")
|
||
<< Result { { Message { QStringLiteral("Cannot assign to method foo") } } };
|
||
|
||
QTest::newRow("CoerceToVoid")
|
||
<< QStringLiteral("coercetovoid.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Function without return type annotation returns double")
|
||
} } };
|
||
|
||
QTest::newRow("lowerCaseQualifiedImport")
|
||
<< QStringLiteral("lowerCaseQualifiedImport.qml")
|
||
<< Result{
|
||
{
|
||
Message{
|
||
u"Import qualifier 'test' must start with a capital letter."_s },
|
||
Message{
|
||
u"Namespace 'test' of 'test.Rectangle' must start with an upper case letter."_s },
|
||
},
|
||
{},
|
||
{},
|
||
Result::HasDuplicates
|
||
};
|
||
QTest::newRow("lowerCaseQualifiedImport2")
|
||
<< QStringLiteral("lowerCaseQualifiedImport2.qml")
|
||
<< Result{
|
||
{
|
||
Message{
|
||
u"Import qualifier 'test' must start with a capital letter."_s },
|
||
Message{
|
||
u"Namespace 'test' of 'test.Item' must start with an upper case letter."_s },
|
||
Message{
|
||
u"Namespace 'test' of 'test.Rectangle' must start with an upper case letter."_s },
|
||
Message{
|
||
u"Namespace 'test' of 'test.color' must start with an upper case letter."_s },
|
||
Message{
|
||
u"Namespace 'test' of 'test.Grid' must start with an upper case letter."_s },
|
||
},
|
||
{},
|
||
{},
|
||
Result::HasDuplicates
|
||
};
|
||
QTest::newRow("notQmlRootMethods")
|
||
<< QStringLiteral("notQmlRootMethods.qml")
|
||
<< Result{ {
|
||
Message{ u"Member \"deleteLater\" not found on type \"QtObject\""_s },
|
||
Message{ u"Member \"destroyed\" not found on type \"QtObject\""_s },
|
||
} };
|
||
|
||
QTest::newRow("connectionsBinding")
|
||
<< QStringLiteral("autofix/ConnectionsHandler.qml")
|
||
<< Result{
|
||
{ Message{
|
||
u"Implicitly defining \"onWidthChanged\" as signal handler in "
|
||
u"Connections is deprecated. "
|
||
u"Create a function instead: \"function onWidthChanged() { ... }\"."_s },
|
||
Message{
|
||
u"Implicitly defining \"onColorChanged\" as signal handler in "
|
||
u"Connections is deprecated. "
|
||
u"Create a function instead: \"function onColorChanged(collie) { ... }\"."_s } },
|
||
};
|
||
QTest::newRow("autoFixConnectionsBinding")
|
||
<< QStringLiteral("autofix/ConnectionsHandler.qml")
|
||
<< Result{
|
||
{ Message{
|
||
u"Implicitly defining \"onWidthChanged\" as signal handler in "
|
||
u"Connections is deprecated. "
|
||
u"Create a function instead: \"function onWidthChanged() { ... }\"."_s },
|
||
Message{
|
||
u"Implicitly defining \"onColorChanged\" as signal handler in "
|
||
u"Connections is deprecated. "
|
||
u"Create a function instead: \"function onColorChanged(collie) { ... }\"."_s } },
|
||
{},
|
||
{
|
||
Message{ u"function onWidthChanged() { console.log(\"new width:\", width) }"_s },
|
||
Message{ u"function onColorChanged(col) { console.log(\"new color:\", col) }"_s },
|
||
},
|
||
};
|
||
QTest::newRow("unresolvedTypeAnnotation")
|
||
<< QStringLiteral("unresolvedTypeAnnotations.qml")
|
||
<< Result{{
|
||
{ uR"("A" was not found for the type of parameter "a" in method "f".)"_s, 4, 17 },
|
||
{ uR"("B" was not found for the type of parameter "b" in method "f".)"_s, 4, 23 },
|
||
{ uR"("R" was not found for the return type of method "g".)"_s, 5, 18 },
|
||
{ uR"("C" was not found for the type of parameter "c" in method "h".)"_s, 6, 17 },
|
||
{ uR"("R" was not found for the return type of method "h".)"_s, 6, 22 },
|
||
{ uR"("D" was not found for the type of parameter "d" in method "i".)"_s, 7, 17 },
|
||
{ uR"("G" was not found for the type of parameter "g" in method "i".)"_s, 7, 26 },
|
||
}};
|
||
}
|
||
|
||
void TestQmllint::dirtyQmlCode()
|
||
{
|
||
QFETCH(QString, filename);
|
||
QFETCH(Result, result);
|
||
|
||
QJsonArray warnings;
|
||
|
||
QEXPECT_FAIL("attachedPropertyAccess", "We cannot discern between types and instances", Abort);
|
||
QEXPECT_FAIL("attachedPropertyNested", "We cannot discern between types and instances", Abort);
|
||
QEXPECT_FAIL("BadLiteralBindingDate",
|
||
"We're currently not able to verify any non-trivial QString conversion that "
|
||
"requires QQmlStringConverters",
|
||
Abort);
|
||
QEXPECT_FAIL("bad tranlsation binding (qsTr)", "We currently do not check translation binding",
|
||
Abort);
|
||
|
||
callQmllint(filename, result.flags.testFlag(Result::ExitsNormally), &warnings, {}, {}, {},
|
||
UseDefaultImports, nullptr, result.flags.testFlag(Result::Flag::AutoFixable));
|
||
|
||
checkResult(
|
||
warnings, result,
|
||
[] {
|
||
QEXPECT_FAIL("BadLiteralBindingDate",
|
||
"We're currently not able to verify any non-trivial QString "
|
||
"conversion that "
|
||
"requires QQmlStringConverters",
|
||
Abort);
|
||
},
|
||
[] {
|
||
QEXPECT_FAIL("badAttachedPropertyNested",
|
||
"We cannot discern between types and instances", Abort);
|
||
},
|
||
[] {
|
||
QEXPECT_FAIL("autoFixConnectionsBinding",
|
||
"We can't autofix the code without the Dom.", Abort);
|
||
});
|
||
}
|
||
|
||
void TestQmllint::cleanQmlCode_data()
|
||
{
|
||
QTest::addColumn<QString>("filename");
|
||
QTest::newRow("Simple_QML") << QStringLiteral("Simple.qml");
|
||
QTest::newRow("QML_importing_JS") << QStringLiteral("importing_js.qml");
|
||
QTest::newRow("JS_with_pragma_and_import") << QStringLiteral("QTBUG-45916.js");
|
||
QTest::newRow("uiQml") << QStringLiteral("FormUser.qml");
|
||
QTest::newRow("methodInScope") << QStringLiteral("MethodInScope.qml");
|
||
QTest::newRow("importWithPrefix") << QStringLiteral("ImportWithPrefix.qml");
|
||
QTest::newRow("catchIdentifier") << QStringLiteral("catchIdentifierNoWarning.qml");
|
||
QTest::newRow("qmldirAndQmltypes") << QStringLiteral("qmldirAndQmltypes.qml");
|
||
QTest::newRow("forLoop") << QStringLiteral("forLoop.qml");
|
||
QTest::newRow("esmodule") << QStringLiteral("esmodule.mjs");
|
||
QTest::newRow("methodsInJavascript") << QStringLiteral("javascriptMethods.qml");
|
||
QTest::newRow("goodAlias") << QStringLiteral("goodAlias.qml");
|
||
QTest::newRow("goodParent") << QStringLiteral("goodParent.qml");
|
||
QTest::newRow("goodTypeAssertion") << QStringLiteral("goodTypeAssertion.qml");
|
||
QTest::newRow("AttachedProps") << QStringLiteral("AttachedProps.qml");
|
||
QTest::newRow("unknownBuiltinFont") << QStringLiteral("ButtonLoader.qml");
|
||
QTest::newRow("confusingImport") << QStringLiteral("Dialog.qml");
|
||
QTest::newRow("qualifiedAttached") << QStringLiteral("Drawer.qml");
|
||
QTest::newRow("EnumAccess1") << QStringLiteral("EnumAccess1.qml");
|
||
QTest::newRow("EnumAccess2") << QStringLiteral("EnumAccess2.qml");
|
||
QTest::newRow("ListProperty") << QStringLiteral("ListProperty.qml");
|
||
QTest::newRow("AttachedType") << QStringLiteral("AttachedType.qml");
|
||
QTest::newRow("qmldirImportAndDepend") << QStringLiteral("qmldirImportAndDepend/good.qml");
|
||
QTest::newRow("ParentEnum") << QStringLiteral("parentEnum.qml");
|
||
QTest::newRow("Signals") << QStringLiteral("Signal.qml");
|
||
QTest::newRow("javascriptMethodsInModule")
|
||
<< QStringLiteral("javascriptMethodsInModuleGood.qml");
|
||
QTest::newRow("enumFromQtQml") << QStringLiteral("enumFromQtQml.qml");
|
||
QTest::newRow("anchors1") << QStringLiteral("anchors1.qml");
|
||
QTest::newRow("anchors2") << QStringLiteral("anchors2.qml");
|
||
QTest::newRow("defaultImport") << QStringLiteral("defaultImport.qml");
|
||
QTest::newRow("goodAliasObject") << QStringLiteral("goodAliasObject.qml");
|
||
QTest::newRow("jsmoduleimport") << QStringLiteral("jsmoduleimport.qml");
|
||
QTest::newRow("overridescript") << QStringLiteral("overridescript.qml");
|
||
QTest::newRow("multiExtension") << QStringLiteral("multiExtension.qml");
|
||
QTest::newRow("segFault") << QStringLiteral("SegFault.qml");
|
||
QTest::newRow("grouped scope failure") << QStringLiteral("groupedScope.qml");
|
||
QTest::newRow("layouts depends quick") << QStringLiteral("layouts.qml");
|
||
QTest::newRow("attached") << QStringLiteral("attached.qml");
|
||
QTest::newRow("enumProperty") << QStringLiteral("enumProperty.qml");
|
||
QTest::newRow("externalEnumProperty") << QStringLiteral("externalEnumProperty.qml");
|
||
QTest::newRow("shapes") << QStringLiteral("shapes.qml");
|
||
QTest::newRow("var") << QStringLiteral("var.qml");
|
||
QTest::newRow("defaultProperty") << QStringLiteral("defaultProperty.qml");
|
||
QTest::newRow("defaultPropertyList") << QStringLiteral("defaultPropertyList.qml");
|
||
QTest::newRow("defaultPropertyComponent") << QStringLiteral("defaultPropertyComponent.qml");
|
||
QTest::newRow("defaultPropertyComponent2") << QStringLiteral("defaultPropertyComponent.2.qml");
|
||
QTest::newRow("defaultPropertyListModel") << QStringLiteral("defaultPropertyListModel.qml");
|
||
QTest::newRow("defaultPropertyVar") << QStringLiteral("defaultPropertyVar.qml");
|
||
QTest::newRow("multiDefaultProperty") << QStringLiteral("multiDefaultPropertyOk.qml");
|
||
QTest::newRow("propertyDelegate") << QStringLiteral("propertyDelegate.qml");
|
||
QTest::newRow("duplicateQmldirImport") << QStringLiteral("qmldirImport/duplicate.qml");
|
||
QTest::newRow("Used imports") << QStringLiteral("used.qml");
|
||
QTest::newRow("Unused imports (multi)") << QStringLiteral("unused_multi.qml");
|
||
QTest::newRow("Unused static module") << QStringLiteral("unused_static.qml");
|
||
QTest::newRow("compositeSingleton") << QStringLiteral("compositesingleton.qml");
|
||
QTest::newRow("stringLength") << QStringLiteral("stringLength.qml");
|
||
QTest::newRow("stringLength2") << QStringLiteral("stringLength2.qml");
|
||
QTest::newRow("stringLength3") << QStringLiteral("stringLength3.qml");
|
||
QTest::newRow("attachedPropertyAssignments")
|
||
<< QStringLiteral("attachedPropertyAssignments.qml");
|
||
QTest::newRow("groupedPropertyAssignments") << QStringLiteral("groupedPropertyAssignments.qml");
|
||
QTest::newRow("goodAttachedProperty") << QStringLiteral("goodAttachedProperty.qml");
|
||
QTest::newRow("objectBindingOnVarProperty") << QStringLiteral("objectBoundToVar.qml");
|
||
QTest::newRow("Unversioned change signal without arguments") << QStringLiteral("unversionChangedSignalSansArguments.qml");
|
||
QTest::newRow("deprecatedFunctionOverride") << QStringLiteral("deprecatedFunctionOverride.qml");
|
||
QTest::newRow("multilineStringEscaped") << QStringLiteral("multilineStringEscaped.qml");
|
||
QTest::newRow("propertyOverride") << QStringLiteral("propertyOverride.qml");
|
||
QTest::newRow("propertyBindingValue") << QStringLiteral("propertyBindingValue.qml");
|
||
QTest::newRow("customParser") << QStringLiteral("customParser.qml");
|
||
QTest::newRow("customParser.recursive") << QStringLiteral("customParser.recursive.qml");
|
||
QTest::newRow("2Behavior") << QStringLiteral("2behavior.qml");
|
||
QTest::newRow("interceptor") << QStringLiteral("interceptor.qml");
|
||
QTest::newRow("valueSource") << QStringLiteral("valueSource.qml");
|
||
QTest::newRow("interceptor+valueSource") << QStringLiteral("interceptor_valueSource.qml");
|
||
QTest::newRow("groupedProperty (valueSource+interceptor)")
|
||
<< QStringLiteral("groupedProperty_valueSource_interceptor.qml");
|
||
QTest::newRow("QtQuick.Window 2.1") << QStringLiteral("qtquickWindow21.qml");
|
||
QTest::newRow("attachedTypeIndirect") << QStringLiteral("attachedTypeIndirect.qml");
|
||
QTest::newRow("objectArray") << QStringLiteral("objectArray.qml");
|
||
QTest::newRow("aliasToList") << QStringLiteral("aliasToList.qml");
|
||
QTest::newRow("QVariant") << QStringLiteral("qvariant.qml");
|
||
QTest::newRow("Accessible") << QStringLiteral("accessible.qml");
|
||
QTest::newRow("qjsroot") << QStringLiteral("qjsroot.qml");
|
||
QTest::newRow("qmlRootMethods") << QStringLiteral("qmlRootMethods.qml");
|
||
QTest::newRow("InlineComponent") << QStringLiteral("inlineComponent.qml");
|
||
QTest::newRow("InlineComponentWithComponents") << QStringLiteral("inlineComponentWithComponents.qml");
|
||
QTest::newRow("InlineComponentsChained") << QStringLiteral("inlineComponentsChained.qml");
|
||
QTest::newRow("ignoreWarnings") << QStringLiteral("ignoreWarnings.qml");
|
||
QTest::newRow("BindingBeforeDeclaration") << QStringLiteral("bindingBeforeDeclaration.qml");
|
||
QTest::newRow("CustomParserUnqualifiedAccess")
|
||
<< QStringLiteral("customParserUnqualifiedAccess.qml");
|
||
QTest::newRow("ImportQMLModule") << QStringLiteral("importQMLModule.qml");
|
||
QTest::newRow("ImportDirectoryQmldir") << QStringLiteral("Things/LintDirectly.qml");
|
||
QTest::newRow("BindingsOnGroupAndAttachedProperties")
|
||
<< QStringLiteral("goodBindingsOnGroupAndAttached.qml");
|
||
QTest::newRow("QQmlEasingEnums::Type") << QStringLiteral("animationEasing.qml");
|
||
QTest::newRow("ValidLiterals") << QStringLiteral("validLiterals.qml");
|
||
QTest::newRow("GoodModulePrefix") << QStringLiteral("goodModulePrefix.qml");
|
||
QTest::newRow("required property in Component") << QStringLiteral("requiredPropertyInComponent.qml");
|
||
QTest::newRow("bytearray") << QStringLiteral("bytearray.qml");
|
||
QTest::newRow("initReadonly") << QStringLiteral("initReadonly.qml");
|
||
QTest::newRow("connectionNoParent") << QStringLiteral("connectionNoParent.qml"); // QTBUG-97600
|
||
QTest::newRow("goodGeneralizedGroup") << QStringLiteral("goodGeneralizedGroup.qml");
|
||
QTest::newRow("on binding in grouped property") << QStringLiteral("onBindingInGroupedProperty.qml");
|
||
QTest::newRow("declared property of JS object") << QStringLiteral("bareQt.qml");
|
||
QTest::newRow("ID overrides property") << QStringLiteral("accessibleId.qml");
|
||
QTest::newRow("matchByName") << QStringLiteral("matchByName.qml");
|
||
QTest::newRow("QObject.hasOwnProperty") << QStringLiteral("qobjectHasOwnProperty.qml");
|
||
QTest::newRow("cppPropertyChangeHandlers")
|
||
<< QStringLiteral("goodCppPropertyChangeHandlers.qml");
|
||
QTest::newRow("unexportedCppBase") << QStringLiteral("unexportedCppBase.qml");
|
||
QTest::newRow("requiredWithRootLevelAlias") << QStringLiteral("RequiredWithRootLevelAlias.qml");
|
||
QTest::newRow("jsVarDeclarations") << QStringLiteral("jsVarDeclarations.qml");
|
||
QTest::newRow("qmodelIndex") << QStringLiteral("qmodelIndex.qml");
|
||
QTest::newRow("boundComponents") << QStringLiteral("boundComponents.qml");
|
||
QTest::newRow("prefixedAttachedProperty") << QStringLiteral("prefixedAttachedProperty.qml");
|
||
QTest::newRow("callLater") << QStringLiteral("callLater.qml");
|
||
QTest::newRow("listPropertyMethods") << QStringLiteral("listPropertyMethods.qml");
|
||
QTest::newRow("v4SequenceMethods") << QStringLiteral("v4SequenceMethods.qml");
|
||
QTest::newRow("stringToByteArray") << QStringLiteral("stringToByteArray.qml");
|
||
QTest::newRow("jsLibrary") << QStringLiteral("jsLibrary.qml");
|
||
QTest::newRow("nullBindingFunction") << QStringLiteral("nullBindingFunction.qml");
|
||
QTest::newRow("BindingTypeMismatchFunction") << QStringLiteral("bindingTypeMismatchFunction.qml");
|
||
QTest::newRow("BindingTypeMismatch") << QStringLiteral("bindingTypeMismatch.qml");
|
||
QTest::newRow("template literal (substitution)") << QStringLiteral("templateStringSubstitution.qml");
|
||
QTest::newRow("enumsOfScrollBar") << QStringLiteral("enumsOfScrollBar.qml");
|
||
QTest::newRow("optionalChainingCall") << QStringLiteral("optionalChainingCall.qml");
|
||
QTest::newRow("EnumAccessCpp") << QStringLiteral("EnumAccessCpp.qml");
|
||
QTest::newRow("qtquickdialog") << QStringLiteral("qtquickdialog.qml");
|
||
QTest::newRow("callBase") << QStringLiteral("callBase.qml");
|
||
QTest::newRow("propertyWithOn") << QStringLiteral("switcher.qml");
|
||
QTest::newRow("constructorProperty") << QStringLiteral("constructorProperty.qml");
|
||
QTest::newRow("onlyMajorVersion") << QStringLiteral("onlyMajorVersion.qml");
|
||
QTest::newRow("attachedImportUse") << QStringLiteral("attachedImportUse.qml");
|
||
QTest::newRow("VariantMapGetPropertyLookup") << QStringLiteral("variantMapLookup.qml");
|
||
QTest::newRow("StringToDateTime") << QStringLiteral("stringToDateTime.qml");
|
||
QTest::newRow("ScriptInTemplate") << QStringLiteral("scriptInTemplate.qml");
|
||
QTest::newRow("AddressableValue") << QStringLiteral("addressableValue.qml");
|
||
QTest::newRow("WriteListProperty") << QStringLiteral("writeListProperty.qml");
|
||
QTest::newRow("dontConfuseMemberPrintWithGlobalPrint") << QStringLiteral("findMemberPrint.qml");
|
||
QTest::newRow("groupedAttachedLayout") << QStringLiteral("groupedAttachedLayout.qml");
|
||
QTest::newRow("QQmlScriptString") << QStringLiteral("scriptstring.qml");
|
||
QTest::newRow("QEventPoint") << QStringLiteral("qEventPoint.qml");
|
||
QTest::newRow("locale") << QStringLiteral("locale.qml");
|
||
QTest::newRow("constInvokable") << QStringLiteral("useConstInvokable.qml");
|
||
QTest::newRow("dontCheckJSTypes") << QStringLiteral("dontCheckJSTypes.qml");
|
||
QTest::newRow("jsonObjectIsRecognized") << QStringLiteral("jsonObjectIsRecognized.qml");
|
||
QTest::newRow("jsonArrayIsRecognized") << QStringLiteral("jsonArrayIsRecognized.qml");
|
||
QTest::newRow("itemviewattached") << QStringLiteral("itemViewAttached.qml");
|
||
}
|
||
|
||
void TestQmllint::cleanQmlCode()
|
||
{
|
||
QFETCH(QString, filename);
|
||
|
||
QJsonArray warnings;
|
||
|
||
runTest(filename, Result::clean());
|
||
}
|
||
|
||
void TestQmllint::compilerWarnings_data()
|
||
{
|
||
QTest::addColumn<QString>("filename");
|
||
QTest::addColumn<Result>("result");
|
||
QTest::addColumn<bool>("enableCompilerWarnings");
|
||
|
||
QTest::newRow("listIndices") << QStringLiteral("listIndices.qml") << Result::clean() << true;
|
||
QTest::newRow("lazyAndDirect")
|
||
<< QStringLiteral("LazyAndDirect/Lazy.qml") << Result::clean() << true;
|
||
QTest::newRow("qQmlV4Function") << QStringLiteral("varargs.qml") << Result::clean() << true;
|
||
QTest::newRow("multiGrouped") << QStringLiteral("multiGrouped.qml") << Result::clean() << true;
|
||
|
||
QTest::newRow("shadowable")
|
||
<< QStringLiteral("shadowable.qml")
|
||
<< Result { { Message {QStringLiteral("with type NotSoSimple can be shadowed") } } }
|
||
<< true;
|
||
QTest::newRow("tooFewParameters")
|
||
<< QStringLiteral("tooFewParams.qml")
|
||
<< Result { { Message { QStringLiteral("No matching override found") } } } << true;
|
||
QTest::newRow("javascriptVariableArgs")
|
||
<< QStringLiteral("javascriptVariableArgs.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Function expects 0 arguments, but 2 were provided") } } }
|
||
<< true;
|
||
QTest::newRow("unknownTypeInRegister")
|
||
<< QStringLiteral("unknownTypeInRegister.qml")
|
||
<< Result { { Message {
|
||
QStringLiteral("Functions without type annotations won't be compiled") } } }
|
||
<< true;
|
||
QTest::newRow("pragmaStrict")
|
||
<< QStringLiteral("pragmaStrict.qml")
|
||
<< Result { { { QStringLiteral(
|
||
"Functions without type annotations won't be compiled") } } }
|
||
<< true;
|
||
QTest::newRow("generalizedGroupHint")
|
||
<< QStringLiteral("generalizedGroupHint.qml")
|
||
<< Result { { { QStringLiteral(
|
||
"Cannot resolve property type for binding on myColor. "
|
||
"You may want use ID-based grouped properties here.") } } }
|
||
<< true;
|
||
QTest::newRow("invalidIdLookup")
|
||
<< QStringLiteral("invalidIdLookup.qml")
|
||
<< Result { { {
|
||
QStringLiteral("Cannot retrieve a non-object type by ID: stateMachine")
|
||
} } }
|
||
<< true;
|
||
QTest::newRow("returnTypeAnnotation-component")
|
||
<< QStringLiteral("returnTypeAnnotation_component.qml")
|
||
<< Result{ { { "Could not compile function comp: function without return type "
|
||
"annotation returns (component in" },
|
||
{ "returnTypeAnnotation_component.qml)::c with type Comp. "
|
||
"This may prevent proper compilation to Cpp." } } }
|
||
<< true;
|
||
QTest::newRow("returnTypeAnnotation-enum")
|
||
<< QStringLiteral("returnTypeAnnotation_enum.qml")
|
||
<< Result{ { { "Could not compile function enumeration: function without return type "
|
||
"annotation returns QQuickText::HAlignment::AlignRight. "
|
||
"This may prevent proper compilation to Cpp." } } }
|
||
<< true;
|
||
QTest::newRow("returnTypeAnnotation-method")
|
||
<< QStringLiteral("returnTypeAnnotation_method.qml")
|
||
<< Result{ { { "Could not compile function method: function without return type "
|
||
"annotation returns (component in " }, // Don't check the build folder path
|
||
{ "returnTypeAnnotation_method.qml)::f(...). This may "
|
||
"prevent proper compilation to Cpp." } } }
|
||
<< true;
|
||
QTest::newRow("returnTypeAnnotation-property")
|
||
<< QStringLiteral("returnTypeAnnotation_property.qml")
|
||
<< Result{ { { "Could not compile function prop: function without return type "
|
||
"annotation returns (component in " }, // Don't check the build folder path
|
||
{ "returnTypeAnnotation_property.qml)::i with type int. This may prevent "
|
||
"proper compilation to Cpp." } } }
|
||
<< true;
|
||
QTest::newRow("returnTypeAnnotation-type")
|
||
<< QStringLiteral("returnTypeAnnotation_type.qml")
|
||
<< Result{ { { "Could not compile function type: function without return type "
|
||
"annotation returns double. This may prevent proper compilation to "
|
||
"Cpp." } } }
|
||
<< true;
|
||
|
||
QTest::newRow("functionAssign1")
|
||
<< QStringLiteral("functionAssign1.qml") << Result::clean() << true;
|
||
QTest::newRow("functionAssign2")
|
||
<< QStringLiteral("functionAssign2.qml") << Result::clean() << true;
|
||
}
|
||
|
||
void TestQmllint::compilerWarnings()
|
||
{
|
||
QFETCH(QString, filename);
|
||
QFETCH(Result, result);
|
||
QFETCH(bool, enableCompilerWarnings);
|
||
|
||
QJsonArray warnings;
|
||
|
||
auto categories = QQmlJSLogger::defaultCategories();
|
||
|
||
auto category = std::find_if(categories.begin(), categories.end(), [](const QQmlJS::LoggerCategory& category) {
|
||
return category.id() == qmlCompiler;
|
||
});
|
||
Q_ASSERT(category != categories.end());
|
||
|
||
if (enableCompilerWarnings) {
|
||
category->setLevel(QtWarningMsg);
|
||
category->setIgnored(false);
|
||
}
|
||
|
||
runTest(filename, result, {}, {}, {}, UseDefaultImports, &categories);
|
||
}
|
||
|
||
QString TestQmllint::runQmllint(const QString &fileToLint,
|
||
std::function<void(QProcess &)> handleResult,
|
||
const QStringList &extraArgs, bool ignoreSettings,
|
||
bool addImportDirs, bool absolutePath, const Environment &env)
|
||
{
|
||
auto qmlImportDir = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
|
||
QStringList args;
|
||
|
||
QString absoluteFilePath =
|
||
QFileInfo(fileToLint).isAbsolute() ? fileToLint : testFile(fileToLint);
|
||
|
||
args << QFileInfo(absoluteFilePath).fileName();
|
||
|
||
if (addImportDirs) {
|
||
args << QStringLiteral("-I") << qmlImportDir
|
||
<< QStringLiteral("-I") << dataDirectory();
|
||
}
|
||
|
||
if (ignoreSettings)
|
||
args << QStringLiteral("--ignore-settings");
|
||
|
||
if (absolutePath)
|
||
args << QStringLiteral("--absolute-path");
|
||
|
||
args << extraArgs;
|
||
args << QStringLiteral("--silent");
|
||
QString errors;
|
||
auto verify = [&](bool isSilent) {
|
||
QProcess process;
|
||
QProcessEnvironment processEnv = QProcessEnvironment::systemEnvironment();
|
||
for (const auto &entry : env)
|
||
processEnv.insert(entry.first, entry.second);
|
||
|
||
process.setProcessEnvironment(processEnv);
|
||
process.setWorkingDirectory(QFileInfo(absoluteFilePath).absolutePath());
|
||
process.start(m_qmllintPath, args);
|
||
handleResult(process);
|
||
errors = process.readAllStandardError();
|
||
|
||
QStringList lines = errors.split(u'\n', Qt::SkipEmptyParts);
|
||
|
||
auto end = std::remove_if(lines.begin(), lines.end(), [](const QString &line) {
|
||
return !line.startsWith("Warning: ") && !line.startsWith("Error: ");
|
||
});
|
||
|
||
std::sort(lines.begin(), end);
|
||
auto it = std::unique(lines.begin(), end);
|
||
if (it != end) {
|
||
qDebug() << "The warnings and errors were generated more than once:";
|
||
do {
|
||
qDebug() << *it;
|
||
} while (++it != end);
|
||
QTest::qFail("Duplicate warnings and errors", __FILE__, __LINE__);
|
||
}
|
||
|
||
if (isSilent) {
|
||
QTest::qVerify(errors.isEmpty(), "errors.isEmpty()", "Silent mode outputs messages",
|
||
__FILE__, __LINE__);
|
||
}
|
||
|
||
if (QTest::currentTestFailed()) {
|
||
qDebug().noquote() << "Command:" << process.program() << args.join(u' ');
|
||
qDebug() << "Exit status:" << process.exitStatus();
|
||
qDebug() << "Exit code:" << process.exitCode();
|
||
qDebug() << "stderr:" << errors;
|
||
qDebug() << "stdout:" << process.readAllStandardOutput();
|
||
}
|
||
};
|
||
verify(true);
|
||
args.removeLast();
|
||
verify(false);
|
||
return errors;
|
||
}
|
||
|
||
QString TestQmllint::runQmllint(const QString &fileToLint, bool shouldSucceed,
|
||
const QStringList &extraArgs, bool ignoreSettings,
|
||
bool addImportDirs, bool absolutePath, const Environment &env)
|
||
{
|
||
return runQmllint(
|
||
fileToLint,
|
||
[&](QProcess &process) {
|
||
QVERIFY(process.waitForFinished());
|
||
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
|
||
|
||
if (shouldSucceed)
|
||
QCOMPARE(process.exitCode(), 0);
|
||
else
|
||
QVERIFY(process.exitCode() != 0);
|
||
},
|
||
extraArgs, ignoreSettings, addImportDirs, absolutePath, env);
|
||
}
|
||
|
||
void TestQmllint::callQmllint(const QString &fileToLint, bool shouldSucceed, QJsonArray *warnings,
|
||
QStringList importPaths, QStringList qmldirFiles,
|
||
QStringList resources, DefaultImportOption defaultImports,
|
||
QList<QQmlJS::LoggerCategory> *categories, bool autoFixable,
|
||
LintType type)
|
||
{
|
||
QJsonArray jsonOutput;
|
||
|
||
const QFileInfo info = QFileInfo(fileToLint);
|
||
const QString lintedFile = info.isAbsolute() ? fileToLint : testFile(fileToLint);
|
||
|
||
QQmlJSLinter::LintResult lintResult;
|
||
|
||
const QStringList resolvedImportPaths = defaultImports == UseDefaultImports
|
||
? m_defaultImportPaths + importPaths
|
||
: importPaths;
|
||
if (type == LintFile) {
|
||
const QList<QQmlJS::LoggerCategory> resolvedCategories =
|
||
categories != nullptr ? *categories : QQmlJSLogger::defaultCategories();
|
||
lintResult = m_linter.lintFile(
|
||
lintedFile, nullptr, true, &jsonOutput, resolvedImportPaths, qmldirFiles,
|
||
resources, resolvedCategories);
|
||
} else {
|
||
lintResult =
|
||
m_linter.lintModule(fileToLint, true, &jsonOutput, resolvedImportPaths, resources);
|
||
}
|
||
|
||
bool success = lintResult == QQmlJSLinter::LintSuccess;
|
||
QEXPECT_FAIL("qtquickdialog", "Will fail until QTBUG-104091 is implemented", Abort);
|
||
QVERIFY2(success == shouldSucceed, QJsonDocument(jsonOutput).toJson());
|
||
|
||
if (warnings) {
|
||
QVERIFY2(jsonOutput.size() == 1, QJsonDocument(jsonOutput).toJson());
|
||
*warnings = jsonOutput.at(0)[u"warnings"_s].toArray();
|
||
}
|
||
|
||
QCOMPARE(success, shouldSucceed);
|
||
|
||
if (lintResult == QQmlJSLinter::LintSuccess || lintResult == QQmlJSLinter::HasWarnings) {
|
||
QString fixedCode;
|
||
QQmlJSLinter::FixResult fixResult = m_linter.applyFixes(&fixedCode, true);
|
||
|
||
if (autoFixable) {
|
||
QCOMPARE(fixResult, QQmlJSLinter::FixSuccess);
|
||
// Check that the fixed version of the file actually passes qmllint now
|
||
QTemporaryDir dir;
|
||
QVERIFY(dir.isValid());
|
||
QFile file(dir.filePath("Fixed.qml"));
|
||
QVERIFY2(file.open(QIODevice::WriteOnly), qPrintable(file.errorString()));
|
||
file.write(fixedCode.toUtf8());
|
||
file.flush();
|
||
file.close();
|
||
|
||
callQmllint(QFileInfo(file).absoluteFilePath(), true, nullptr, importPaths, qmldirFiles,
|
||
resources, defaultImports, categories, false);
|
||
|
||
const QString fixedPath = testFile(info.baseName() + u".fixed.qml"_s);
|
||
|
||
if (QFileInfo(fixedPath).exists()) {
|
||
QFile fixedFile(fixedPath);
|
||
QVERIFY(fixedFile.open(QFile::ReadOnly));
|
||
QString fixedFileContents = QString::fromUtf8(fixedFile.readAll());
|
||
#ifdef Q_OS_WIN
|
||
fixedCode = fixedCode.replace(u"\r\n"_s, u"\n"_s);
|
||
fixedFileContents = fixedFileContents.replace(u"\r\n"_s, u"\n"_s);
|
||
#endif
|
||
|
||
QCOMPARE(fixedCode, fixedFileContents);
|
||
}
|
||
} else {
|
||
if (shouldSucceed)
|
||
QCOMPARE(fixResult, QQmlJSLinter::NothingToFix);
|
||
else
|
||
QVERIFY(fixResult == QQmlJSLinter::FixSuccess
|
||
|| fixResult == QQmlJSLinter::NothingToFix);
|
||
}
|
||
}
|
||
}
|
||
|
||
void TestQmllint::runTest(const QString &testFile, const Result &result, QStringList importDirs,
|
||
QStringList qmltypesFiles, QStringList resources,
|
||
DefaultImportOption defaultImports,
|
||
QList<QQmlJS::LoggerCategory> *categories)
|
||
{
|
||
QJsonArray warnings;
|
||
callQmllint(testFile, result.flags.testFlag(Result::Flag::ExitsNormally), &warnings, importDirs,
|
||
qmltypesFiles, resources, defaultImports, categories,
|
||
result.flags.testFlag(Result::Flag::AutoFixable));
|
||
checkResult(warnings, result);
|
||
}
|
||
|
||
static QtMsgType typeStringToMsgType(const QString &type)
|
||
{
|
||
if (type == u"debug")
|
||
return QtDebugMsg;
|
||
if (type == u"info")
|
||
return QtInfoMsg;
|
||
if (type == u"warning")
|
||
return QtWarningMsg;
|
||
if (type == u"critical")
|
||
return QtCriticalMsg;
|
||
if (type == u"fatal")
|
||
return QtFatalMsg;
|
||
|
||
Q_UNREACHABLE();
|
||
}
|
||
|
||
struct SimplifiedWarning
|
||
{
|
||
QString message;
|
||
quint32 line;
|
||
quint32 column;
|
||
QtMsgType type;
|
||
|
||
SimplifiedWarning(QJsonValueConstRef warning)
|
||
: message(warning[u"message"].toString()),
|
||
line(warning[u"line"].toInt()),
|
||
column(warning[u"column"].toInt()),
|
||
type(typeStringToMsgType(warning[u"type"].toString()))
|
||
{
|
||
}
|
||
|
||
QString toString() const { return u"%1:%2: %3"_s.arg(line).arg(column).arg(message); }
|
||
|
||
std::tuple<QString, quint32, quint32, QtMsgType> asTuple() const
|
||
{
|
||
return std::make_tuple(message, line, column, type);
|
||
}
|
||
|
||
friend bool comparesEqual(const SimplifiedWarning& a, const SimplifiedWarning& b) noexcept
|
||
{
|
||
return a.asTuple() == b.asTuple();
|
||
}
|
||
friend Qt::strong_ordering compareThreeWay(const SimplifiedWarning& a, const SimplifiedWarning& b) noexcept {
|
||
return QtOrderingPrivate::compareThreeWayMulti(a.asTuple(), b.asTuple());
|
||
}
|
||
Q_DECLARE_STRONGLY_ORDERED(SimplifiedWarning)
|
||
};
|
||
|
||
template<typename ExpectedMessageFailureHandler, typename BadMessageFailureHandler,
|
||
typename ReplacementFailureHandler>
|
||
void TestQmllint::checkResult(const QJsonArray &warnings, const Result &result,
|
||
ExpectedMessageFailureHandler onExpectedMessageFailures,
|
||
BadMessageFailureHandler onBadMessageFailures,
|
||
ReplacementFailureHandler onReplacementFailures)
|
||
{
|
||
if (result.flags.testFlag(Result::Flag::NoMessages))
|
||
QVERIFY2(warnings.isEmpty(), qPrintable(QJsonDocument(warnings).toJson()));
|
||
|
||
for (const Message &msg : result.expectedMessages) {
|
||
// output.contains() expect fails:
|
||
onExpectedMessageFailures();
|
||
|
||
searchWarnings(warnings, msg.text, msg.severity, msg.line, msg.column);
|
||
}
|
||
|
||
for (const Message &msg : result.badMessages) {
|
||
// !output.contains() expect fails:
|
||
onBadMessageFailures();
|
||
|
||
searchWarnings(warnings, msg.text, msg.severity, msg.line, msg.column, StringNotContained);
|
||
}
|
||
|
||
for (const Message &replacement : result.expectedReplacements) {
|
||
onReplacementFailures();
|
||
searchWarnings(warnings, replacement.text, replacement.severity, replacement.line,
|
||
replacement.column, StringContained, DoReplacementSearch);
|
||
}
|
||
|
||
// check for duplicates
|
||
QList<SimplifiedWarning> sortedWarnings;
|
||
std::transform(warnings.begin(), warnings.end(), std::back_inserter(sortedWarnings),
|
||
[](QJsonValueConstRef ref) { return SimplifiedWarning(ref); });
|
||
std::sort(sortedWarnings.begin(), sortedWarnings.end());
|
||
const auto firstDuplicate =
|
||
std::adjacent_find(sortedWarnings.constBegin(), sortedWarnings.constEnd());
|
||
for (auto it = firstDuplicate; it != sortedWarnings.constEnd();
|
||
it = std::adjacent_find(it + 1, sortedWarnings.constEnd()))
|
||
qDebug() << "Found duplicate warning: " << it->toString();
|
||
if (result.flags.testFlag(Result::HasDuplicates))
|
||
QEXPECT_FAIL("", "TODO: remove duplicate warnings", Continue);
|
||
QVERIFY2(firstDuplicate == sortedWarnings.constEnd(), "Found duplicate warnings!");
|
||
}
|
||
|
||
void TestQmllint::searchWarnings(const QJsonArray &warnings, const QString &substring,
|
||
QtMsgType type, quint32 line, quint32 column,
|
||
ContainOption shouldContain, ReplacementOption searchReplacements)
|
||
{
|
||
bool contains = false;
|
||
|
||
for (const QJsonValueConstRef warningJson : warnings) {
|
||
SimplifiedWarning warning(warningJson);
|
||
|
||
if (warning.message.contains(substring)) {
|
||
if (warning.type != type) {
|
||
continue;
|
||
}
|
||
if (line != 0 || column != 0) {
|
||
if (warning.line != line || warning.column != column) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
contains = true;
|
||
break;
|
||
}
|
||
|
||
for (const QJsonValueConstRef fix : warningJson[u"suggestions"].toArray()) {
|
||
const QString fixMessage = fix[u"message"].toString();
|
||
if (fixMessage.contains(substring)) {
|
||
contains = true;
|
||
break;
|
||
}
|
||
|
||
if (searchReplacements == DoReplacementSearch) {
|
||
QString replacement = fix[u"replacement"].toString();
|
||
#ifdef Q_OS_WIN
|
||
// Replacements can contain native line endings
|
||
// but we need them to be uniform in order for them to conform to our test data
|
||
replacement = replacement.replace(u"\r\n"_s, u"\n"_s);
|
||
#endif
|
||
|
||
if (replacement.contains(substring)) {
|
||
quint32 fixLine = fix[u"line"].toInt();
|
||
quint32 fixColumn = fix[u"column"].toInt();
|
||
if (line != 0 || column != 0) {
|
||
if (fixLine != line || fixColumn != column) {
|
||
continue;
|
||
}
|
||
}
|
||
contains = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
const auto toDescription = [](const QJsonArray &warnings, const QString &substring,
|
||
quint32 line, quint32 column, bool must = true) {
|
||
QString msg = QStringLiteral("qmllint output:\n%1\nIt %2 contain '%3'")
|
||
.arg(QString::fromUtf8(
|
||
QJsonDocument(warnings).toJson(QJsonDocument::Indented)),
|
||
must ? u"must" : u"must NOT", substring);
|
||
if (line != 0 || column != 0)
|
||
msg += u" (%1:%2)"_s.arg(line).arg(column);
|
||
|
||
return msg;
|
||
};
|
||
|
||
if (shouldContain == StringContained) {
|
||
if (!contains)
|
||
qWarning().noquote() << toDescription(warnings, substring, line, column);
|
||
QVERIFY(contains);
|
||
} else {
|
||
if (contains)
|
||
qWarning().noquote() << toDescription(warnings, substring, line, column, false);
|
||
QVERIFY(!contains);
|
||
}
|
||
}
|
||
|
||
void TestQmllint::requiredProperty()
|
||
{
|
||
runTest("requiredProperty.qml", Result::clean());
|
||
|
||
runTest("requiredMissingProperty.qml",
|
||
Result { { Message { QStringLiteral(
|
||
"Property \"foo\" was marked as required but does not exist.") } } });
|
||
|
||
runTest("requiredPropertyBindings.qml", Result::clean());
|
||
runTest("requiredPropertyBindingsNow.qml",
|
||
Result { { Message { QStringLiteral("Component is missing required property "
|
||
"required_now_string from Base") },
|
||
Message { QStringLiteral("Component is missing required property "
|
||
"required_defined_here_string from here") } } });
|
||
runTest("requiredPropertyBindingsLater.qml",
|
||
Result { { Message { QStringLiteral("Component is missing required property "
|
||
"required_later_string from "
|
||
"Base") },
|
||
Message { QStringLiteral("Property marked as required in Derived") },
|
||
Message { QStringLiteral("Component is missing required property "
|
||
"required_even_later_string "
|
||
"from Base (marked as required by here)") } } });
|
||
}
|
||
|
||
void TestQmllint::settingsFile()
|
||
{
|
||
QVERIFY(runQmllint("settings/unqualifiedSilent/unqualified.qml", true, warningsShouldFailArgs(), false)
|
||
.isEmpty());
|
||
QVERIFY(runQmllint("settings/unusedImportWarning/unused.qml", false, warningsShouldFailArgs(), false)
|
||
.contains(QStringLiteral("Warning: %1:2:1: Unused import")
|
||
.arg(testFile("settings/unusedImportWarning/unused.qml"))));
|
||
QVERIFY(runQmllint("settings/bare/bare.qml", false, warningsShouldFailArgs(), false, false)
|
||
.contains(
|
||
u"Failed to import QtQuick. Are your import paths set up properly?"_s));
|
||
QVERIFY(runQmllint("settings/qmltypes/qmltypes.qml", false, warningsShouldFailArgs(), false)
|
||
.contains(QStringLiteral("not a qmldir file. Assuming qmltypes.")));
|
||
QVERIFY(runQmllint("settings/qmlimports/qmlimports.qml", true, warningsShouldFailArgs(), false).isEmpty());
|
||
}
|
||
|
||
void TestQmllint::additionalImplicitImport()
|
||
{
|
||
// We're polluting the resource file system here, so let's clean up afterwards.
|
||
const auto guard = qScopeGuard([this]() {m_linter.clearCache(); });
|
||
runTest("additionalImplicitImport.qml", Result::clean(), {}, {},
|
||
{ testFile("implicitImportResource.qrc") });
|
||
}
|
||
|
||
void TestQmllint::qrcUrlImport()
|
||
{
|
||
const auto guard = qScopeGuard([this]() { m_linter.clearCache(); });
|
||
|
||
QJsonArray warnings;
|
||
callQmllint(testFile("untitled/main.qml"), true, &warnings, {}, {},
|
||
{ testFile("untitled/qrcUrlImport.qrc") });
|
||
checkResult(warnings, Result::clean());
|
||
}
|
||
|
||
void TestQmllint::incorrectImportFromHost_data()
|
||
{
|
||
QTest::addColumn<QString>("filename");
|
||
QTest::addColumn<Result>("result");
|
||
|
||
QTest::newRow("NonexistentFile")
|
||
<< QStringLiteral("importNonexistentFile.qml")
|
||
<< Result{ { Message{
|
||
QStringLiteral("File or directory you are trying to import does not exist"),
|
||
1, 1 } } };
|
||
#ifndef Q_OS_WIN
|
||
// there is no /dev/null device on Win
|
||
QTest::newRow("NullDevice")
|
||
<< QStringLiteral("importNullDevice.qml")
|
||
<< Result{ { Message{ QStringLiteral("is neither a file nor a directory. Are sure the "
|
||
"import path is correct?"),
|
||
1, 1 } } };
|
||
#endif
|
||
}
|
||
|
||
void TestQmllint::incorrectImportFromHost()
|
||
{
|
||
QFETCH(QString, filename);
|
||
QFETCH(Result, result);
|
||
|
||
runTest(filename, result);
|
||
}
|
||
|
||
void TestQmllint::attachedPropertyReuse()
|
||
{
|
||
auto categories = QQmlJSLogger::defaultCategories();
|
||
auto category = std::find_if(categories.begin(), categories.end(), [](const QQmlJS::LoggerCategory& category) {
|
||
return category.id() == qmlAttachedPropertyReuse;
|
||
});
|
||
Q_ASSERT(category != categories.end());
|
||
|
||
category->setLevel(QtWarningMsg);
|
||
category->setIgnored(false);
|
||
runTest("attachedPropNotReused.qml",
|
||
Result { { Message { QStringLiteral("Using attached type QQuickKeyNavigationAttached "
|
||
"already initialized in a parent "
|
||
"scope") } } },
|
||
{}, {}, {}, UseDefaultImports, &categories);
|
||
|
||
runTest("attachedPropEnum.qml", Result::clean(), {}, {}, {}, UseDefaultImports, &categories);
|
||
runTest("MyStyle/ToolBar.qml",
|
||
Result{ { Message{
|
||
"Using attached type MyStyle already initialized in a parent scope"_L1,
|
||
10, 16 } },
|
||
{},
|
||
{ Message{ "Reference it by id instead"_L1, 10, 16 } },
|
||
Result::AutoFixable },
|
||
{}, {}, {}, UseDefaultImports, &categories);
|
||
runTest("pluginQuick_multipleAttachedPropertyReuse.qml",
|
||
Result{ { Message{ QStringLiteral(
|
||
"Using attached type Test already initialized in a parent scope") } } },
|
||
{}, {}, {}, UseDefaultImports, &categories);
|
||
}
|
||
|
||
void TestQmllint::missingBuiltinsNoCrash()
|
||
{
|
||
// We cannot use the normal linter here since the other tests might have cached the builtins
|
||
// alread
|
||
QQmlJSLinter linter(m_defaultImportPaths);
|
||
|
||
QJsonArray jsonOutput;
|
||
QJsonArray warnings;
|
||
|
||
bool success = linter.lintFile(testFile("missingBuiltinsNoCrash.qml"), nullptr, true,
|
||
&jsonOutput, {}, {}, {}, {})
|
||
== QQmlJSLinter::LintSuccess;
|
||
QVERIFY2(!success, QJsonDocument(jsonOutput).toJson());
|
||
|
||
QVERIFY2(jsonOutput.size() == 1, QJsonDocument(jsonOutput).toJson());
|
||
warnings = jsonOutput.at(0)[u"warnings"_s].toArray();
|
||
|
||
checkResult(
|
||
warnings,
|
||
Result{ { Message{
|
||
u"Failed to import QtQuick. Are your import paths set up properly?"_s } } });
|
||
}
|
||
|
||
void TestQmllint::absolutePath()
|
||
{
|
||
QString absPathOutput = runQmllint("memberNotFound.qml", false, warningsShouldFailArgs(), true, true, true);
|
||
QString relPathOutput = runQmllint("memberNotFound.qml", false, warningsShouldFailArgs(), true, true, false);
|
||
const QString absolutePath = QFileInfo(testFile("memberNotFound.qml")).absoluteFilePath();
|
||
|
||
QVERIFY(absPathOutput.contains(absolutePath));
|
||
QVERIFY(!relPathOutput.contains(absolutePath));
|
||
}
|
||
|
||
void TestQmllint::importMultipartUri()
|
||
{
|
||
runTest("here.qml", Result::clean(), {}, { testFile("Elsewhere/qmldir") });
|
||
}
|
||
|
||
void TestQmllint::lintModule_data()
|
||
{
|
||
QTest::addColumn<QString>("module");
|
||
QTest::addColumn<QStringList>("importPaths");
|
||
QTest::addColumn<QStringList>("resources");
|
||
QTest::addColumn<Result>("result");
|
||
|
||
QTest::addRow("Things")
|
||
<< u"Things"_s
|
||
<< QStringList()
|
||
<< QStringList()
|
||
<< Result {
|
||
{ Message {
|
||
u"Type \"QPalette\" not found. Used in SomethingEntirelyStrange.palette"_s,
|
||
},
|
||
Message {
|
||
u"Type \"CustomPalette\" is not fully resolved. Used in SomethingEntirelyStrange.palette2"_s } }
|
||
};
|
||
QTest::addRow("missingQmltypes")
|
||
<< u"Fake5Compat.GraphicalEffects.private"_s
|
||
<< QStringList()
|
||
<< QStringList()
|
||
<< Result { { Message { u"QML types file does not exist"_s } } };
|
||
|
||
QTest::addRow("moduleWithQrc")
|
||
<< u"moduleWithQrc"_s
|
||
<< QStringList({ testFile("hidden") })
|
||
<< QStringList({
|
||
testFile("hidden/qmake_moduleWithQrc.qrc"),
|
||
testFile("hidden/moduleWithQrc_raw_qml_0.qrc")
|
||
})
|
||
<< Result::clean();
|
||
}
|
||
|
||
void TestQmllint::lintModule()
|
||
{
|
||
QFETCH(QString, module);
|
||
QFETCH(QStringList, importPaths);
|
||
QFETCH(QStringList, resources);
|
||
QFETCH(Result, result);
|
||
|
||
QJsonArray warnings;
|
||
callQmllint(module, result.flags & Result::ExitsNormally, &warnings, importPaths, {}, resources,
|
||
UseDefaultImports, nullptr, false, LintModule);
|
||
checkResult(warnings, result);
|
||
}
|
||
|
||
void TestQmllint::testLineEndings()
|
||
{
|
||
{
|
||
const auto textWithLF = QString::fromUtf16(u"import QtQuick 2.0\nimport QtTest 2.0 // qmllint disable unused-imports\n"
|
||
"import QtTest 2.0 // qmllint disable\n\nItem {\n @Deprecated {}\n property string deprecated\n\n "
|
||
"property string a: root.a // qmllint disable unqualifi77777777777777777777777777777777777777777777777777777"
|
||
"777777777777777777777777777777777777ed\n property string b: root.a // qmllint di000000000000000000000000"
|
||
"000000000000000000inyyyyyyyyg c: root.a\n property string d: root.a\n // qmllint enable unqualified\n\n "
|
||
"//qmllint d 4isable\n property string e: root.a\n Component.onCompleted: {\n console.log"
|
||
"(deprecated);\n }\n // qmllint enable\n\n}\n");
|
||
|
||
const auto lintResult = m_linter.lintFile( {}, &textWithLF, true, nullptr, {}, {}, {}, {});
|
||
|
||
QCOMPARE(lintResult, QQmlJSLinter::LintResult::HasWarnings);
|
||
}
|
||
{
|
||
const auto textWithCRLF = QString::fromUtf16(u"import QtQuick 2.0\nimport QtTest 2.0 // qmllint disable unused-imports\n"
|
||
"import QtTest 2.0 // qmllint disable\n\nItem {\n @Deprecated {}\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r"
|
||
"\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\n property string deprecated\n\n property string a: root.a "
|
||
"// qmllint disable unqualifi77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777ed\n "
|
||
"property string b: root.a // qmllint di000000000000000000000000000000000000000000inyyyyyyyyg c: root.a\n property string d: "
|
||
"root.a\n // qmllint enable unqualified\n\n //qmllint d 4isable\n property string e: root.a\n Component.onCompleted: "
|
||
"{\n console.log(deprecated);\n }\n // qmllint enable\n\n}\n");
|
||
|
||
const auto lintResult = m_linter.lintFile( {}, &textWithCRLF, true, nullptr, {}, {}, {}, {});
|
||
|
||
QCOMPARE(lintResult, QQmlJSLinter::LintResult::HasWarnings);
|
||
}
|
||
}
|
||
|
||
void TestQmllint::valueTypesFromString()
|
||
{
|
||
runTest("valueTypesFromString.qml",
|
||
Result{ {
|
||
Message{
|
||
u"Construction from string is deprecated. Use structured value type construction instead for type \"QPointF\""_s },
|
||
Message{
|
||
u"Construction from string is deprecated. Use structured value type construction instead for type \"QSizeF\""_s },
|
||
Message{
|
||
u"Construction from string is deprecated. Use structured value type construction instead for type \"QRectF\""_s },
|
||
Message{
|
||
u"Construction from string is deprecated. Use structured value type construction instead for type \"QVector2D\""_s },
|
||
Message{
|
||
u"Construction from string is deprecated. Use structured value type construction instead for type \"QVector3D\""_s },
|
||
Message{
|
||
u"Construction from string is deprecated. Use structured value type construction instead for type \"QVector4D\""_s },
|
||
Message{
|
||
u"Construction from string is deprecated. Use structured value type construction instead for type \"QQuaternion\""_s },
|
||
Message{
|
||
u"Construction from string is deprecated. Use structured value type construction instead for type \"QMatrix4x4\""_s },
|
||
},
|
||
{ /*bad messages */ },
|
||
{
|
||
Message{ u"({ width: 30, height: 50 })"_s },
|
||
Message{ u"({ x: 10, y: 20, width: 30, height: 50 })"_s },
|
||
Message{ u"({ x: 30, y: 50 })"_s },
|
||
Message{ u"({ x: 1, y: 2 })"_s },
|
||
Message{ u"({ x: 1, y: 2 })"_s },
|
||
Message{ u"({ x: 1, y: 2, z: 3 })"_s },
|
||
Message{ u"({ x: 1, y: 2, z: 3, w: 4 })"_s },
|
||
Message{ u"({ scalar: 1, x: 2, y: 3, z: 4 })"_s },
|
||
Message{
|
||
u"({ m11: 1, m12: 2, m13: 3, m14: 4, m21: 5, m22: 6, m23: 7, m24: 8, m31: 9, m32: 10, m33: 11, m34: 12, m41: 13, m42: 14, m43: 15, m44: 16 })"_s },
|
||
} });
|
||
}
|
||
|
||
#if QT_CONFIG(library)
|
||
void TestQmllint::testPlugin()
|
||
{
|
||
bool pluginFound = false;
|
||
for (const QQmlJSLinter::Plugin &plugin : m_linter.plugins()) {
|
||
if (plugin.name() == "testPlugin") {
|
||
pluginFound = true;
|
||
QCOMPARE(plugin.author(), u"Qt"_s);
|
||
QCOMPARE(plugin.description(), u"A test plugin for tst_qmllint"_s);
|
||
QCOMPARE(plugin.version(), u"1.0"_s);
|
||
break;
|
||
}
|
||
}
|
||
QVERIFY(pluginFound);
|
||
|
||
runTest("elementpass_pluginTest.qml", Result { { Message { u"ElementTest OK"_s, 4, 5 } } });
|
||
runTest("propertypass_pluginTest.qml",
|
||
Result{ { // Specific binding for specific property
|
||
Message{
|
||
u"Saw binding on Text property text with value NULL (and type 3) in scope Text"_s },
|
||
|
||
// Property on any type
|
||
Message{ u"Saw read on Text property x in scope Text"_s },
|
||
Message{
|
||
u"Saw binding on Text property x with value NULL (and type 2) in scope Text"_s },
|
||
Message{ u"Saw read on Text property x in scope Item"_s },
|
||
Message{ u"Saw write on Text property x with value int in scope Item"_s },
|
||
Message{
|
||
u"Saw binding on Item property x with value NULL (and type 2) in scope Item"_s },
|
||
// JavaScript
|
||
Message{ u"Saw read on ObjectPrototype property log in scope Item"_s },
|
||
Message{ u"Saw read on ObjectPrototype property log in scope Item"_s },
|
||
// ListModel
|
||
Message{
|
||
u"Saw binding on ListView property model with value ListModel (and type 8) in scope ListView"_s },
|
||
Message{
|
||
u"Saw binding on ListView property height with value NULL (and type 2) in scope ListView"_s } },
|
||
{},
|
||
{},
|
||
Result::HasDuplicates });
|
||
runTest("controlsWithQuick_pluginTest.qml",
|
||
Result { { Message { u"QtQuick.Controls, QtQuick and QtQuick.Window present"_s } } });
|
||
runTest("controlsWithoutQuick_pluginTest.qml",
|
||
Result { { Message { u"QtQuick.Controls and NO QtQuick present"_s } } });
|
||
// Verify that none of the passes do anything when they're not supposed to
|
||
runTest("nothing_pluginTest.qml", Result::clean());
|
||
|
||
QVERIFY(runQmllint("settings/plugin/elemenpass_pluginSettingTest.qml", true, QStringList(), false)
|
||
.isEmpty());
|
||
}
|
||
|
||
// TODO: Eventually tests for (real) plugins need to be moved into a separate file
|
||
void TestQmllint::quickPlugin()
|
||
{
|
||
const auto &plugins = m_linter.plugins();
|
||
|
||
const bool pluginFound =
|
||
std::find_if(plugins.cbegin(), plugins.cend(),
|
||
[](const auto &plugin) { return plugin.name() == "Quick"; })
|
||
!= plugins.cend();
|
||
QVERIFY(pluginFound);
|
||
|
||
runTest("pluginQuick_anchors.qml",
|
||
Result{ { Message{
|
||
u"Cannot specify left, right, and horizontalCenter anchors at the same time."_s },
|
||
Message {
|
||
u"Cannot specify top, bottom, and verticalCenter anchors at the same time."_s },
|
||
Message{
|
||
u"Baseline anchor cannot be used in conjunction with top, bottom, or verticalCenter anchors."_s },
|
||
Message { u"Cannot assign literal of type null to QQuickAnchorLine"_s, 5,
|
||
35 },
|
||
Message { u"Cannot assign literal of type null to QQuickAnchorLine"_s, 6,
|
||
33 } } });
|
||
runTest("pluginQuick_anchorsUndefined.qml", Result::clean());
|
||
runTest("pluginQuick_layoutChildren.qml",
|
||
Result {
|
||
{ Message {
|
||
u"Detected anchors on an item that is managed by a layout. This is undefined behavior; use Layout.alignment instead."_s },
|
||
Message {
|
||
u"Detected x on an item that is managed by a layout. This is undefined behavior; use Layout.leftMargin or Layout.rightMargin instead."_s },
|
||
Message {
|
||
u"Detected y on an item that is managed by a layout. This is undefined behavior; use Layout.topMargin or Layout.bottomMargin instead."_s },
|
||
Message {
|
||
u"Detected height on an item that is managed by a layout. This is undefined behavior; use implictHeight or Layout.preferredHeight instead."_s },
|
||
Message {
|
||
u"Detected width on an item that is managed by a layout. This is undefined behavior; use implicitWidth or Layout.preferredWidth instead."_s },
|
||
Message {
|
||
u"Cannot specify anchors for items inside Grid. Grid will not function."_s },
|
||
Message {
|
||
u"Cannot specify x for items inside Grid. Grid will not function."_s },
|
||
Message {
|
||
u"Cannot specify y for items inside Grid. Grid will not function."_s },
|
||
Message {
|
||
u"Cannot specify anchors for items inside Flow. Flow will not function."_s },
|
||
Message {
|
||
u"Cannot specify x for items inside Flow. Flow will not function."_s },
|
||
Message {
|
||
u"Cannot specify y for items inside Flow. Flow will not function."_s } } });
|
||
runTest("pluginQuick_attached.qml",
|
||
Result {
|
||
{ Message { u"ToolTip attached property must be attached to an object deriving from Item"_s },
|
||
Message { u"SplitView attached property must be attached to an object deriving from Item"_s },
|
||
Message { u"ScrollIndicator attached property must be attached to an object deriving from Flickable"_s },
|
||
Message { u"ScrollBar attached property must be attached to an object deriving from Flickable or ScrollView"_s },
|
||
Message { u"Accessible attached property must be attached to an object deriving from Item or Action"_s },
|
||
Message { u"EnterKey attached property must be attached to an object deriving from Item"_s },
|
||
Message {
|
||
u"LayoutMirroring attached property must be attached to an object deriving from Item or Window"_s },
|
||
Message { u"Layout attached property must be attached to an object deriving from Item"_s },
|
||
Message { u"StackView attached property must be attached to an object deriving from Item"_s },
|
||
Message { u"TextArea attached property must be attached to an object deriving from Flickable"_s },
|
||
Message { u"StackLayout attached property must be attached to an object deriving from Item"_s },
|
||
Message { u"SwipeDelegate attached property must be attached to an object deriving from Item"_s },
|
||
Message { u"SwipeView attached property must be attached to an object deriving from Item"_s } } });
|
||
|
||
{
|
||
const Result result{ {}, { Message{ u"Tumbler"_s }, }, };
|
||
runTest("pluginQuick_tumblerGood.qml", result);
|
||
}
|
||
|
||
runTest("pluginQuick_swipeDelegate.qml",
|
||
Result { {
|
||
Message {
|
||
u"SwipeDelegate: Cannot use horizontal anchors with contentItem; unable to layout the item."_s,
|
||
6, 43 },
|
||
Message {
|
||
u"SwipeDelegate: Cannot use horizontal anchors with background; unable to layout the item."_s,
|
||
7, 43 },
|
||
Message { u"SwipeDelegate: Cannot set both behind and left/right properties"_s,
|
||
9, 9 },
|
||
Message {
|
||
u"SwipeDelegate: Cannot use horizontal anchors with contentItem; unable to layout the item."_s,
|
||
13, 47 },
|
||
Message {
|
||
u"SwipeDelegate: Cannot use horizontal anchors with background; unable to layout the item."_s,
|
||
14, 42 },
|
||
Message { u"SwipeDelegate: Cannot set both behind and left/right properties"_s,
|
||
16, 9 },
|
||
} });
|
||
|
||
runTest("pluginQuick_varProp.qml",
|
||
Result {
|
||
{ Message {
|
||
u"Unexpected type for property \"contentItem\" expected QQuickPathView, QQuickListView got QQuickItem"_s },
|
||
Message {
|
||
u"Unexpected type for property \"columnWidthProvider\" expected function got null"_s },
|
||
Message {
|
||
u"Unexpected type for property \"textFromValue\" expected function got null"_s },
|
||
Message {
|
||
u"Unexpected type for property \"valueFromText\" expected function got int"_s },
|
||
Message {
|
||
u"Unexpected type for property \"rowHeightProvider\" expected function got int"_s } } });
|
||
runTest("pluginQuick_varPropClean.qml", Result::clean());
|
||
runTest("pluginQuick_attachedClean.qml", Result::clean());
|
||
runTest("pluginQuick_attachedIgnore.qml", Result::clean());
|
||
runTest("pluginQuick_noCrashOnUneresolved.qml", Result {}); // we don't care about the specific warnings
|
||
|
||
runTest("pluginQuick_propertyChangesParsed.qml",
|
||
Result { {
|
||
Message {
|
||
u"Property \"myColor\" is custom-parsed in PropertyChanges. "
|
||
"You should phrase this binding as \"foo.myColor: Qt.rgba(0.5, ...\""_s,
|
||
12, 30
|
||
},
|
||
Message {
|
||
u"You should remove any bindings on the \"target\" property and avoid "
|
||
"custom-parsed bindings in PropertyChanges."_s,
|
||
11, 29
|
||
},
|
||
Message {
|
||
u"Unknown property \"notThere\" in PropertyChanges."_s,
|
||
13, 31
|
||
}
|
||
} });
|
||
runTest("pluginQuick_propertyChangesInvalidTarget.qml", Result {}); // we don't care about the specific warnings
|
||
}
|
||
|
||
void TestQmllint::environment_data()
|
||
{
|
||
QTest::addColumn<QString>("file");
|
||
QTest::addColumn<bool>("shouldSucceed");
|
||
QTest::addColumn<QStringList>("extraArgs");
|
||
QTest::addColumn<Environment>("env");
|
||
QTest::addColumn<QString>("expectedWarning");
|
||
|
||
const QString fileThatNeedsImportPath = testFile(u"NeedImportPath.qml"_s);
|
||
const QString importPath = testFile(u"ImportPath"_s);
|
||
const QString invalidImportPath = testFile(u"ImportPathThatDoesNotExist"_s);
|
||
const QString noWarningExpected;
|
||
|
||
QTest::addRow("missing-import-dir")
|
||
<< fileThatNeedsImportPath << false << warningsShouldFailArgs()
|
||
<< Environment{ { u"QML_IMPORT_PATH"_s, importPath } } << noWarningExpected;
|
||
|
||
QTest::addRow("import-dir-via-arg")
|
||
<< fileThatNeedsImportPath << true << QStringList{ u"-I"_s, importPath }
|
||
<< Environment{ { u"QML_IMPORT_PATH"_s, invalidImportPath } } << noWarningExpected;
|
||
|
||
QTest::addRow("import-dir-via-env")
|
||
<< fileThatNeedsImportPath << true << QStringList{ u"-E"_s }
|
||
<< Environment{ { u"QML_IMPORT_PATH"_s, importPath } }
|
||
<< u"Using import directories passed from environment variable \"QML_IMPORT_PATH\": \"%1\"."_s
|
||
.arg(importPath);
|
||
|
||
QTest::addRow("import-dir-via-env2")
|
||
<< fileThatNeedsImportPath << true << QStringList{ u"-E"_s }
|
||
<< Environment{ { u"QML2_IMPORT_PATH"_s, importPath } }
|
||
<< u"Using import directories passed from the deprecated environment variable \"QML2_IMPORT_PATH\": \"%1\"."_s
|
||
.arg(importPath);
|
||
}
|
||
|
||
void TestQmllint::environment()
|
||
{
|
||
QFETCH(QString, file);
|
||
QFETCH(bool, shouldSucceed);
|
||
QFETCH(QStringList, extraArgs);
|
||
QFETCH(Environment, env);
|
||
QFETCH(QString, expectedWarning);
|
||
|
||
const QString output = runQmllint(file, shouldSucceed, extraArgs, false, true, false, env);
|
||
if (!expectedWarning.isEmpty()) {
|
||
QVERIFY(output.contains(expectedWarning));
|
||
}
|
||
}
|
||
|
||
void TestQmllint::maxWarnings()
|
||
{
|
||
// warnings are not fatal by default
|
||
runQmllint(testFile("badScript.qml"), true);
|
||
// or when max-warnings is set to -1
|
||
runQmllint(testFile("badScript.qml"), true, {"-W", "-1"});
|
||
// 1 warning => should fail
|
||
runQmllint(testFile("badScript.qml"), false, {"--max-warnings", "0"});
|
||
// only 2 warning => should exit normally
|
||
runQmllint(testFile("badScript.qml"), true, {"--max-warnings", "2"});
|
||
}
|
||
|
||
#endif
|
||
|
||
void TestQmllint::ignoreSettingsNotCommandLineOptions()
|
||
{
|
||
const QString importPath = testFile(u"ImportPath"_s);
|
||
// makes sure that ignore settings only ignores settings and not command line options like
|
||
// "-I".
|
||
const QString output = runQmllint(testFile(u"NeedImportPath.qml"_s), true,
|
||
QStringList{ u"-I"_s, importPath }, true);
|
||
// should not complain about not finding the module that is in importPath
|
||
QCOMPARE(output, QString());
|
||
}
|
||
|
||
void TestQmllint::backslashedQmldirPath()
|
||
{
|
||
const QString qmldirPath
|
||
= testFile(u"ImportPath/ModuleInImportPath/qmldir"_s).replace('/', QDir::separator());
|
||
const QString output = runQmllint(
|
||
testFile(u"something.qml"_s), true, QStringList{ u"-i"_s, qmldirPath });
|
||
QVERIFY(output.isEmpty());
|
||
}
|
||
|
||
#if QT_CONFIG(process)
|
||
void TestQmllint::importRelScript()
|
||
{
|
||
QProcess proc;
|
||
proc.start(m_qmllintPath, { QStringLiteral(TST_QMLLINT_IMPORT_REL_SCRIPT_ARGS) });
|
||
QVERIFY(proc.waitForFinished());
|
||
QVERIFY(proc.readAllStandardOutput().isEmpty());
|
||
QVERIFY(proc.readAllStandardError().isEmpty());
|
||
}
|
||
#endif
|
||
|
||
QTEST_GUILESS_MAIN(TestQmllint)
|
||
#include "tst_qmllint.moc"
|