diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 05c7298b04..5470a08259 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -72,6 +72,7 @@ add_subdirectory(plugins) if(QT_FEATURE_qml_devtools) add_subdirectory(qmlcompiler) + add_subdirectory(qmllint) add_subdirectory(qmldom) # Build qmlcachegen now, so that we can use it in src/imports. diff --git a/src/qmllint/CMakeLists.txt b/src/qmllint/CMakeLists.txt new file mode 100644 index 0000000000..d27e01333d --- /dev/null +++ b/src/qmllint/CMakeLists.txt @@ -0,0 +1,13 @@ +qt_internal_add_module(QmlLintPrivate + STATIC + INTERNAL_MODULE + SOURCES + codegen_p.h codegen.cpp + codegenwarninginterface_p.h codegenwarninginterface.cpp + findwarnings_p.h findwarnings.cpp + qqmllinter_p.h qqmllinter.cpp + PUBLIC_LIBRARIES + Qt::CorePrivate + Qt::QmlPrivate + Qt::QmlCompilerPrivate +) diff --git a/tools/qmllint/codegen.cpp b/src/qmllint/codegen.cpp similarity index 89% rename from tools/qmllint/codegen.cpp rename to src/qmllint/codegen.cpp index 7404ce220d..9bc8cbbe0d 100644 --- a/tools/qmllint/codegen.cpp +++ b/src/qmllint/codegen.cpp @@ -26,7 +26,7 @@ ** ****************************************************************************/ -#include "codegen.h" +#include "codegen_p.h" #include #include @@ -34,6 +34,8 @@ #include +QT_BEGIN_NAMESPACE + Codegen::Codegen(QQmlJSImporter *importer, const QString &fileName, const QStringList &qmltypesFiles, QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo, const QString &code) @@ -119,9 +121,9 @@ Codegen::compileBinding(const QV4::Compiler::Context *context, const QmlIR::Bind return "nothing"; }; - return diagnose( - QStringLiteral("Binding is not a script binding, but %1.").arg(bindingName()), - QtDebugMsg, bindingLocation); + return diagnose(QStringLiteral("Binding is not a script binding, but %1.") + .arg(QString::fromUtf8(bindingName())), + QtDebugMsg, bindingLocation); } Function function; @@ -193,7 +195,7 @@ Codegen::compileBinding(const QV4::Compiler::Context *context, const QmlIR::Bind auto body = new (m_pool) QQmlJS::AST::StatementList(stmt); body = body->finish(); - QString name = "binding for "; // #### + QString name = u"binding for "_qs; // #### ast = new (m_pool) QQmlJS::AST::FunctionDeclaration(m_pool->newString(name), /*formals*/ nullptr, body); ast->lbraceToken = astNode->firstSourceLocation(); @@ -205,10 +207,10 @@ Codegen::compileBinding(const QV4::Compiler::Context *context, const QmlIR::Bind if (!generateFunction(QV4::Compiler::ContextType::Binding, context, ast, &function, &error)) { // If it's a signal and the function just returns a closure, it's harmless. // Otherwise promote the message to warning level. - return diagnose( - QStringLiteral("Could not compile binding for %1: %2") - .arg(propertyName, error.message), - (isSignal && error.type == QtDebugMsg) ? QtDebugMsg : QtWarningMsg, error.loc); + return diagnose(QStringLiteral("Could not compile binding for %1: %2") + .arg(propertyName, error.message), + (isSignal && error.type == QtDebugMsg) ? QtDebugMsg : QtWarningMsg, + error.loc); } return QQmlJSAotFunction {}; @@ -265,12 +267,10 @@ QQmlJS::DiagnosticMessage Codegen::diagnose(const QString &message, QtMsgType ty return QQmlJS::DiagnosticMessage { message, type, location }; } -bool Codegen::generateFunction( - QV4::Compiler::ContextType contextType, - const QV4::Compiler::Context *context, - QQmlJS::AST::FunctionExpression *ast, - Function *function, - QQmlJS::DiagnosticMessage *error) const +bool Codegen::generateFunction(QV4::Compiler::ContextType contextType, + const QV4::Compiler::Context *context, + QQmlJS::AST::FunctionExpression *ast, Function *function, + QQmlJS::DiagnosticMessage *error) const { const auto fail = [&](const QString &message) { error->loc = ast->firstSourceLocation(); @@ -286,17 +286,16 @@ bool Codegen::generateFunction( for (const QQmlJS::AST::BoundName &argument : qAsConst(arguments)) { if (argument.typeAnnotation) { const auto rawType = m_typeResolver->typeFromAST(argument.typeAnnotation->type); - if (m_typeResolver->storedType( - rawType, QQmlJSTypeResolver::ComponentIsGeneric::Yes)) { + if (m_typeResolver->storedType(rawType, + QQmlJSTypeResolver::ComponentIsGeneric::Yes)) { function->argumentTypes.append(rawType); continue; } else { return fail(QStringLiteral("Cannot store the argument type %1.") - .arg(rawType ? rawType->internalName() : "")); + .arg(rawType ? rawType->internalName() : u""_qs)); } } else { - return fail( - QStringLiteral("Functions without type annotations won't be compiled")); + return fail(QStringLiteral("Functions without type annotations won't be compiled")); return false; } } @@ -313,15 +312,15 @@ bool Codegen::generateFunction( } if (function->returnType) { - if (!m_typeResolver->storedType( - function->returnType, QQmlJSTypeResolver::ComponentIsGeneric::Yes)) { + if (!m_typeResolver->storedType(function->returnType, + QQmlJSTypeResolver::ComponentIsGeneric::Yes)) { return fail(QStringLiteral("Cannot store the return type %1.") .arg(function->returnType->internalName())); } } - function->isSignalHandler = !function->returnType - && contextType == QV4::Compiler::ContextType::Binding; + function->isSignalHandler = + !function->returnType && contextType == QV4::Compiler::ContextType::Binding; function->addressableScopes = m_typeResolver->objectsById(); function->code = context->code; function->sourceLocations = context->sourceLocationTable.get(); @@ -338,3 +337,5 @@ bool Codegen::generateFunction( return true; } + +QT_END_NAMESPACE diff --git a/tools/qmllint/codegen.h b/src/qmllint/codegen_p.h similarity index 95% rename from tools/qmllint/codegen.h rename to src/qmllint/codegen_p.h index 1a9dff3ed8..481f8e2341 100644 --- a/tools/qmllint/codegen.h +++ b/src/qmllint/codegen_p.h @@ -26,8 +26,8 @@ ** ****************************************************************************/ -#ifndef CODEGEN_H -#define CODEGEN_H +#ifndef CODEGEN_P_H +#define CODEGEN_P_H // // W A R N I N G @@ -54,6 +54,8 @@ #include #include +QT_BEGIN_NAMESPACE + class Codegen : public QQmlJSAotCompiler { public: @@ -94,9 +96,10 @@ private: const QQmlJS::SourceLocation &location); bool generateFunction(QV4::Compiler::ContextType contextType, const QV4::Compiler::Context *context, - QQmlJS::AST::FunctionExpression *ast, - Function *function, + QQmlJS::AST::FunctionExpression *ast, Function *function, QQmlJS::DiagnosticMessage *error) const; }; +QT_END_NAMESPACE + #endif diff --git a/tools/qmllint/codegenwarninginterface.cpp b/src/qmllint/codegenwarninginterface.cpp similarity index 96% rename from tools/qmllint/codegenwarninginterface.cpp rename to src/qmllint/codegenwarninginterface.cpp index 11e39abbb0..c06571d1bf 100644 --- a/tools/qmllint/codegenwarninginterface.cpp +++ b/src/qmllint/codegenwarninginterface.cpp @@ -26,10 +26,12 @@ ** ****************************************************************************/ -#include "codegenwarninginterface.h" +#include "codegenwarninginterface_p.h" #include +QT_BEGIN_NAMESPACE + void CodegenWarningInterface::reportVarUsedBeforeDeclaration( const QString &name, const QString &fileName, QQmlJS::SourceLocation declarationLocation, QQmlJS::SourceLocation accessLocation) @@ -42,3 +44,5 @@ void CodegenWarningInterface::reportVarUsedBeforeDeclaration( .arg(declarationLocation.startColumn), Log_Type, accessLocation); } + +QT_END_NAMESPACE diff --git a/tools/qmllint/codegenwarninginterface.h b/src/qmllint/codegenwarninginterface_p.h similarity index 81% rename from tools/qmllint/codegenwarninginterface.h rename to src/qmllint/codegenwarninginterface_p.h index e0e7078629..fb9c5ca862 100644 --- a/tools/qmllint/codegenwarninginterface.h +++ b/src/qmllint/codegenwarninginterface_p.h @@ -26,13 +26,24 @@ ** ****************************************************************************/ -#ifndef CODEGENWARNINGINTERFACE_H -#define CODEGENWARNINGINTERFACE_H +#ifndef CODEGENWARNINGINTERFACE_P_H +#define CODEGENWARNINGINTERFACE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. #include -QT_FORWARD_DECLARE_CLASS(QQmlJSLogger) +QT_BEGIN_NAMESPACE +class QQmlJSLogger; class CodegenWarningInterface final : public QV4::Compiler::CodegenWarningInterface { public: @@ -46,4 +57,6 @@ private: QQmlJSLogger *m_logger; }; -#endif // CODEGENWARNINGINTERFACE_H +QT_END_NAMESPACE + +#endif // CODEGENWARNINGINTERFACE_P_H diff --git a/tools/qmllint/findwarnings.cpp b/src/qmllint/findwarnings.cpp similarity index 99% rename from tools/qmllint/findwarnings.cpp rename to src/qmllint/findwarnings.cpp index 4943593236..d06dc59c5c 100644 --- a/tools/qmllint/findwarnings.cpp +++ b/src/qmllint/findwarnings.cpp @@ -26,7 +26,7 @@ ** ****************************************************************************/ -#include "findwarnings.h" +#include "findwarnings_p.h" #include #include @@ -41,6 +41,8 @@ #include #include +QT_BEGIN_NAMESPACE + bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) { QQmlJSImportVisitor::visit(uiod); @@ -239,3 +241,5 @@ bool FindWarningVisitor::check() return !m_logger->hasWarnings() && !m_logger->hasErrors(); } + +QT_END_NAMESPACE diff --git a/tools/qmllint/findwarnings.h b/src/qmllint/findwarnings_p.h similarity index 95% rename from tools/qmllint/findwarnings.h rename to src/qmllint/findwarnings_p.h index 8d9475f467..599eaf2dc8 100644 --- a/tools/qmllint/findwarnings.h +++ b/src/qmllint/findwarnings_p.h @@ -26,8 +26,8 @@ ** ****************************************************************************/ -#ifndef FINDUNQUALIFIED_H -#define FINDUNQUALIFIED_H +#ifndef FINDUNQUALIFIED_P_H +#define FINDUNQUALIFIED_P_H // // W A R N I N G @@ -51,6 +51,8 @@ #include +QT_BEGIN_NAMESPACE + class FindWarningVisitor : public QQmlJSImportVisitor { Q_DISABLE_COPY_MOVE(FindWarningVisitor) @@ -64,11 +66,13 @@ private: void parseComments(const QList &comments); // work around compiler error in clang11 - using QQmlJSImportVisitor::visit; using QQmlJSImportVisitor::endVisit; + using QQmlJSImportVisitor::visit; bool visit(QQmlJS::AST::UiObjectDefinition *uiod) override; void endVisit(QQmlJS::AST::UiObjectDefinition *uiod) override; }; -#endif // FINDUNQUALIFIED_H +QT_END_NAMESPACE + +#endif // FINDUNQUALIFIED_P_H diff --git a/src/qmllint/qqmllinter.cpp b/src/qmllint/qqmllinter.cpp new file mode 100644 index 0000000000..db225edde4 --- /dev/null +++ b/src/qmllint/qqmllinter.cpp @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmllinter_p.h" + +#include "codegen_p.h" +#include "codegenwarninginterface_p.h" +#include "findwarnings_p.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QQmlLinter::QQmlLinter(const QStringList &importPaths, bool useAbsolutePath) + : m_useAbsolutePath(useAbsolutePath), m_importer(importPaths, nullptr) +{ +} + +bool QQmlLinter::lintFile(const QString &filename, const bool silent, QJsonArray *json, + const QStringList &qmlImportPaths, const QStringList &qmltypesFiles, + const QStringList &resourceFiles, + const QMap &options) +{ + QJsonArray warnings; + QJsonObject result; + + bool success = true; + + QScopeGuard jsonOutput([&] { + if (!json) + return; + + result[u"filename"_qs] = QFileInfo(filename).absoluteFilePath(); + result[u"warnings"] = warnings; + result[u"success"] = success; + + json->append(result); + }); + + auto addJsonWarning = [&](const QQmlJS::DiagnosticMessage &message) { + QJsonObject jsonMessage; + + QString type; + switch (message.type) { + case QtDebugMsg: + type = u"debug"_qs; + break; + case QtWarningMsg: + type = u"warning"_qs; + break; + case QtCriticalMsg: + type = u"critical"_qs; + break; + case QtFatalMsg: + type = u"fatal"_qs; + break; + case QtInfoMsg: + type = u"info"_qs; + break; + default: + type = u"unknown"_qs; + break; + } + + jsonMessage[u"type"_qs] = type; + + if (message.loc.isValid()) { + jsonMessage[u"line"_qs] = static_cast(message.loc.startLine); + jsonMessage[u"column"_qs] = static_cast(message.loc.startColumn); + jsonMessage[u"charOffset"_qs] = static_cast(message.loc.offset); + jsonMessage[u"length"_qs] = static_cast(message.loc.length); + } + + jsonMessage[u"message"_qs] = message.message; + + warnings << jsonMessage; + }; + + QFile file(filename); + if (!file.open(QFile::ReadOnly)) { + if (json) { + result[u"openFailed"] = true; + success = false; + } else if (!silent) { + qWarning() << "Failed to open file" << filename << file.error(); + } + return false; + } + + QString code = QString::fromUtf8(file.readAll()); + file.close(); + + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + + QFileInfo info(filename); + const QString lowerSuffix = info.suffix().toLower(); + const bool isESModule = lowerSuffix == QLatin1String("mjs"); + const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js"); + + lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/!isJavaScript); + QQmlJS::Parser parser(&engine); + + success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram()) + : parser.parse(); + + if (!success && !silent) { + const auto diagnosticMessages = parser.diagnosticMessages(); + for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { + if (json) { + addJsonWarning(m); + } else { + qWarning().noquote() << QString::fromLatin1("%1:%2 : %3") + .arg(filename) + .arg(m.loc.startLine) + .arg(m.message); + } + } + } + + if (success && !isJavaScript) { + const auto check = [&](QQmlJSResourceFileMapper *mapper) { + if (m_importer.importPaths() != qmlImportPaths) + m_importer.setImportPaths(qmlImportPaths); + + m_importer.setResourceFileMapper(mapper); + + QQmlJSLogger logger(m_useAbsolutePath ? info.absoluteFilePath() : filename, code, + silent || json); + FindWarningVisitor v { + &m_importer, + &logger, + qmltypesFiles, + engine.comments(), + }; + + for (auto it = options.cbegin(); it != options.cend(); ++it) { + logger.setCategoryError(it.value().m_category, it.value().m_error); + logger.setCategoryLevel(it.value().m_category, it.value().m_level); + } + + parser.rootNode()->accept(&v); + success = v.check(); + + if (logger.hasErrors()) + return; + + QQmlJSTypeInfo typeInfo; + Codegen codegen { &m_importer, filename, qmltypesFiles, &logger, &typeInfo, code }; + QQmlJSSaveFunction saveFunction = [](const QV4::CompiledData::SaveableUnitPointer &, + const QQmlJSAotFunctionMap &, + QString *) { return true; }; + + QQmlJSCompileError error; + + QLoggingCategory::setFilterRules(u"qt.qml.compiler=false"_qs); + + CodegenWarningInterface interface(&logger); + qCompileQmlFile(filename, saveFunction, &codegen, &error, true, &interface); + + success &= !logger.hasWarnings() && !logger.hasErrors(); + + if (json) { + for (const auto &error : logger.errors()) + addJsonWarning(error); + for (const auto &warning : logger.warnings()) + addJsonWarning(warning); + for (const auto &info : logger.infos()) + addJsonWarning(info); + } + }; + + if (resourceFiles.isEmpty()) { + check(nullptr); + } else { + QQmlJSResourceFileMapper mapper(resourceFiles); + check(&mapper); + } + } + + return success; +} + +QT_END_NAMESPACE diff --git a/src/qmllint/qqmllinter_p.h b/src/qmllint/qqmllinter_p.h new file mode 100644 index 0000000000..3d420d9d59 --- /dev/null +++ b/src/qmllint/qqmllinter_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLLINT_P_H +#define QMLLINT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlLinter +{ +public: + QQmlLinter(const QStringList &importPaths, bool useAbsolutePath = false); + + bool lintFile(const QString &filename, const bool silent, QJsonArray *json, + const QStringList &qmlImportPaths, const QStringList &qmltypesFiles, + const QStringList &resourceFiles, + const QMap &options); + +private: + bool m_useAbsolutePath; + QQmlJSImporter m_importer; +}; + +QT_END_NAMESPACE + +#endif // QMLLINT_P_H diff --git a/sync.profile b/sync.profile index 362332c63d..06ff8e2f00 100644 --- a/sync.profile +++ b/sync.profile @@ -12,6 +12,7 @@ "QtQmlModels" => "$basedir/src/qmlmodels", "QtQmlWorkerScript" => "$basedir/src/qmlworkerscript", "QtQmlCompiler" => "$basedir/src/qmlcompiler", + "QtQmlLint" => "$basedir/src/qmllint", "QtQmlDom" => "$basedir/src/qmldom", "QtQuickLayouts" => "$basedir/src/quicklayouts", "QtQmlLocalStorage" => "$basedir/src/qmllocalstorage", diff --git a/tests/auto/qml/qmllint/CMakeLists.txt b/tests/auto/qml/qmllint/CMakeLists.txt index 15549a93b7..d7f1c6e833 100644 --- a/tests/auto/qml/qmllint/CMakeLists.txt +++ b/tests/auto/qml/qmllint/CMakeLists.txt @@ -16,6 +16,7 @@ qt_internal_add_test(tst_qmllint PUBLIC_LIBRARIES Qt::Gui Qt::QuickTestUtilsPrivate + Qt::QmlLintPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 0cd74d8fd4..148f0a01cb 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -31,6 +31,7 @@ #include #include #include +#include class TestQmllint: public QQmlDataTest { @@ -95,14 +96,21 @@ private: QString runQmllint(const QString &fileToLint, bool shouldSucceed, const QStringList &extraArgs = QStringList(), bool ignoreSettings = true, bool addIncludeDirs = true); + void callQmllint(const QString &fileToLint, bool shouldSucceed, QJsonArray *warnings = nullptr); QString m_qmllintPath; QString m_qmljsrootgenPath; QString m_qmltyperegistrarPath; + + QStringList m_defaultImportPaths; + QQmlLinter m_linter; }; TestQmllint::TestQmllint() - : QQmlDataTest(QT_QMLTEST_DATADIR) + : QQmlDataTest(QT_QMLTEST_DATADIR), + m_defaultImportPaths({ QLibraryInfo::path(QLibraryInfo::QmlImportsPath), dataDirectory() }), + m_linter(m_defaultImportPaths) + { } @@ -754,7 +762,6 @@ void TestQmllint::dirtyQmlCode() if (warningMessage.contains(QLatin1String("%1"))) warningMessage = warningMessage.arg(testFile(filename)); - const QString output = runQmllint(filename, [&](QProcess &process) { QVERIFY(process.waitForFinished()); QCOMPARE(process.exitStatus(), QProcess::NormalExit); @@ -908,8 +915,11 @@ void TestQmllint::cleanQmlCode_data() void TestQmllint::cleanQmlCode() { QFETCH(QString, filename); - const QString warnings = runQmllint(filename, true); - QVERIFY2(warnings.isEmpty(), qPrintable(warnings)); + + QJsonArray warnings; + + callQmllint(filename, true, &warnings); + QVERIFY2(warnings.isEmpty(), qPrintable(QJsonDocument(warnings).toJson())); } QString TestQmllint::runQmllint(const QString &fileToLint, @@ -974,6 +984,22 @@ QString TestQmllint::runQmllint(const QString &fileToLint, bool shouldSucceed, extraArgs, ignoreSettings, addIncludeDirs); } +void TestQmllint::callQmllint(const QString &fileToLint, bool shouldSucceed, QJsonArray *warnings) +{ + QJsonArray jsonOutput; + + bool success = m_linter.lintFile(QFileInfo(fileToLint).isAbsolute() ? fileToLint + : testFile(fileToLint), + true, warnings ? &jsonOutput : nullptr, m_defaultImportPaths, + QStringList(), QStringList(), {}); + QCOMPARE(success, shouldSucceed); + + if (warnings) { + Q_ASSERT(jsonOutput.size() == 1); + *warnings = jsonOutput.at(0)[u"warnings"_qs].toArray(); + } +} + void TestQmllint::requiredProperty() { QVERIFY(runQmllint("requiredProperty.qml", true).isEmpty()); diff --git a/tools/qmllint/CMakeLists.txt b/tools/qmllint/CMakeLists.txt index 4107e10f74..f9127acaa3 100644 --- a/tools/qmllint/CMakeLists.txt +++ b/tools/qmllint/CMakeLists.txt @@ -9,15 +9,13 @@ qt_internal_add_tool(${target_name} TARGET_DESCRIPTION "QML Syntax Verifier" TOOLS_TARGET Qml # special case SOURCES - findwarnings.cpp findwarnings.h - codegen.cpp codegen.h - codegenwarninginterface.cpp codegenwarninginterface.h main.cpp ../shared/qqmltoolingsettings.h ../shared/qqmltoolingsettings.cpp PUBLIC_LIBRARIES Qt::CorePrivate Qt::QmlCompilerPrivate + Qt::QmlLintPrivate ) qt_internal_return_unless_building_tools() diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp index 3df510ed79..b7b64f0b2b 100644 --- a/tools/qmllint/main.cpp +++ b/tools/qmllint/main.cpp @@ -26,20 +26,13 @@ ** ****************************************************************************/ -#include "findwarnings.h" -#include "codegen.h" -#include "codegenwarninginterface.h" #include "../shared/qqmltoolingsettings.h" +#include + #include #include -#include -#include -#include -#include -#include - #include #include #include @@ -48,7 +41,6 @@ #include #include #include -#include #include #if QT_CONFIG(commandlineparser) @@ -61,171 +53,6 @@ constexpr int JSON_LOGGING_FORMAT_REVISION = 1; -static bool lint_file(const QString &filename, const bool silent, const bool useAbsolutePath, - QJsonArray *json, const QStringList &qmlImportPaths, - const QStringList &qmltypesFiles, const QStringList &resourceFiles, - const QMap &options, QQmlJSImporter &importer) -{ - QJsonArray warnings; - QJsonObject result; - - bool success = true; - - QScopeGuard jsonOutput([&] { - if (!json) - return; - - result[u"filename"_qs] = QFileInfo(filename).absoluteFilePath(); - result[u"warnings"] = warnings; - result[u"success"] = success; - - json->append(result); - }); - - auto addJsonWarning = [&](const QQmlJS::DiagnosticMessage &message) { - QJsonObject jsonMessage; - - QString type; - switch (message.type) { - case QtDebugMsg: - type = "debug"; - break; - case QtWarningMsg: - type = "warning"; - break; - case QtCriticalMsg: - type = "critical"; - break; - case QtFatalMsg: - type = "fatal"; - break; - case QtInfoMsg: - type = "info"; - break; - default: - type = "unknown"; - break; - } - - jsonMessage[u"type"_qs] = type; - - if (message.loc.isValid()) { - jsonMessage[u"line"_qs] = static_cast(message.loc.startLine); - jsonMessage[u"column"_qs] = static_cast(message.loc.startColumn); - jsonMessage[u"charOffset"_qs] = static_cast(message.loc.offset); - jsonMessage[u"length"_qs] = static_cast(message.loc.length); - } - - jsonMessage[u"message"_qs] = message.message; - - warnings << jsonMessage; - }; - - QFile file(filename); - if (!file.open(QFile::ReadOnly)) { - if (json) { - result[u"openFailed"] = true; - success = false; - } else if (!silent) { - qWarning() << "Failed to open file" << filename << file.error(); - } - return false; - } - - QString code = QString::fromUtf8(file.readAll()); - file.close(); - - QQmlJS::Engine engine; - QQmlJS::Lexer lexer(&engine); - - QFileInfo info(filename); - const QString lowerSuffix = info.suffix().toLower(); - const bool isESModule = lowerSuffix == QLatin1String("mjs"); - const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js"); - - lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/ !isJavaScript); - QQmlJS::Parser parser(&engine); - - success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram()) - : parser.parse(); - - if (!success && !silent) { - const auto diagnosticMessages = parser.diagnosticMessages(); - for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { - if (json) { - addJsonWarning(m); - } else { - qWarning().noquote() << QString::fromLatin1("%1:%2 : %3") - .arg(filename) - .arg(m.loc.startLine) - .arg(m.message); - } - } - } - - if (success && !isJavaScript) { - const auto check = [&](QQmlJSResourceFileMapper *mapper) { - if (importer.importPaths() != qmlImportPaths) - importer.setImportPaths(qmlImportPaths); - - importer.setResourceFileMapper(mapper); - - QQmlJSLogger logger(useAbsolutePath ? info.absoluteFilePath() : filename, code, - silent || json); - FindWarningVisitor v { - &importer, - &logger, - qmltypesFiles, - engine.comments(), - }; - - for (auto it = options.cbegin(); it != options.cend(); ++it) { - logger.setCategoryError(it.value().m_category, it.value().m_error); - logger.setCategoryLevel(it.value().m_category, it.value().m_level); - } - - parser.rootNode()->accept(&v); - success = v.check(); - - if (logger.hasErrors()) - return; - - QQmlJSTypeInfo typeInfo; - Codegen codegen { &importer, filename, qmltypesFiles, &logger, &typeInfo, code }; - QQmlJSSaveFunction saveFunction = [](const QV4::CompiledData::SaveableUnitPointer &, - const QQmlJSAotFunctionMap &, - QString *) { return true; }; - - QQmlJSCompileError error; - - QLoggingCategory::setFilterRules(u"qt.qml.compiler=false"_qs); - - CodegenWarningInterface interface(&logger); - qCompileQmlFile(filename, saveFunction, &codegen, &error, true, &interface); - - success &= !logger.hasWarnings() && !logger.hasErrors(); - - if (json) { - for (const auto &error : logger.errors()) - addJsonWarning(error); - for (const auto &warning : logger.warnings()) - addJsonWarning(warning); - for (const auto &info : logger.infos()) - addJsonWarning(info); - } - }; - - if (resourceFiles.isEmpty()) { - check(nullptr); - } else { - QQmlJSResourceFileMapper mapper(resourceFiles); - check(&mapper); - } - } - - return success; -} - int main(int argv, char *argc[]) { qSetGlobalQHashSeed(0); @@ -394,7 +221,7 @@ All warnings can be set to three levels: QStringList resourceFiles {}; #endif bool success = true; - QQmlJSImporter importer(qmlImportPaths, nullptr); + QQmlLinter linter(qmlImportPaths, useAbsolutePath); QJsonArray jsonFiles; @@ -439,8 +266,8 @@ All warnings can be set to three levels: const auto arguments = app.arguments(); for (const QString &filename : arguments) { #endif - success &= lint_file(filename, silent, useAbsolutePath, useJson ? &jsonFiles : nullptr, - qmlImportPaths, qmltypesFiles, resourceFiles, options, importer); + success &= linter.lintFile(filename, silent, useJson ? &jsonFiles : nullptr, qmlImportPaths, + qmltypesFiles, resourceFiles, options); } if (useJson) {