qmllint: Move most code into a separate library

This is necessary step for both making qmllint viable for use in
controls' tst_sanity, as well as for integration with the
language server.

As an additional upside it allows us to run our tests up to 10x faster.

Eventually we want to integrate all of this into qmlcompiler but due
to the state of some of the code we will keep qmllint in a separate
library as to keep qmlcompiler tidier.

Change-Id: Ic057ef0cd4424d28aa05e517d74280a442ec9c5a
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
This commit is contained in:
Maximilian Goldstein 2021-11-17 11:25:36 +01:00
parent 608e1a8053
commit a98747ab6c
15 changed files with 407 additions and 223 deletions

View File

@ -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.

View File

@ -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
)

View File

@ -26,7 +26,7 @@
**
****************************************************************************/
#include "codegen.h"
#include "codegen_p.h"
#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h>
#include <QtQmlCompiler/private/qqmljsshadowcheck_p.h>
@ -34,6 +34,8 @@
#include <QFileInfo>
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() : "<unknown>"));
.arg(rawType ? rawType->internalName() : u"<unknown>"_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

View File

@ -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 <QtQmlCompiler/private/qqmljslogger_p.h>
#include <QtQmlCompiler/private/qqmljscompilepass_p.h>
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

View File

@ -26,10 +26,12 @@
**
****************************************************************************/
#include "codegenwarninginterface.h"
#include "codegenwarninginterface_p.h"
#include <QtQmlCompiler/private/qqmljslogger_p.h>
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

View File

@ -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 <QtQml/private/qv4codegen_p.h>
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

View File

@ -26,7 +26,7 @@
**
****************************************************************************/
#include "findwarnings.h"
#include "findwarnings_p.h"
#include <QtQmlCompiler/private/qqmljsscope_p.h>
#include <QtQmlCompiler/private/qqmljstypedescriptionreader_p.h>
@ -41,6 +41,8 @@
#include <QtCore/qdiriterator.h>
#include <QtCore/qscopedvaluerollback.h>
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

View File

@ -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 <QtCore/qscopedpointer.h>
QT_BEGIN_NAMESPACE
class FindWarningVisitor : public QQmlJSImportVisitor
{
Q_DISABLE_COPY_MOVE(FindWarningVisitor)
@ -64,11 +66,13 @@ private:
void parseComments(const QList<QQmlJS::SourceLocation> &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

220
src/qmllint/qqmllinter.cpp Normal file
View File

@ -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 <QtQmlCompiler/private/qqmljsimporter_p.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qloggingcategory.h>
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
#include <QtQml/private/qqmljsengine_p.h>
#include <QtQml/private/qqmljsastvisitor_p.h>
#include <QtQml/private/qqmljsast_p.h>
#include <QtQml/private/qqmljsdiagnosticmessage_p.h>
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<QString, QQmlJSLogger::Option> &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<int>(message.loc.startLine);
jsonMessage[u"column"_qs] = static_cast<int>(message.loc.startColumn);
jsonMessage[u"charOffset"_qs] = static_cast<int>(message.loc.offset);
jsonMessage[u"length"_qs] = static_cast<int>(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

View File

@ -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 <QtQmlCompiler/private/qqmljslogger_p.h>
#include <QtQmlCompiler/private/qqmljsimporter_p.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qstring.h>
#include <QtCore/qmap.h>
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<QString, QQmlJSLogger::Option> &options);
private:
bool m_useAbsolutePath;
QQmlJSImporter m_importer;
};
QT_END_NAMESPACE
#endif // QMLLINT_P_H

View File

@ -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",

View File

@ -16,6 +16,7 @@ qt_internal_add_test(tst_qmllint
PUBLIC_LIBRARIES
Qt::Gui
Qt::QuickTestUtilsPrivate
Qt::QmlLintPrivate
TESTDATA ${test_data}
)

View File

@ -31,6 +31,7 @@
#include <QProcess>
#include <QString>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQmlLint/private/qqmllinter_p.h>
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());

View File

@ -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()

View File

@ -26,20 +26,13 @@
**
****************************************************************************/
#include "findwarnings.h"
#include "codegen.h"
#include "codegenwarninginterface.h"
#include "../shared/qqmltoolingsettings.h"
#include <QtQmlLint/private/qqmllinter_p.h>
#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h>
#include <QtQmlCompiler/private/qqmljscompiler_p.h>
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
#include <QtQml/private/qqmljsengine_p.h>
#include <QtQml/private/qqmljsastvisitor_p.h>
#include <QtQml/private/qqmljsast_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
@ -48,7 +41,6 @@
#include <QtCore/qjsonobject.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qscopeguard.h>
#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<QString, QQmlJSLogger::Option> &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<int>(message.loc.startLine);
jsonMessage[u"column"_qs] = static_cast<int>(message.loc.startColumn);
jsonMessage[u"charOffset"_qs] = static_cast<int>(message.loc.offset);
jsonMessage[u"length"_qs] = static_cast<int>(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) {