qmllint: Use fix suggestions in a more structured way

Fix suggestions are now attached to the warnings they are caused by
and are also accessible via JSON.

This allows us to use the qmllint library for more of tst_qmllint,
greatly improving performance.

Change-Id: Idd0398028bff1272a75dc1193d2c15a25d335dbf
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Maximilian Goldstein 2021-12-07 18:48:08 +01:00
parent d4d56b519d
commit 52c09a27ef
7 changed files with 385 additions and 372 deletions

View File

@ -834,9 +834,7 @@ void QQmlJSImportVisitor::checkSignals()
const QQmlJSScope::ConstPtr signalScope = it.key(); const QQmlJSScope::ConstPtr signalScope = it.key();
if (!signalScope->hasMethod(signal)) { if (!signalScope->hasMethod(signal)) {
m_logger->logWarning(QStringLiteral("no matching signal found for handler \"%1\"") std::optional<FixSuggestion> fix;
.arg(pair.first),
Log_UnqualifiedAccess, location);
// There is a small chance of suggesting this fix for things that are not actually // There is a small chance of suggesting this fix for things that are not actually
// QtQml/Connections elements, but rather some other thing that is also called // QtQml/Connections elements, but rather some other thing that is also called
@ -848,22 +846,21 @@ void QQmlJSImportVisitor::checkSignals()
const qsizetype newLength = m_logger->code().indexOf(u'\n', location.end()) const qsizetype newLength = m_logger->code().indexOf(u'\n', location.end())
- location.offset; - location.offset;
const FixSuggestion suggestion { fix = FixSuggestion { { FixSuggestion::Fix {
Log_UnqualifiedAccess, QStringLiteral("Implicitly defining %1 as signal handler in "
{ "Connections is deprecated. Create a function "
FixSuggestion::Fix { "instead")
QStringLiteral("Implicitly defining %1 as signal handler in " .arg(pair.first),
"Connections is deprecated. Create a function " QQmlJS::SourceLocation(location.offset, newLength, location.startLine,
"instead").arg(pair.first), location.startColumn),
QQmlJS::SourceLocation(location.offset, newLength, QStringLiteral("function %1(%2) { ... }")
location.startLine, location.startColumn), .arg(pair.first, pair.second.join(u", ")) } } };
QStringLiteral("function %1(%2) { ... }")
.arg(pair.first, pair.second.join(u", "))
}
}
};
m_logger->suggestFix(suggestion);
} }
m_logger->logWarning(QStringLiteral("no matching signal found for handler \"%1\"")
.arg(pair.first),
Log_UnqualifiedAccess, location, true, true, fix);
continue; continue;
} }

View File

@ -134,7 +134,7 @@ static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
void QQmlJSLogger::log(const QString &message, QQmlJSLoggerCategory category, void QQmlJSLogger::log(const QString &message, QQmlJSLoggerCategory category,
const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext, const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext,
bool showFileName) bool showFileName, const std::optional<FixSuggestion> &suggestion)
{ {
if (isMsgTypeLess(type, m_categoryLevels[category])) if (isMsgTypeLess(type, m_categoryLevels[category]))
return; return;
@ -166,10 +166,11 @@ void QQmlJSLogger::log(const QString &message, QQmlJSLoggerCategory category,
machineType = type; machineType = type;
} }
QQmlJS::DiagnosticMessage diagMsg; Message diagMsg;
diagMsg.message = message; diagMsg.message = message;
diagMsg.loc = srcLocation; diagMsg.loc = srcLocation;
diagMsg.type = machineType; diagMsg.type = machineType;
diagMsg.fixSuggestion = suggestion;
switch (machineType) { switch (machineType) {
case QtWarningMsg: m_warnings.push_back(diagMsg); break; case QtWarningMsg: m_warnings.push_back(diagMsg); break;
@ -180,13 +181,9 @@ void QQmlJSLogger::log(const QString &message, QQmlJSLoggerCategory category,
if (srcLocation.isValid() && !m_code.isEmpty() && showContext) if (srcLocation.isValid() && !m_code.isEmpty() && showContext)
printContext(srcLocation); printContext(srcLocation);
}
void QQmlJSLogger::suggestFix(const FixSuggestion &fix) if (suggestion.has_value())
{ printFix(suggestion.value());
if (isMsgTypeLess(QtInfoMsg, m_categoryLevels[fix.category]))
return;
printFix(fix);
} }
void QQmlJSLogger::processMessages(const QList<QQmlJS::DiagnosticMessage> &messages, void QQmlJSLogger::processMessages(const QList<QQmlJS::DiagnosticMessage> &messages,

View File

@ -50,6 +50,8 @@
#include <QtCore/qlist.h> #include <QtCore/qlist.h>
#include <QtCore/qset.h> #include <QtCore/qset.h>
#include <optional>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
/*! /*!
@ -113,8 +115,6 @@ enum QQmlJSLoggerCategory {
struct FixSuggestion struct FixSuggestion
{ {
QQmlJSLoggerCategory category;
struct Fix struct Fix
{ {
QString message; QString message;
@ -124,6 +124,11 @@ struct FixSuggestion
QList<Fix> fixes; QList<Fix> fixes;
}; };
struct Message : public QQmlJS::DiagnosticMessage
{
std::optional<FixSuggestion> fixSuggestion;
};
class QQmlJSLogger class QQmlJSLogger
{ {
Q_DISABLE_COPY_MOVE(QQmlJSLogger) Q_DISABLE_COPY_MOVE(QQmlJSLogger)
@ -188,9 +193,9 @@ public:
bool hasWarnings() const { return !m_warnings.isEmpty(); } bool hasWarnings() const { return !m_warnings.isEmpty(); }
bool hasErrors() const { return !m_errors.isEmpty(); } bool hasErrors() const { return !m_errors.isEmpty(); }
const QList<QQmlJS::DiagnosticMessage> &infos() const { return m_infos; } const QList<Message> &infos() const { return m_infos; }
const QList<QQmlJS::DiagnosticMessage> &warnings() const { return m_warnings; } const QList<Message> &warnings() const { return m_warnings; }
const QList<QQmlJS::DiagnosticMessage> &errors() const { return m_errors; } const QList<Message> &errors() const { return m_errors; }
QtMsgType categoryLevel(QQmlJSLoggerCategory category) const { return m_categoryLevels[category]; } QtMsgType categoryLevel(QQmlJSLoggerCategory category) const { return m_categoryLevels[category]; }
void setCategoryLevel(QQmlJSLoggerCategory category, QtMsgType Level) { m_categoryLevels[category] = Level; } void setCategoryLevel(QQmlJSLoggerCategory category, QtMsgType Level) { m_categoryLevels[category] = Level; }
@ -203,27 +208,28 @@ public:
void logInfo(const QString &message, QQmlJSLoggerCategory category, void logInfo(const QString &message, QQmlJSLoggerCategory category,
const QQmlJS::SourceLocation &srcLocation = QQmlJS::SourceLocation(), const QQmlJS::SourceLocation &srcLocation = QQmlJS::SourceLocation(),
bool showContext = true, bool showFileName = true) bool showContext = true, bool showFileName = true,
const std::optional<FixSuggestion> &suggestion = {})
{ {
log(message, category, srcLocation, QtInfoMsg, showContext, showFileName); log(message, category, srcLocation, QtInfoMsg, showContext, showFileName, suggestion);
} }
void logWarning(const QString &message, QQmlJSLoggerCategory category, void logWarning(const QString &message, QQmlJSLoggerCategory category,
const QQmlJS::SourceLocation &srcLocation = QQmlJS::SourceLocation(), const QQmlJS::SourceLocation &srcLocation = QQmlJS::SourceLocation(),
bool showContext = true, bool showFileName = true) bool showContext = true, bool showFileName = true,
const std::optional<FixSuggestion> &suggestion = {})
{ {
log(message, category, srcLocation, QtWarningMsg, showContext, showFileName); log(message, category, srcLocation, QtWarningMsg, showContext, showFileName, suggestion);
} }
void logCritical(const QString &message, QQmlJSLoggerCategory category, void logCritical(const QString &message, QQmlJSLoggerCategory category,
const QQmlJS::SourceLocation &srcLocation = QQmlJS::SourceLocation(), const QQmlJS::SourceLocation &srcLocation = QQmlJS::SourceLocation(),
bool showContext = true, bool showFileName = true) bool showContext = true, bool showFileName = true,
const std::optional<FixSuggestion> &suggestion = {})
{ {
log(message, category, srcLocation, QtCriticalMsg, showContext, showFileName); log(message, category, srcLocation, QtCriticalMsg, showContext, showFileName, suggestion);
} }
void suggestFix(const FixSuggestion &fix);
void processMessages(const QList<QQmlJS::DiagnosticMessage> &messages, QtMsgType level, void processMessages(const QList<QQmlJS::DiagnosticMessage> &messages, QtMsgType level,
QQmlJSLoggerCategory category); QQmlJSLoggerCategory category);
@ -246,7 +252,8 @@ private:
void printFix(const FixSuggestion &fix); void printFix(const FixSuggestion &fix);
void log(const QString &message, QQmlJSLoggerCategory category, const QQmlJS::SourceLocation &, void log(const QString &message, QQmlJSLoggerCategory category, const QQmlJS::SourceLocation &,
QtMsgType type, bool showContext, bool showFileName); QtMsgType type, bool showContext, bool showFileName,
const std::optional<FixSuggestion> &suggestion);
QString m_fileName; QString m_fileName;
QString m_code; QString m_code;
@ -256,9 +263,9 @@ private:
QtMsgType m_categoryLevels[QQmlJSLoggerCategory_Last + 1] = {}; QtMsgType m_categoryLevels[QQmlJSLoggerCategory_Last + 1] = {};
bool m_categoryError[QQmlJSLoggerCategory_Last + 1] = {}; bool m_categoryError[QQmlJSLoggerCategory_Last + 1] = {};
QList<QQmlJS::DiagnosticMessage> m_infos; QList<Message> m_infos;
QList<QQmlJS::DiagnosticMessage> m_warnings; QList<Message> m_warnings;
QList<QQmlJS::DiagnosticMessage> m_errors; QList<Message> m_errors;
QHash<uint32_t, QSet<QQmlJSLoggerCategory>> m_ignoredWarnings; QHash<uint32_t, QSet<QQmlJSLoggerCategory>> m_ignoredWarnings;
}; };

View File

@ -268,7 +268,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const
return; return;
} }
m_logger->logWarning(QLatin1String("Unqualified access"), Log_UnqualifiedAccess, location); std::optional<FixSuggestion> suggestion;
auto childScopes = m_function->qmlScope->childScopes(); auto childScopes = m_function->qmlScope->childScopes();
for (qsizetype i = 0; i < m_function->qmlScope->childScopes().length(); i++) { for (qsizetype i = 0; i < m_function->qmlScope->childScopes().length(); i++) {
@ -284,7 +284,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const
if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) { if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
FixSuggestion suggestion { Log_UnqualifiedAccess, {} }; suggestion = FixSuggestion {};
const QQmlJSScope::JavaScriptIdentifier id = jsId.value(); const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
@ -304,7 +304,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const
fixString += handler.isMultiline ? u") "_qs : u") => "_qs; fixString += handler.isMultiline ? u") "_qs : u") => "_qs;
suggestion.fixes << FixSuggestion::Fix { suggestion->fixes << FixSuggestion::Fix {
name name
+ QString::fromLatin1(" is accessible in this scope because " + QString::fromLatin1(" is accessible in this scope because "
"you are handling a signal at %1:%2. Use a " "you are handling a signal at %1:%2. Use a "
@ -313,8 +313,6 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const
.arg(id.location.startColumn), .arg(id.location.startColumn),
fixLocation, fixString fixLocation, fixString
}; };
m_logger->suggestFix(suggestion);
} }
break; break;
} }
@ -325,11 +323,11 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const
if (scope->hasProperty(name)) { if (scope->hasProperty(name)) {
const QString id = m_function->addressableScopes.id(scope); const QString id = m_function->addressableScopes.id(scope);
FixSuggestion suggestion { Log_UnqualifiedAccess, {} }; suggestion = FixSuggestion {};
QQmlJS::SourceLocation fixLocation = location; QQmlJS::SourceLocation fixLocation = location;
fixLocation.length = 0; fixLocation.length = 0;
suggestion.fixes << FixSuggestion::Fix { suggestion->fixes << FixSuggestion::Fix {
name + QLatin1String(" is a member of a parent element\n") name + QLatin1String(" is a member of a parent element\n")
+ QLatin1String(" You can qualify the access with its id " + QLatin1String(" You can qualify the access with its id "
"to avoid this warning:\n"), "to avoid this warning:\n"),
@ -337,16 +335,15 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const
}; };
if (id.isEmpty()) { if (id.isEmpty()) {
suggestion.fixes << FixSuggestion::Fix { suggestion->fixes << FixSuggestion::Fix {
u"You first have to give the element an id"_qs, u"You first have to give the element an id"_qs, QQmlJS::SourceLocation {}, {}
QQmlJS::SourceLocation {},
{}
}; };
} }
m_logger->suggestFix(suggestion);
} }
} }
m_logger->logWarning(QLatin1String("Unqualified access"), Log_UnqualifiedAccess, location, true,
true, suggestion);
} }
void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name, void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
@ -537,12 +534,7 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
const QString id = m_function->addressableScopes.id(scope); const QString id = m_function->addressableScopes.id(scope);
m_logger->logWarning( FixSuggestion suggestion;
u"Using attached type %1 already initialized in a parent scope."_qs.arg(
m_state.accumulatorIn.scopeType()->internalName()),
Log_AttachedPropertyReuse, getCurrentSourceLocation());
FixSuggestion suggestion { Log_AttachedPropertyReuse, {} };
QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation(); QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation();
fixLocation.length = 0; fixLocation.length = 0;
@ -563,7 +555,11 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
{} }; {} };
} }
m_logger->suggestFix(suggestion); m_logger->logWarning(
u"Using attached type %1 already initialized in a parent scope."_qs.arg(
m_state.accumulatorIn.scopeType()->internalName()),
Log_AttachedPropertyReuse, getCurrentSourceLocation(), true, true,
suggestion);
} }
} }
m_typeInfo->usedAttachedTypes.insert(m_function->qmlScope, attachedType); m_typeInfo->usedAttachedTypes.insert(m_function->qmlScope, attachedType);

View File

@ -76,7 +76,8 @@ bool QQmlLinter::lintFile(const QString &filename, const QString *fileContents,
json->append(result); json->append(result);
}); });
auto addJsonWarning = [&](const QQmlJS::DiagnosticMessage &message) { auto addJsonWarning = [&](const QQmlJS::DiagnosticMessage &message,
const std::optional<FixSuggestion> &suggestion = {}) {
QJsonObject jsonMessage; QJsonObject jsonMessage;
QString type; QString type;
@ -112,6 +113,21 @@ bool QQmlLinter::lintFile(const QString &filename, const QString *fileContents,
jsonMessage[u"message"_qs] = message.message; jsonMessage[u"message"_qs] = message.message;
QJsonArray suggestions;
if (suggestion.has_value()) {
for (const auto &fix : suggestion->fixes) {
QJsonObject jsonFix;
jsonFix[u"message"] = fix.message;
jsonFix[u"line"_qs] = static_cast<int>(fix.cutLocation.startLine);
jsonFix[u"column"_qs] = static_cast<int>(fix.cutLocation.startColumn);
jsonFix[u"charOffset"_qs] = static_cast<int>(fix.cutLocation.offset);
jsonFix[u"length"_qs] = static_cast<int>(fix.cutLocation.length);
jsonFix[u"replacement"_qs] = fix.replacementString;
suggestions << jsonFix;
}
}
jsonMessage[u"suggestions"] = suggestions;
warnings << jsonMessage; warnings << jsonMessage;
}; };
@ -153,15 +169,16 @@ bool QQmlLinter::lintFile(const QString &filename, const QString *fileContents,
success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram()) success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram())
: parser.parse(); : parser.parse();
if (!success && !silent) { if (!success) {
const auto diagnosticMessages = parser.diagnosticMessages(); const auto diagnosticMessages = parser.diagnosticMessages();
for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
if (json) { if (json) {
addJsonWarning(m); addJsonWarning(m);
} else { } else if (!silent) {
qWarning().noquote() << QString::fromLatin1("%1:%2 : %3") qWarning().noquote() << QString::fromLatin1("%1:%2:%3: %4")
.arg(filename) .arg(filename)
.arg(m.loc.startLine) .arg(m.loc.startLine)
.arg(m.loc.startColumn)
.arg(m.message); .arg(m.message);
} }
} }
@ -231,11 +248,11 @@ bool QQmlLinter::lintFile(const QString &filename, const QString *fileContents,
if (json) { if (json) {
for (const auto &error : m_logger->errors()) for (const auto &error : m_logger->errors())
addJsonWarning(error); addJsonWarning(error, error.fixSuggestion);
for (const auto &warning : m_logger->warnings()) for (const auto &warning : m_logger->warnings())
addJsonWarning(warning); addJsonWarning(warning, warning.fixSuggestion);
for (const auto &info : m_logger->infos()) for (const auto &info : m_logger->infos())
addJsonWarning(info); addJsonWarning(info, info.fixSuggestion);
} }
}; };

View File

@ -92,13 +92,27 @@ private Q_SLOTS:
void javascriptVariableArgs(); void javascriptVariableArgs();
private: private:
enum DefaultIncludeOption { NoDefaultIncludes, UseDefaultIncludes };
enum ContainOption { StringNotContained, StringContained };
enum ReplacementOption {
NoReplacementSearch,
DoReplacementSearch,
};
QString runQmllint(const QString &fileToLint, std::function<void(QProcess &)> handleResult, QString runQmllint(const QString &fileToLint, std::function<void(QProcess &)> handleResult,
const QStringList &extraArgs = QStringList(), bool ignoreSettings = true, const QStringList &extraArgs = QStringList(), bool ignoreSettings = true,
bool addIncludeDirs = true); bool addIncludeDirs = true);
QString runQmllint(const QString &fileToLint, bool shouldSucceed, QString runQmllint(const QString &fileToLint, bool shouldSucceed,
const QStringList &extraArgs = QStringList(), bool ignoreSettings = true, const QStringList &extraArgs = QStringList(), bool ignoreSettings = true,
bool addIncludeDirs = true); bool addIncludeDirs = true);
void callQmllint(const QString &fileToLint, bool shouldSucceed, QJsonArray *warnings = nullptr); void callQmllint(const QString &fileToLint, bool shouldSucceed, QJsonArray *warnings = nullptr,
QStringList includeDirs = {}, QStringList qmltypesFiles = {},
QStringList resources = {},
DefaultIncludeOption defaultIncludes = UseDefaultIncludes);
void searchWarnings(const QJsonArray &warnings, const QString &string, const QString &filename,
ContainOption shouldContain = StringContained,
ReplacementOption searchReplacements = NoReplacementSearch);
QString m_qmllintPath; QString m_qmllintPath;
QString m_qmljsrootgenPath; QString m_qmljsrootgenPath;
@ -339,488 +353,416 @@ void TestQmllint::dirtyQmlCode_data()
QTest::addColumn<QString>("filename"); QTest::addColumn<QString>("filename");
QTest::addColumn<QString>("warningMessage"); QTest::addColumn<QString>("warningMessage");
QTest::addColumn<QString>("notContained"); QTest::addColumn<QString>("notContained");
QTest::addColumn<QString>("replacement");
QTest::addColumn<bool>("exitsNormally"); QTest::addColumn<bool>("exitsNormally");
QTest::newRow("Invalid_syntax_QML") QTest::newRow("Invalid_syntax_QML")
<< QStringLiteral("failure1.qml") << QStringLiteral("failure1.qml") << QStringLiteral("%1:4:8: Expected token `:'")
<< QStringLiteral("%1:4 : Expected token `:'") << QString() << QString() << false;
<< QString()
<< false;
QTest::newRow("Invalid_syntax_JS") QTest::newRow("Invalid_syntax_JS")
<< QStringLiteral("failure1.js") << QStringLiteral("failure1.js") << QStringLiteral("%1:4:12: Expected token `;'")
<< QStringLiteral("%1:4 : Expected token `;'") << QString() << QString() << false;
<< QString()
<< false;
QTest::newRow("AutomatchedSignalHandler") QTest::newRow("AutomatchedSignalHandler")
<< QStringLiteral("AutomatchedSignalHandler.qml") << QStringLiteral("AutomatchedSignalHandler.qml")
<< QString("Warning: %1:12:36: Unqualified access") << QString("Warning: %1:12:36: Unqualified access") << QString() << QString() << false;
<< QString()
<< false;
QTest::newRow("AutomatchedSignalHandler2") QTest::newRow("AutomatchedSignalHandler2")
<< QStringLiteral("AutomatchedSignalHandler.qml") << QStringLiteral("AutomatchedSignalHandler.qml")
<< QString("Info: Implicitly defining onClicked as signal handler") << QString("Info: Implicitly defining onClicked as signal handler") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("MemberNotFound") QTest::newRow("MemberNotFound")
<< QStringLiteral("memberNotFound.qml") << QStringLiteral("memberNotFound.qml")
<< QString("Warning: %1:6:31: Property \"foo\" not found on type \"QtObject\"") << QString("Warning: %1:6:31: Property \"foo\" not found on type \"QtObject\"")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("UnknownJavascriptMethd") QTest::newRow("UnknownJavascriptMethd")
<< QStringLiteral("unknownJavascriptMethod.qml") << QStringLiteral("unknownJavascriptMethod.qml")
<< QString("Warning: %1:5:25: Property \"foo2\" not found on type \"Methods\"") << QString("Warning: %1:5:25: Property \"foo2\" not found on type \"Methods\"")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("badAlias") << QStringLiteral("badAlias.qml") QTest::newRow("badAlias") << QStringLiteral("badAlias.qml")
<< QString("Warning: %1:3:1: Cannot resolve alias \"wrong\"") << QString("Warning: %1:3:1: Cannot resolve alias \"wrong\"")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("badAliasProperty1") QTest::newRow("badAliasProperty1") << QStringLiteral("badAliasProperty.qml")
<< QStringLiteral("badAliasProperty.qml") << QString("Warning: %1:3:1: Cannot resolve alias \"wrong\"")
<< QString("Warning: %1:3:1: Cannot resolve alias \"wrong\"") << QString() << false; << QString() << QString() << false;
QTest::newRow("badAliasExpression") QTest::newRow("badAliasExpression")
<< QStringLiteral("badAliasExpression.qml") << QStringLiteral("badAliasExpression.qml")
<< QString("Warning: %1:5:26: Invalid alias expression. Only IDs and field member " << QString("Warning: %1:5:26: Invalid alias expression. Only IDs and field member "
"expressions can be aliased") "expressions can be aliased")
<< QString() << QString() << QString() << false;
<< false; QTest::newRow("aliasCycle1") << QStringLiteral(
QTest::newRow("aliasCycle1") "aliasCycle.qml") << QString("Warning: %1:3:1: Alias \"b\" is part of an alias cycle")
<< QStringLiteral("aliasCycle.qml") << QString() << QString() << false;
<< QString("Warning: %1:3:1: Alias \"b\" is part of an alias cycle") QTest::newRow("aliasCycle2") << QStringLiteral(
<< QString() "aliasCycle.qml") << QString("Warning: %1:3:1: Alias \"a\" is part of an alias cycle")
<< false; << QString() << QString() << false;
QTest::newRow("aliasCycle2")
<< QStringLiteral("aliasCycle.qml")
<< QString("Warning: %1:3:1: Alias \"a\" is part of an alias cycle")
<< QString()
<< false;
QTest::newRow("badParent") QTest::newRow("badParent")
<< QStringLiteral("badParent.qml") << QStringLiteral("badParent.qml")
<< QString("Warning: %1:5:34: Property \"rrr\" not found on type \"Item\"") << QString("Warning: %1:5:34: Property \"rrr\" not found on type \"Item\"") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("parentIsComponent") QTest::newRow("parentIsComponent")
<< QStringLiteral("parentIsComponent.qml") << QStringLiteral("parentIsComponent.qml")
<< QString("Warning: %1:7:39: Property \"progress\" not found on type \"QQuickItem\"") << QString("Warning: %1:7:39: Property \"progress\" not found on type \"QQuickItem\"")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("badTypeAssertion") QTest::newRow("badTypeAssertion")
<< QStringLiteral("badTypeAssertion.qml") << QStringLiteral("badTypeAssertion.qml")
<< QString("Warning: %1:5:39: Property \"rrr\" not found on type \"QQuickItem\"") << QString("Warning: %1:5:39: Property \"rrr\" not found on type \"QQuickItem\"")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("incompleteQmltypes") QTest::newRow("incompleteQmltypes")
<< QStringLiteral("incompleteQmltypes.qml") << QStringLiteral("incompleteQmltypes.qml")
<< QString("Warning: %1:5:26: Type \"QPalette\" of property \"palette\" not found") << QString("Warning: %1:5:26: Type \"QPalette\" of property \"palette\" not found")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("incompleteQmltypes2") << QStringLiteral("incompleteQmltypes2.qml") QTest::newRow("incompleteQmltypes2") << QStringLiteral("incompleteQmltypes2.qml")
<< QString("Warning: %1:5:35: Property \"weDontKnowIt\" " << QString("Warning: %1:5:35: Property \"weDontKnowIt\" "
"not found on type \"CustomPalette\"") "not found on type \"CustomPalette\"")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("inheritanceCylce") QTest::newRow("inheritanceCylce") << QStringLiteral("Cycle1.qml")
<< QStringLiteral("Cycle1.qml") << QString("Warning: %1: Cycle2 is part of an inheritance "
<< QString("Warning: %1: Cycle2 is part of an inheritance cycle: Cycle2 -> Cycle3 -> Cycle1 -> Cycle2") "cycle: Cycle2 -> Cycle3 -> Cycle1 -> Cycle2")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("badQmldirImportAndDepend") QTest::newRow("badQmldirImportAndDepend")
<< QStringLiteral("qmldirImportAndDepend/bad.qml") << QStringLiteral("qmldirImportAndDepend/bad.qml")
<< QString("Warning: %1:3:1: Item was not found. Did you add all import paths?") << QString("Warning: %1:3:1: Item was not found. Did you add all import paths?")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("javascriptMethodsInModule") QTest::newRow("javascriptMethodsInModule")
<< QStringLiteral("javascriptMethodsInModuleBad.qml") << QStringLiteral("javascriptMethodsInModuleBad.qml")
<< QString("Warning: %1:5:21: Property \"unknownFunc\" not found on type \"Foo\"") << QString("Warning: %1:5:21: Property \"unknownFunc\" not found on type \"Foo\"")
<< QString() << QString() << QString() << false;
<< false; QTest::newRow("badEnumFromQtQml") << QStringLiteral("badEnumFromQtQml.qml")
QTest::newRow("badEnumFromQtQml") << QString("Warning: %1:4:30: Property \"Linear123\" not "
<< QStringLiteral("badEnumFromQtQml.qml") "found on type \"QQmlEasingEnums\"")
<< QString("Warning: %1:4:30: Property \"Linear123\" not found on type \"QQmlEasingEnums\"") << QString() << QString() << false;
<< QString()
<< false;
QTest::newRow("anchors3") QTest::newRow("anchors3")
<< QStringLiteral("anchors3.qml") << QStringLiteral("anchors3.qml")
<< QString("Cannot assign binding of type QQuickItem to QQuickAnchorLine") << QString("Cannot assign binding of type QQuickItem to QQuickAnchorLine") << QString()
<< QString() << QString() << false;
<< false; QTest::newRow("nanchors1") << QStringLiteral("nanchors1.qml")
QTest::newRow("nanchors1") << QString("unknown grouped property scope nanchors.") << QString()
<< QStringLiteral("nanchors1.qml") << QString() << false;
<< QString("unknown grouped property scope nanchors.") QTest::newRow("nanchors2") << QStringLiteral("nanchors2.qml")
<< QString() << QString("unknown grouped property scope nanchors.") << QString()
<< false; << QString() << false;
QTest::newRow("nanchors2") QTest::newRow("nanchors3") << QStringLiteral("nanchors3.qml")
<< QStringLiteral("nanchors2.qml") << QString("unknown grouped property scope nanchors.") << QString()
<< QString("unknown grouped property scope nanchors.") << QString() << false;
<< QString() QTest::newRow("badAliasObject") << QStringLiteral("badAliasObject.qml")
<< false; << QString("Warning: %1:8:40: Property \"wrongwrongwrong\" not "
QTest::newRow("nanchors3") "found on type \"QtObject\"")
<< QStringLiteral("nanchors3.qml") << QString() << QString() << false;
<< QString("unknown grouped property scope nanchors.")
<< QString()
<< false;
QTest::newRow("badAliasObject")
<< QStringLiteral("badAliasObject.qml")
<< QString("Warning: %1:8:40: Property \"wrongwrongwrong\" not found on type \"QtObject\"")
<< QString()
<< false;
QTest::newRow("badScript") QTest::newRow("badScript")
<< QStringLiteral("badScript.qml") << QStringLiteral("badScript.qml")
<< QString("Warning: %1:5:21: Property \"stuff\" not found on type \"Empty\"") << QString("Warning: %1:5:21: Property \"stuff\" not found on type \"Empty\"")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("badScriptOnAttachedProperty") QTest::newRow("badScriptOnAttachedProperty")
<< QStringLiteral("badScript.attached.qml") << QStringLiteral("badScript.attached.qml")
<< QString("Warning: %1:3:26: Unqualified access") << QString() << false; << QString("Warning: %1:3:26: Unqualified access") << QString() << QString() << false;
QTest::newRow("brokenNamespace") QTest::newRow("brokenNamespace") << QStringLiteral("brokenNamespace.qml")
<< QStringLiteral("brokenNamespace.qml") << QString("Warning: %1:4:19: Type not found in namespace")
<< QString("Warning: %1:4:19: Type not found in namespace") << QString() << false; << QString() << QString() << false;
QTest::newRow("segFault (bad)") QTest::newRow("segFault (bad)")
<< QStringLiteral("SegFault.bad.qml") << QStringLiteral("SegFault.bad.qml")
<< QStringLiteral("Property \"foobar\" not found on type \"QQuickScreenAttached\"") << QStringLiteral("Property \"foobar\" not found on type \"QQuickScreenAttached\"")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("VariableUsedBeforeDeclaration") QTest::newRow("VariableUsedBeforeDeclaration")
<< QStringLiteral("useBeforeDeclaration.qml") << QStringLiteral("useBeforeDeclaration.qml")
<< QStringLiteral("%1:5:9: Variable \"argq\" is used here before its declaration. " << QStringLiteral("%1:5:9: Variable \"argq\" is used here before its declaration. "
"The declaration is at 6:13.") "The declaration is at 6:13.")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("SignalParameterMismatch") QTest::newRow("SignalParameterMismatch")
<< QStringLiteral("namedSignalParameters.qml") << QStringLiteral("namedSignalParameters.qml")
<< QStringLiteral("Parameter 1 to signal handler for \"onSig\" is called \"argarg\". " << QStringLiteral("Parameter 1 to signal handler for \"onSig\" is called \"argarg\". "
"The signal has a parameter of the same name in position 2.") "The signal has a parameter of the same name in position 2.")
<< QStringLiteral("onSig2") << QStringLiteral("onSig2") << QString() << false;
<< false;
QTest::newRow("TooManySignalParameters") QTest::newRow("TooManySignalParameters")
<< QStringLiteral("tooManySignalParameters.qml") << QStringLiteral("tooManySignalParameters.qml")
<< QStringLiteral("Signal handler for \"onSig\" has more formal parameters " << QStringLiteral("Signal handler for \"onSig\" has more formal parameters "
"than the signal it handles.") "than the signal it handles.")
<< QString() << QString() << QString() << false;
<< false; QTest::newRow("OnAssignment") << QStringLiteral("onAssignment.qml")
QTest::newRow("OnAssignment") << QStringLiteral("Property \"loops\" not found on type \"bool\"")
<< QStringLiteral("onAssignment.qml") << QString() << QString() << false;
<< QStringLiteral("Property \"loops\" not found on type \"bool\"") QTest::newRow("BadAttached") << QStringLiteral("badAttached.qml")
<< QString() << QStringLiteral("unknown attached property scope WrongAttached.")
<< false; << QString() << QString() << false;
QTest::newRow("BadAttached") QTest::newRow("BadBinding") << QStringLiteral("badBinding.qml")
<< QStringLiteral("badAttached.qml") << QStringLiteral(
<< QStringLiteral("unknown attached property scope WrongAttached.") "Binding assigned to \"doesNotExist\", but no property "
<< QString() "\"doesNotExist\" exists in the current element.")
<< false; << QString() << QString() << false;
QTest::newRow("bad template literal (simple)") QTest::newRow("bad template literal (simple)")
<< QStringLiteral("badTemplateStringSimple.qml") << QStringLiteral("badTemplateStringSimple.qml")
<< QStringLiteral("Cannot assign binding of type string to int") << QStringLiteral("Cannot assign binding of type string to int") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("bad template literal (substitution)") QTest::newRow("bad template literal (substitution)")
<< QStringLiteral("badTemplateStringSubstitution.qml") << QStringLiteral("badTemplateStringSubstitution.qml")
<< QStringLiteral("Cannot assign binding of type QString to int") << QStringLiteral("Cannot assign binding of type QString to int") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("bad constant number to string") QTest::newRow("bad constant number to string")
<< QStringLiteral("numberToStringProperty.qml") << QStringLiteral("numberToStringProperty.qml")
<< QStringLiteral("Cannot assign a numeric constant to a string property") << QStringLiteral("Cannot assign a numeric constant to a string property") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("bad unary minus to string") QTest::newRow("bad unary minus to string")
<< QStringLiteral("unaryMinusToStringProperty.qml") << QStringLiteral("unaryMinusToStringProperty.qml")
<< QStringLiteral("Cannot assign a numeric constant to a string property") << QStringLiteral("Cannot assign a numeric constant to a string property") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("bad tranlsation binding (qsTr)") QTest::newRow("bad tranlsation binding (qsTr)")
<< QStringLiteral("bad_qsTr.qml") << QStringLiteral("bad_qsTr.qml") << QStringLiteral("") << QString() << QString()
<< QStringLiteral("")
<< QString()
<< false;
QTest::newRow("bad string binding (QT_TR_NOOP)")
<< QStringLiteral("bad_QT_TR_NOOP.qml")
<< QStringLiteral("Cannot assign binding of type string to int")
<< QString()
<< false;
QTest::newRow("BadBinding")
<< QStringLiteral("badBinding.qml")
<< QStringLiteral("Binding assigned to \"doesNotExist\", but no property "
"\"doesNotExist\" exists in the current element.")
<< QString()
<< false; << false;
QTest::newRow("bad string binding (QT_TR_NOOP)")
<< QStringLiteral("bad_QT_TR_NOOP.qml")
<< QStringLiteral("Cannot assign binding of type string to int") << QString()
<< QString() << false;
QTest::newRow("BadScriptBindingOnGroup") QTest::newRow("BadScriptBindingOnGroup")
<< QStringLiteral("badScriptBinding.group.qml") << QStringLiteral("badScriptBinding.group.qml")
<< QStringLiteral("Warning: %1:3:10: Binding assigned to \"bogusProperty\", but no " << QStringLiteral("Warning: %1:3:10: Binding assigned to \"bogusProperty\", but no "
"property \"bogusProperty\" exists in the current element.") "property \"bogusProperty\" exists in the current element.")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("BadScriptBindingOnAttachedType") QTest::newRow("BadScriptBindingOnAttachedType")
<< QStringLiteral("badScriptBinding.attached.qml") << QStringLiteral("badScriptBinding.attached.qml")
<< QStringLiteral("Warning: %1:5:12: Binding assigned to \"bogusProperty\", but no " << QStringLiteral("Warning: %1:5:12: Binding assigned to \"bogusProperty\", but no "
"property \"bogusProperty\" exists in the current element.") "property \"bogusProperty\" exists in the current element.")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("BadScriptBindingOnAttachedSignalHandler") QTest::newRow("BadScriptBindingOnAttachedSignalHandler")
<< QStringLiteral("badScriptBinding.attachedSignalHandler.qml") << QStringLiteral("badScriptBinding.attachedSignalHandler.qml")
<< QStringLiteral( << QStringLiteral(
"Warning: %1:3:10: no matching signal found for handler \"onBogusSignal\"") "Warning: %1:3:10: no matching signal found for handler \"onBogusSignal\"")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("BadPropertyType") QTest::newRow("BadPropertyType")
<< QStringLiteral("badPropertyType.qml") << QStringLiteral("badPropertyType.qml")
<< QStringLiteral("No type found for property \"bad\". This may be due to a missing " << QStringLiteral("No type found for property \"bad\". This may be due to a missing "
"import statement or incomplete qmltypes files.") "import statement or incomplete qmltypes files.")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("Deprecation (Property, with reason)") QTest::newRow("Deprecation (Property, with reason)")
<< QStringLiteral("deprecatedPropertyReason.qml") << QStringLiteral("deprecatedPropertyReason.qml")
<< QStringLiteral("Property \"deprecated\" is deprecated (Reason: Test)") << QStringLiteral("Property \"deprecated\" is deprecated (Reason: Test)") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("Deprecation (Property, no reason)") QTest::newRow("Deprecation (Property, no reason)")
<< QStringLiteral("deprecatedProperty.qml") << QStringLiteral("deprecatedProperty.qml")
<< QStringLiteral("Property \"deprecated\" is deprecated") << QStringLiteral("Property \"deprecated\" is deprecated") << QString() << QString()
<< QString()
<< false; << false;
QTest::newRow("Deprecation (Property binding, with reason)") QTest::newRow("Deprecation (Property binding, with reason)")
<< QStringLiteral("deprecatedPropertyBindingReason.qml") << QStringLiteral("deprecatedPropertyBindingReason.qml")
<< QStringLiteral("Binding on deprecated property \"deprecatedReason\" (Reason: Test)") << QStringLiteral("Binding on deprecated property \"deprecatedReason\" (Reason: Test)")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("Deprecation (Property binding, no reason)") QTest::newRow("Deprecation (Property binding, no reason)")
<< QStringLiteral("deprecatedPropertyBinding.qml") << QStringLiteral("deprecatedPropertyBinding.qml")
<< QStringLiteral("Binding on deprecated property \"deprecated\"") << QStringLiteral("Binding on deprecated property \"deprecated\"") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("Deprecation (Type, with reason)") QTest::newRow("Deprecation (Type, with reason)")
<< QStringLiteral("deprecatedTypeReason.qml") << QStringLiteral("deprecatedTypeReason.qml")
<< QStringLiteral("Type \"TypeDeprecatedReason\" is deprecated (Reason: Test)") << QStringLiteral("Type \"TypeDeprecatedReason\" is deprecated (Reason: Test)")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("Deprecation (Type, no reason)") QTest::newRow("Deprecation (Type, no reason)")
<< QStringLiteral("deprecatedType.qml") << QStringLiteral("deprecatedType.qml")
<< QStringLiteral("Type \"TypeDeprecated\" is deprecated") << QStringLiteral("Type \"TypeDeprecated\" is deprecated") << QString() << QString()
<< QString()
<< false; << false;
QTest::newRow("MissingDefaultProperty") QTest::newRow("MissingDefaultProperty")
<< QStringLiteral("defaultPropertyWithoutKeyword.qml") << QStringLiteral("defaultPropertyWithoutKeyword.qml")
<< QStringLiteral("Cannot assign to non-existent default property") << QString() << QStringLiteral("Cannot assign to non-existent default property") << QString()
<< false; << QString() << false;
QTest::newRow("MissingDefaultPropertyDefinedInTheSameType") QTest::newRow("MissingDefaultPropertyDefinedInTheSameType")
<< QStringLiteral("defaultPropertyWithinTheSameType.qml") << QStringLiteral("defaultPropertyWithinTheSameType.qml")
<< QStringLiteral("Cannot assign to non-existent default property") << QString() << QStringLiteral("Cannot assign to non-existent default property") << QString()
<< false; << QString() << false;
QTest::newRow("DoubleAssignToDefaultProperty") QTest::newRow("DoubleAssignToDefaultProperty")
<< QStringLiteral("defaultPropertyWithDoubleAssignment.qml") << QStringLiteral("defaultPropertyWithDoubleAssignment.qml")
<< QStringLiteral("Cannot assign multiple objects to a default non-list property") << QStringLiteral("Cannot assign multiple objects to a default non-list property")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("DefaultPropertyWithWrongType(string)") QTest::newRow("DefaultPropertyWithWrongType(string)")
<< QStringLiteral("defaultPropertyWithWrongType.qml") << QStringLiteral("defaultPropertyWithWrongType.qml")
<< QStringLiteral("Cannot assign to default property of incompatible type") << QStringLiteral("Cannot assign to default property of incompatible type")
<< QStringLiteral("Cannot assign to non-existent default property") << QStringLiteral("Cannot assign to non-existent default property") << QString()
<< false; << false;
QTest::newRow("MultiDefaultPropertyWithWrongType") QTest::newRow("MultiDefaultPropertyWithWrongType")
<< QStringLiteral("multiDefaultPropertyWithWrongType.qml") << QStringLiteral("multiDefaultPropertyWithWrongType.qml")
<< QStringLiteral("Cannot assign to default property of incompatible type") << QStringLiteral("Cannot assign to default property of incompatible type")
<< QStringLiteral("Cannot assign to non-existent default property") << QStringLiteral("Cannot assign to non-existent default property") << QString()
<< false; << false;
QTest::newRow("InvalidImport") QTest::newRow("InvalidImport")
<< QStringLiteral("invalidImport.qml") << QStringLiteral("invalidImport.qml")
<< QStringLiteral("Failed to import FooBar. Are your include paths set up properly?") << QStringLiteral("Failed to import FooBar. Are your include paths set up properly?")
<< QString() << QString() << QString() << false;
<< false;
QTest::newRow("Unused Import (simple)") QTest::newRow("Unused Import (simple)")
<< QStringLiteral("unused_simple.qml") << QStringLiteral("unused_simple.qml") << QStringLiteral("Unused import at %1:1:1")
<< QStringLiteral("Unused import at %1:1:1") << QString() << QString() << true;
<< QString()
<< true;
QTest::newRow("Unused Import (prefix)") QTest::newRow("Unused Import (prefix)")
<< QStringLiteral("unused_prefix.qml") << QStringLiteral("unused_prefix.qml") << QStringLiteral("Unused import at %1:1:1")
<< QStringLiteral("Unused import at %1:1:1") << QString() << QString() << true;
<< QString() QTest::newRow("TypePropertAccess") << QStringLiteral("typePropertyAccess.qml") << QString()
<< true; << QString() << QString() << false;
QTest::newRow("TypePropertAccess")
<< QStringLiteral("typePropertyAccess.qml")
<< QString()
<< QString()
<< false;
QTest::newRow("badAttachedProperty") QTest::newRow("badAttachedProperty")
<< QStringLiteral("badAttachedProperty.qml") << QStringLiteral("badAttachedProperty.qml")
<< QString("Property \"progress\" not found on type \"TestType\"") << QString("Property \"progress\" not found on type \"TestType\"") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("badAttachedPropertyNested") QTest::newRow("badAttachedPropertyNested")
<< QStringLiteral("badAttachedPropertyNested.qml") << QStringLiteral("badAttachedPropertyNested.qml")
<< QString("12:41: Property \"progress\" not found on type \"QObject\"") << QString("12:41: Property \"progress\" not found on type \"QObject\"")
<< QString("6:37: Property \"progress\" not found on type \"QObject\"") << QString("6:37: Property \"progress\" not found on type \"QObject\"") << QString()
<< false; << false;
QTest::newRow("badAttachedPropertyTypeString") QTest::newRow("badAttachedPropertyTypeString")
<< QStringLiteral("badAttachedPropertyTypeString.qml") << QStringLiteral("badAttachedPropertyTypeString.qml")
<< QString("Cannot assign binding of type string to int") << QString() << false; << QString("Cannot assign binding of type string to int") << QString() << QString()
<< false;
QTest::newRow("badAttachedPropertyTypeQtObject") QTest::newRow("badAttachedPropertyTypeQtObject")
<< QStringLiteral("badAttachedPropertyTypeQtObject.qml") << QStringLiteral("badAttachedPropertyTypeQtObject.qml")
<< QString("Property \"count\" of type \"int\" is assigned an incompatible type " << QString("Property \"count\" of type \"int\" is assigned an incompatible type "
"\"QtObject\"") "\"QtObject\"")
<< QString() << QString() << QString() << false;
<< false;
// should succeed, but it does not: // should succeed, but it does not:
QTest::newRow("attachedPropertyAccess") QTest::newRow("attachedPropertyAccess") << QStringLiteral("goodAttachedPropertyAccess.qml")
<< QStringLiteral("goodAttachedPropertyAccess.qml") << QString() << QString() << QString() << true;
<< QString()
<< QString()
<< true;
// should succeed, but it does not: // should succeed, but it does not:
QTest::newRow("attachedPropertyNested") QTest::newRow("attachedPropertyNested") << QStringLiteral("goodAttachedPropertyNested.qml")
<< QStringLiteral("goodAttachedPropertyNested.qml") << QString() << QString() << QString() << true;
<< QString()
<< QString()
<< true;
QTest::newRow("deprecatedFunction") QTest::newRow("deprecatedFunction")
<< QStringLiteral("deprecatedFunction.qml") << QStringLiteral("deprecatedFunction.qml")
<< QStringLiteral("Method \"deprecated(foobar)\" is deprecated (Reason: No particular reason.)") << QStringLiteral("Method \"deprecated(foobar)\" is deprecated (Reason: No particular "
<< QString() "reason.)")
<< false; << QString() << QString() << false;
QTest::newRow("deprecatedFunctionInherited") QTest::newRow("deprecatedFunctionInherited")
<< QStringLiteral("deprecatedFunctionInherited.qml") << QStringLiteral("deprecatedFunctionInherited.qml")
<< QStringLiteral("Method \"deprecatedInherited(c, d)\" is deprecated (Reason: This deprecation should be visible!)") << QStringLiteral("Method \"deprecatedInherited(c, d)\" is deprecated (Reason: This "
<< QString() "deprecation should be visible!)")
<< false; << QString() << QString() << false;
QTest::newRow("string as id") QTest::newRow("string as id") << QStringLiteral("stringAsId.qml")
<< QStringLiteral("stringAsId.qml") << QStringLiteral("ids do not need quotation marks") << QString()
<< QStringLiteral("ids do not need quotation marks") << QString() << false;
<< QString() QTest::newRow("stringIdUsedInWarning") << QStringLiteral("stringIdUsedInWarning.qml")
<< false; << QStringLiteral("i is a member of a parent element")
QTest::newRow("stringIdUsedInWarning") << QString() << QStringLiteral("stringy.") << false;
<< QStringLiteral("stringIdUsedInWarning.qml")
<< QStringLiteral("Component.onCompleted: console.log(stringy.i)")
<< QString()
<< false;
QTest::newRow("Invalid id (expression)") QTest::newRow("Invalid id (expression)")
<< QStringLiteral("invalidId1.qml") << QStringLiteral("invalidId1.qml") << QStringLiteral("Failed to parse id") << QString()
<< QStringLiteral("Failed to parse id") << QString() << false;
<< QString()
<< false;
QTest::newRow("multilineString") QTest::newRow("multilineString")
<< QStringLiteral("multilineString.qml") << QStringLiteral("multilineString.qml")
<< QStringLiteral("String contains unescaped line terminator which is deprecated. Use " << QStringLiteral("String contains unescaped line terminator which is deprecated. Use "
"a template literal instead.") "a template literal instead.")
<< QString() << true; << QString() << QString() << true;
QTest::newRow("unresolvedType") QTest::newRow("unresolvedType")
<< QStringLiteral("unresolvedType.qml") << QStringLiteral("unresolvedType.qml")
<< QStringLiteral("UnresolvedType was not found. Did you add all import paths?") << QStringLiteral("UnresolvedType was not found. Did you add all import paths?")
<< QStringLiteral("incompatible type") << false; << QStringLiteral("incompatible type") << QString() << false;
QTest::newRow("invalidInterceptor") QTest::newRow("invalidInterceptor")
<< QStringLiteral("invalidInterceptor.qml") << QStringLiteral("invalidInterceptor.qml")
<< QStringLiteral("On-binding for property \"angle\" has wrong type \"Item\"") << QStringLiteral("On-binding for property \"angle\" has wrong type \"Item\"")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("2Interceptors") QTest::newRow("2Interceptors") << QStringLiteral("2interceptors.qml")
<< QStringLiteral("2interceptors.qml") << QStringLiteral("Duplicate interceptor on property \"x\"")
<< QStringLiteral("Duplicate interceptor on property \"x\"") << QString() << false; << QString() << QString() << false;
QTest::newRow("ValueSource+2Interceptors") QTest::newRow("ValueSource+2Interceptors")
<< QStringLiteral("valueSourceBetween2interceptors.qml") << QStringLiteral("valueSourceBetween2interceptors.qml")
<< QStringLiteral("Duplicate interceptor on property \"x\"") << QString() << false; << QStringLiteral("Duplicate interceptor on property \"x\"") << QString() << QString()
QTest::newRow("2ValueSources") << false;
<< QStringLiteral("2valueSources.qml") QTest::newRow("2ValueSources") << QStringLiteral("2valueSources.qml")
<< QStringLiteral("Duplicate value source on property \"x\"") << QString() << false; << QStringLiteral("Duplicate value source on property \"x\"")
<< QString() << QString() << false;
QTest::newRow("ValueSource+Value") QTest::newRow("ValueSource+Value")
<< QStringLiteral("valueSource_Value.qml") << QStringLiteral("valueSource_Value.qml")
<< QStringLiteral("Cannot combine value source and binding on property \"obj\"") << QStringLiteral("Cannot combine value source and binding on property \"obj\"")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("ValueSource+ListValue") QTest::newRow("ValueSource+ListValue")
<< QStringLiteral("valueSource_listValue.qml") << QStringLiteral("valueSource_listValue.qml")
<< QStringLiteral("Cannot combine value source and binding on property \"objs\"") << QStringLiteral("Cannot combine value source and binding on property \"objs\"")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("NonExistentListProperty") QTest::newRow("NonExistentListProperty")
<< QStringLiteral("nonExistentListProperty.qml") << QStringLiteral("nonExistentListProperty.qml")
<< QStringLiteral("Property \"objs\" is invalid or does not exist") << QStringLiteral("Property \"objs\" is invalid or does not exist") << QString()
<< QString() << QString() << false;
<< false;
QTest::newRow("QtQuick.Window 2.0") QTest::newRow("QtQuick.Window 2.0")
<< QStringLiteral("qtquickWindow20.qml") << QStringLiteral("qtquickWindow20.qml")
<< QStringLiteral("Property \"window\" not found on type \"QQuickWindow\"") << QString() << QStringLiteral("Property \"window\" not found on type \"QQuickWindow\"") << QString()
<< false; << QString() << false;
QTest::newRow("unresolvedAttachedType") QTest::newRow("unresolvedAttachedType")
<< QStringLiteral("unresolvedAttachedType.qml") << QStringLiteral("unresolvedAttachedType.qml")
<< QStringLiteral("unknown attached property scope UnresolvedAttachedType.") << QStringLiteral("unknown attached property scope UnresolvedAttachedType.")
<< QStringLiteral("Property \"property\" is invalid or does not exist") << false; << QStringLiteral("Property \"property\" is invalid or does not exist") << QString()
<< false;
QTest::newRow("nestedInlineComponents") QTest::newRow("nestedInlineComponents")
<< QStringLiteral("nestedInlineComponents.qml") << QStringLiteral("nestedInlineComponents.qml")
<< QStringLiteral("Nested inline components are not supported") << QString() << false; << QStringLiteral("Nested inline components are not supported") << QString()
QTest::newRow("WithStatement") << QString() << false;
<< QStringLiteral("WithStatement.qml") QTest::newRow("WithStatement") << QStringLiteral("WithStatement.qml")
<< QStringLiteral("with statements are strongly discouraged") << QString() << false; << QStringLiteral("with statements are strongly discouraged")
<< QString() << QString() << false;
QTest::newRow("BindingTypeMismatch") QTest::newRow("BindingTypeMismatch")
<< QStringLiteral("bindingTypeMismatch.qml") << QStringLiteral("bindingTypeMismatch.qml")
<< QStringLiteral("Cannot assign binding of type QString to int") << QString() << false; << QStringLiteral("Cannot assign binding of type QString to int") << QString()
<< QString() << false;
QTest::newRow("BindingTypeMismatchFunction") QTest::newRow("BindingTypeMismatchFunction")
<< QStringLiteral("bindingTypeMismatchFunction.qml") << QStringLiteral("bindingTypeMismatchFunction.qml")
<< QStringLiteral("Cannot assign binding of type QString to int") << QString() << false; << QStringLiteral("Cannot assign binding of type QString to int") << QString()
<< QString() << false;
QTest::newRow("BadLiteralBinding") QTest::newRow("BadLiteralBinding")
<< QStringLiteral("badLiteralBinding.qml") << QStringLiteral("badLiteralBinding.qml")
<< QStringLiteral("Cannot assign binding of type string to int") << QString() << false; << QStringLiteral("Cannot assign binding of type string to int") << QString()
<< QString() << false;
QTest::newRow("BadLiteralBindingDate") QTest::newRow("BadLiteralBindingDate")
<< QStringLiteral("badLiteralBindingDate.qml") << QStringLiteral("badLiteralBindingDate.qml")
<< QStringLiteral("Cannot assign binding of type QString to QDateTime") << QString() << QStringLiteral("Cannot assign binding of type QString to QDateTime") << QString()
<< false; << QString() << false;
QTest::newRow("BadModulePrefix") QTest::newRow("BadModulePrefix")
<< QStringLiteral("badModulePrefix.qml") << QStringLiteral("badModulePrefix.qml")
<< QStringLiteral("Cannot load singleton as property of object") << QString() << false; << QStringLiteral("Cannot load singleton as property of object") << QString()
<< QString() << false;
QTest::newRow("BadModulePrefix2") QTest::newRow("BadModulePrefix2")
<< QStringLiteral("badModulePrefix2.qml") << QStringLiteral("badModulePrefix2.qml")
<< QStringLiteral("Cannot use non-reference type QRectF as base " << QStringLiteral("Cannot use non-reference type QRectF as base "
"of namespaced attached type") "of namespaced attached type")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("AssignToReadOnlyProperty") QTest::newRow("AssignToReadOnlyProperty")
<< QStringLiteral("assignToReadOnlyProperty.qml") << QStringLiteral("assignToReadOnlyProperty.qml")
<< QStringLiteral("Cannot assign to read-only property activeFocus") << QString() << QStringLiteral("Cannot assign to read-only property activeFocus") << QString()
<< false; << QString() << false;
QTest::newRow("AssignToReadOnlyProperty") QTest::newRow("AssignToReadOnlyProperty")
<< QStringLiteral("assignToReadOnlyProperty2.qml") << QStringLiteral("assignToReadOnlyProperty2.qml")
<< QStringLiteral("Cannot assign to read-only property activeFocus") << QString() << QStringLiteral("Cannot assign to read-only property activeFocus") << QString()
<< false; << QString() << false;
QTest::newRow("DeferredPropertyID") QTest::newRow("DeferredPropertyID")
<< QStringLiteral("deferredPropertyID.qml") << QStringLiteral("deferredPropertyID.qml")
<< QStringLiteral( << QStringLiteral(
"Cannot defer property assignment to " "Cannot defer property assignment to "
"\"contentData\". Assigning an id to an object or one of its sub-objects " "\"contentData\". Assigning an id to an object or one of its sub-objects "
"bound to a deferred property will make the assignment immediate.") "bound to a deferred property will make the assignment immediate.")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("DeferredPropertyNestedID") QTest::newRow("DeferredPropertyNestedID")
<< QStringLiteral("deferredPropertyNestedID.qml") << QStringLiteral("deferredPropertyNestedID.qml")
<< QStringLiteral( << QStringLiteral(
"Cannot defer property assignment to " "Cannot defer property assignment to "
"\"contentData\". Assigning an id to an object or one of its sub-objects " "\"contentData\". Assigning an id to an object or one of its sub-objects "
"bound to a deferred property will make the assignment immediate.") "bound to a deferred property will make the assignment immediate.")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("cachedDependency") QTest::newRow("cachedDependency")
<< QStringLiteral("cachedDependency.qml") << QStringLiteral("cachedDependency.qml") << QStringLiteral("Unused import at %1:1:1")
<< QStringLiteral("Unused import at %1:1:1") << QStringLiteral("Cannot assign binding of type QQuickItem to QObject") << QString()
<< QStringLiteral("Cannot assign binding of type QQuickItem to QObject")
<< true; << true;
QTest::newRow("cycle in import") QTest::newRow("cycle in import")
<< QStringLiteral("cycleHead.qml") << QStringLiteral("cycleHead.qml")
<< QStringLiteral("MenuItem is part of an inheritance cycle: MenuItem -> MenuItem") << QStringLiteral("MenuItem is part of an inheritance cycle: MenuItem -> MenuItem")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("badGeneralizedGroup1") QTest::newRow("badGeneralizedGroup1")
<< QStringLiteral("badGeneralizedGroup1.qml") << QStringLiteral("badGeneralizedGroup1.qml")
<< QStringLiteral("Binding assigned to \"aaaa\", " << QStringLiteral("Binding assigned to \"aaaa\", "
"but no property \"aaaa\" exists in the current element") "but no property \"aaaa\" exists in the current element")
<< QString() << false; << QString() << QString() << false;
QTest::newRow("badGeneralizedGroup2") QTest::newRow("badGeneralizedGroup2") << QStringLiteral("badGeneralizedGroup2.qml")
<< QStringLiteral("badGeneralizedGroup2.qml") << QStringLiteral("unknown grouped property scope aself")
<< QStringLiteral("unknown grouped property scope aself") << QString() << QString() << false;
<< QString() << false;
QTest::newRow("missingQmltypes") QTest::newRow("missingQmltypes")
<< QStringLiteral("missingQmltypes.qml") << QStringLiteral("missingQmltypes.qml")
<< QStringLiteral("QML types file does not exist") << QStringLiteral("QML types file does not exist") << QString() << QString() << false;
<< QString() << false; QTest::newRow("enumInvalid") << QStringLiteral(
QTest::newRow("enumInvalid") "enumInvalid.qml") << QStringLiteral("Property \"red\" not found on type \"QtObject\"")
<< QStringLiteral("enumInvalid.qml") << QString() << QString() << false;
<< QStringLiteral("Property \"red\" not found on type \"QtObject\"")
<< QString() << false;
QTest::newRow("inaccessibleId") QTest::newRow("inaccessibleId")
<< QStringLiteral("inaccessibleId.qml") << QStringLiteral("inaccessibleId.qml")
<< QStringLiteral("Property \"objectName\" not found on type \"int\"") << QStringLiteral("Property \"objectName\" not found on type \"int\"") << QString()
<< QString() << false; << QString() << false;
QTest::newRow("inaccessibleId2") QTest::newRow("inaccessibleId2")
<< QStringLiteral("inaccessibleId2.qml") << QStringLiteral("inaccessibleId2.qml")
<< QStringLiteral("Property \"objectName\" not found on type \"int\"") << QStringLiteral("Property \"objectName\" not found on type \"int\"") << QString()
<< QString() << false; << QString() << false;
QTest::newRow("unknownTypeCustomParser") QTest::newRow("unknownTypeCustomParser")
<< QStringLiteral("unknownTypeCustomParser.qml") << QStringLiteral("unknownTypeCustomParser.qml")
<< QStringLiteral("TypeDoesNotExist was not found.") << QString() << false; << QStringLiteral("TypeDoesNotExist was not found.") << QString() << QString() << false;
} }
void TestQmllint::dirtyQmlCode() void TestQmllint::dirtyQmlCode()
@ -828,53 +770,45 @@ void TestQmllint::dirtyQmlCode()
QFETCH(QString, filename); QFETCH(QString, filename);
QFETCH(QString, warningMessage); QFETCH(QString, warningMessage);
QFETCH(QString, notContained); QFETCH(QString, notContained);
QFETCH(QString, replacement);
QFETCH(bool, exitsNormally); QFETCH(bool, exitsNormally);
if (warningMessage.contains(QLatin1String("%1"))) if (warningMessage.contains(QLatin1String("%1")))
warningMessage = warningMessage.arg(testFile(filename)); warningMessage = warningMessage.arg(testFile(filename));
const QString output = runQmllint(filename, [&](QProcess &process) {
QVERIFY(process.waitForFinished());
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
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);
if (exitsNormally) QJsonArray warnings;
QVERIFY(process.exitCode() == 0);
else
QVERIFY(process.exitCode() != 0);
});
const auto toDescription = [](const QString &output, const QString &substring) { QEXPECT_FAIL("attachedPropertyAccess", "We cannot discern between types and instances", Abort);
// Note: this actually produces a very poorly formatted multi-line QEXPECT_FAIL("attachedPropertyNested", "We cannot discern between types and instances", Abort);
// description, but this is how we also do it in cleanQmlCode test case,
// so this should suffice. in any case this mainly aids the debugging
// and CI stays (or should stay) clean.
return QStringLiteral("qmllint output '%1' must contain '%2'").arg(output, substring);
};
// output.contains() expect fails:
QEXPECT_FAIL("BadLiteralBindingDate", QEXPECT_FAIL("BadLiteralBindingDate",
"We're currently not able to verify any non-trivial QString conversion that " "We're currently not able to verify any non-trivial QString conversion that "
"requires QQmlStringConverters", "requires QQmlStringConverters",
Abort); Abort);
QEXPECT_FAIL("bad tranlsation binding (qsTr)", "We currently do not check translation binding",
QVERIFY2(output.contains(warningMessage), qPrintable(toDescription(output, warningMessage)));
// !output.contains() expect fails:
QEXPECT_FAIL("badAttachedPropertyNested", "We cannot discern between types and instances",
Abort); Abort);
if (!notContained.isEmpty()) callQmllint(filename, exitsNormally, &warnings);
QVERIFY2(!output.contains(notContained), qPrintable(toDescription(output, notContained)));
if (!warningMessage.isEmpty()) {
// output.contains() expect fails:
QEXPECT_FAIL("BadLiteralBindingDate",
"We're currently not able to verify any non-trivial QString conversion that "
"requires QQmlStringConverters",
Abort);
searchWarnings(warnings, warningMessage, filename);
}
if (!notContained.isEmpty()) {
// !output.contains() expect fails:
QEXPECT_FAIL("badAttachedPropertyNested", "We cannot discern between types and instances",
Abort);
searchWarnings(warnings, notContained, filename, StringNotContained);
}
if (!replacement.isEmpty())
searchWarnings(warnings, replacement, filename, StringContained, DoReplacementSearch);
} }
void TestQmllint::cleanQmlCode_data() void TestQmllint::cleanQmlCode_data()
@ -1078,14 +1012,20 @@ QString TestQmllint::runQmllint(const QString &fileToLint, bool shouldSucceed,
extraArgs, ignoreSettings, addIncludeDirs); extraArgs, ignoreSettings, addIncludeDirs);
} }
void TestQmllint::callQmllint(const QString &fileToLint, bool shouldSucceed, QJsonArray *warnings) void TestQmllint::callQmllint(const QString &fileToLint, bool shouldSucceed, QJsonArray *warnings,
QStringList includeDirs, QStringList qmltypesFiles,
QStringList resources, DefaultIncludeOption defaultIncludes)
{ {
QJsonArray jsonOutput; QJsonArray jsonOutput;
bool success = m_linter.lintFile(QFileInfo(fileToLint).isAbsolute() ? fileToLint bool success = m_linter.lintFile(
: testFile(fileToLint), QFileInfo(fileToLint).isAbsolute() ? fileToLint : testFile(fileToLint), nullptr, true,
nullptr, false, warnings ? &jsonOutput : nullptr, warnings ? &jsonOutput : nullptr,
m_defaultImportPaths, QStringList(), QStringList(), {}); defaultIncludes == UseDefaultIncludes ? m_defaultImportPaths + includeDirs
: includeDirs,
qmltypesFiles, resources, {});
QCOMPARE(success, shouldSucceed);
if (warnings) { if (warnings) {
QVERIFY2(jsonOutput.size() == 1, QJsonDocument(jsonOutput).toJson()); QVERIFY2(jsonOutput.size() == 1, QJsonDocument(jsonOutput).toJson());
*warnings = jsonOutput.at(0)[u"warnings"_qs].toArray(); *warnings = jsonOutput.at(0)[u"warnings"_qs].toArray();
@ -1094,6 +1034,65 @@ void TestQmllint::callQmllint(const QString &fileToLint, bool shouldSucceed, QJs
QCOMPARE(success, shouldSucceed); QCOMPARE(success, shouldSucceed);
} }
void TestQmllint::searchWarnings(const QJsonArray &warnings, const QString &substring,
const QString &filename, ContainOption shouldContain,
ReplacementOption searchReplacements)
{
bool contains = false;
auto toDisplayWarningType = [](const QString &warningType) {
return warningType.first(1).toUpper() + warningType.mid(1);
};
for (const QJsonValue &warning : warnings) {
// We're currently recreating the output that the logger would write to the console,
// so we can keep using our existing test data without modification.
// In the future our test data should be replaced with a structured representation of the
// warning it expects, possibly as a QQmlJS::DiagnosticMessage.
const QString warningMessage =
u"%1: %2:%3 %4"_qs
.arg(toDisplayWarningType(warning[u"type"].toString()), testFile(filename))
.arg(warning[u"line"].isUndefined()
? u""_qs
: u"%1:%2:"_qs.arg(warning[u"line"].toInt())
.arg(warning[u"column"].toInt()))
.arg(warning[u"message"].toString());
if (warningMessage.contains(substring)) {
contains = true;
break;
}
for (const QJsonValue &fix : warning[u"suggestions"].toArray()) {
const QString jsonFixMessage = u"Info: "_qs + fix[u"message"].toString();
if (jsonFixMessage.contains(substring)) {
contains = true;
break;
}
if (searchReplacements == DoReplacementSearch
&& fix[u"replacement"].toString().contains(substring)) {
contains = true;
break;
}
}
}
const auto toDescription = [](const QJsonArray &warnings, const QString &substring,
bool must = true) {
// Note: this actually produces a very poorly formatted multi-line
// description, but this is how we also do it in cleanQmlCode test case,
// so this should suffice. in any case this mainly aids the debugging
// and CI stays (or should stay) clean.
return QStringLiteral("qmllint output '%1' %2 contain '%3'")
.arg(QString::fromUtf8(QJsonDocument(warnings).toJson()),
must ? u"must" : u"must NOT", substring);
};
if (shouldContain == StringContained)
QVERIFY2(contains, qPrintable(toDescription(warnings, substring)));
else
QVERIFY2(!contains, qPrintable(toDescription(warnings, substring, false)));
}
void TestQmllint::requiredProperty() void TestQmllint::requiredProperty()
{ {
QVERIFY(runQmllint("requiredProperty.qml", true).isEmpty()); QVERIFY(runQmllint("requiredProperty.qml", true).isEmpty());

View File

@ -51,7 +51,7 @@
#include <cstdio> #include <cstdio>
constexpr int JSON_LOGGING_FORMAT_REVISION = 2; constexpr int JSON_LOGGING_FORMAT_REVISION = 3;
int main(int argv, char *argc[]) int main(int argv, char *argc[])
{ {