qmllint: add an "error" logging category

Add support for a critical error category in qmllint called "error",
so that the qds plugin can show errors for unsupported components for
example.

Add a test. The error category makes qmllint return -1.

Simplify a bit the logic of the "success" bool in lintFile() by making
it a LintResult and returning it.

Change-Id: Ide6a6a265f1aeede0fcbc35d9e4d0abe3f5c2aa3
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Sami Shalayel 2024-10-22 14:54:08 +02:00
parent 37f69c4341
commit 2529b4515b
7 changed files with 63 additions and 20 deletions

View File

@ -196,7 +196,7 @@ bool QQmlJSLinter::Plugin::parseMetaData(const QJsonObject &metaData, QString pl
if (!QQmlJS::LoggingUtils::applyLevelToCategory(level, m_categories.last())) { if (!QQmlJS::LoggingUtils::applyLevelToCategory(level, m_categories.last())) {
qWarning() << "Invalid logging level" << level << "provided for" qWarning() << "Invalid logging level" << level << "provided for"
<< m_categories.last().id().name().toString() << m_categories.last().id().name().toString()
<< "(allowed are: disable, info, warning) found in plugin metadata"; << "(allowed are: disable, info, warning, error) found in plugin metadata.";
} }
} }
@ -444,7 +444,7 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
QJsonArray warnings; QJsonArray warnings;
QJsonObject result; QJsonObject result;
bool success = true; LintResult success = LintSuccess;
QScopeGuard jsonOutput([&] { QScopeGuard jsonOutput([&] {
if (!json) if (!json)
@ -452,7 +452,7 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
result[u"filename"_s] = QFileInfo(filename).absoluteFilePath(); result[u"filename"_s] = QFileInfo(filename).absoluteFilePath();
result[u"warnings"] = warnings; result[u"warnings"] = warnings;
result[u"success"] = success; result[u"success"] = success == LintSuccess;
json->append(result); json->append(result);
}); });
@ -469,11 +469,11 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
.arg(filename, file.errorString()), .arg(filename, file.errorString()),
QtCriticalMsg, QQmlJS::SourceLocation() }, QtCriticalMsg, QQmlJS::SourceLocation() },
qmlImport.name()); qmlImport.name());
success = false;
} else if (!silent) { } else if (!silent) {
qWarning() << "Failed to open file" << filename << file.error(); qWarning() << "Failed to open file" << filename << file.error();
} }
return FailedToOpen; success = FailedToOpen;
return success;
} }
code = QString::fromUtf8(file.readAll()); code = QString::fromUtf8(file.readAll());
@ -495,10 +495,9 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/!isJavaScript); lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/!isJavaScript);
QQmlJS::Parser parser(&engine); QQmlJS::Parser parser(&engine);
success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram()) if (!(isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram())
: parser.parse(); : parser.parse())) {
success = FailedToParse;
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) {
@ -511,10 +510,10 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
.arg(m.message); .arg(m.message);
} }
} }
return FailedToParse; return success;
} }
if (success && !isJavaScript) { if (!isJavaScript) {
const auto check = [&](QQmlJSResourceFileMapper *mapper) { const auto check = [&](QQmlJSResourceFileMapper *mapper) {
if (m_importer.importPaths() != qmlImportPaths) if (m_importer.importPaths() != qmlImportPaths)
m_importer.setImportPaths(qmlImportPaths); m_importer.setImportPaths(qmlImportPaths);
@ -594,13 +593,13 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
} }
passMan->analyze(QQmlJSScope::createQQmlSAElement(v.result())); passMan->analyze(QQmlJSScope::createQQmlSAElement(v.result()));
success = !m_logger->hasWarnings() && !m_logger->hasErrors();
if (m_logger->hasErrors()) { if (m_logger->hasErrors()) {
success = HasErrors;
if (json) if (json)
processMessages(warnings); processMessages(warnings);
return; return;
} } else if (m_logger->hasWarnings())
success = HasWarnings;
if (passMan) { if (passMan) {
// passMan now has a pointer to the moved from type resolver // passMan now has a pointer to the moved from type resolver
@ -627,7 +626,10 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
m_logger->processMessages(globalWarnings, qmlImport); m_logger->processMessages(globalWarnings, qmlImport);
} }
success &= !m_logger->hasWarnings() && !m_logger->hasErrors(); if (m_logger->hasErrors())
success = HasErrors;
else if (m_logger->hasWarnings())
success = HasWarnings;
if (json) if (json)
processMessages(warnings); processMessages(warnings);
@ -641,7 +643,7 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
} }
} }
return success ? LintSuccess : HasWarnings; return success;
} }
QQmlJSLinter::LintResult QQmlJSLinter::lintModule( QQmlJSLinter::LintResult QQmlJSLinter::lintModule(

View File

@ -44,7 +44,7 @@ public:
QQmlJSLinter(const QStringList &importPaths, const QStringList &extraPluginPaths = {}, QQmlJSLinter(const QStringList &importPaths, const QStringList &extraPluginPaths = {},
bool useAbsolutePath = false); bool useAbsolutePath = false);
enum LintResult { FailedToOpen, FailedToParse, HasWarnings, LintSuccess }; enum LintResult { FailedToOpen, FailedToParse, HasWarnings, HasErrors, LintSuccess };
enum FixResult { NothingToFix, FixError, FixSuccess }; enum FixResult { NothingToFix, FixError, FixSuccess };
class Q_QMLCOMPILER_EXPORT Plugin class Q_QMLCOMPILER_EXPORT Plugin

View File

@ -139,7 +139,6 @@ namespace LoggingUtils {
QString levelToString(const QQmlJS::LoggerCategory &category) QString levelToString(const QQmlJS::LoggerCategory &category)
{ {
Q_ASSERT(category.isIgnored() || category.level() != QtCriticalMsg);
if (category.isIgnored()) if (category.isIgnored())
return QStringLiteral("disable"); return QStringLiteral("disable");
@ -148,6 +147,8 @@ QString levelToString(const QQmlJS::LoggerCategory &category)
return QStringLiteral("info"); return QStringLiteral("info");
case QtWarningMsg: case QtWarningMsg:
return QStringLiteral("warning"); return QStringLiteral("warning");
case QtCriticalMsg:
return QStringLiteral("error");
default: default:
Q_UNREACHABLE(); Q_UNREACHABLE();
break; break;
@ -188,6 +189,10 @@ static QString levelValueForCategory(const LoggerCategory &category,
bool applyLevelToCategory(const QStringView level, LoggerCategory &category) bool applyLevelToCategory(const QStringView level, LoggerCategory &category)
{ {
// you can't downgrade errors
if (category.level() == QtCriticalMsg && !category.isIgnored() && level != "error"_L1)
return false;
if (level == "disable"_L1) { if (level == "disable"_L1) {
category.setLevel(QtCriticalMsg); category.setLevel(QtCriticalMsg);
category.setIgnored(true); category.setIgnored(true);
@ -203,6 +208,12 @@ bool applyLevelToCategory(const QStringView level, LoggerCategory &category)
category.setIgnored(false); category.setIgnored(false);
return true; return true;
} }
if (level == "error"_L1) {
category.setLevel(QtCriticalMsg);
category.setIgnored(false);
return true;
}
return false; return false;
}; };
@ -227,9 +238,9 @@ void updateLogLevels(QList<LoggerCategory> &categories,
if (!applyLevelToCategory(value, category)) { if (!applyLevelToCategory(value, category)) {
qWarning() << "Invalid logging level" << value << "provided for" qWarning() << "Invalid logging level" << value << "provided for"
<< category.id().name().toString() << category.id().name().toString()
<< "(allowed are: disable, info, warning)"; << "(allowed are: disable, info, warning, error)\n."
"You can't change categories that have level \"error\" by default.";
success = false; success = false;
} }
} }
if (!success && parser) if (!success && parser)

View File

@ -0,0 +1,5 @@
import QtQuick
Item {
property int myI: asdf
}

View File

@ -27,6 +27,12 @@
"settingsName": "TestDefaultValue3", "settingsName": "TestDefaultValue3",
"description": "A warning to test defaults", "description": "A warning to test defaults",
"defaultLevel": "warning" "defaultLevel": "warning"
},
{
"name": "TestDefaultValue4",
"settingsName": "TestDefaultValue3",
"description": "A warning to test defaults",
"defaultLevel": "error"
} }
] ]
} }

View File

@ -138,6 +138,7 @@ private Q_SLOTS:
#endif #endif
void replayImportWarnings(); void replayImportWarnings();
void errorCategory();
private: private:
enum DefaultImportOption { NoDefaultImports, UseDefaultImports }; enum DefaultImportOption { NoDefaultImports, UseDefaultImports };
@ -2261,6 +2262,9 @@ void TestQmllint::hasTestPlugin()
} else if (category.name() == u"testPlugin.TestDefaultValue3"){ } else if (category.name() == u"testPlugin.TestDefaultValue3"){
QCOMPARE(category.level(), QtWarningMsg); QCOMPARE(category.level(), QtWarningMsg);
QCOMPARE(category.isIgnored(), false); QCOMPARE(category.isIgnored(), false);
} else if (category.name() == u"testPlugin.TestDefaultValue4"){
QCOMPARE(category.level(), QtCriticalMsg);
QCOMPARE(category.isIgnored(), false);
} else { } else {
QFAIL("This category was not tested!"); QFAIL("This category was not tested!");
} }
@ -2651,5 +2655,19 @@ void TestQmllint::replayImportWarnings()
searchWarnings(warnings, "Ambiguous type detected. T 1.0 is defined multiple times."); searchWarnings(warnings, "Ambiguous type detected. T 1.0 is defined multiple times.");
} }
void TestQmllint::errorCategory()
{
{
const QString output = runQmllint(testFile(u"HasUnqualified.qml"_s), false,
QStringList{ u"--unqualified"_s, u"error"_s });
QVERIFY(output.startsWith("Error: "));
}
{
const QString output = runQmllint(testFile(u"HasUnqualified.qml"_s), true);
QVERIFY(output.startsWith("Warning: "));
}
}
QTEST_GUILESS_MAIN(TestQmllint) QTEST_GUILESS_MAIN(TestQmllint)
#include "tst_qmllint.moc" #include "tst_qmllint.moc"

View File

@ -77,6 +77,7 @@ All warnings can be set to three levels:
disable - Fully disables the warning. disable - Fully disables the warning.
info - Displays the warning but does not influence the return code. info - Displays the warning but does not influence the return code.
warning - Displays the warning and leads to a non-zero exit code if encountered. warning - Displays the warning and leads to a non-zero exit code if encountered.
error - Displays the warning as error and leads to a non-zero exit code if encountered.
)")); )"));
parser.addHelpOption(); parser.addHelpOption();