qtdeclarative/tests/auto/qml/qmllint/tst_qmllint.cpp

2444 lines
120 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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"