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:
parent
608e1a8053
commit
a98747ab6c
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
@ -16,6 +16,7 @@ qt_internal_add_test(tst_qmllint
|
|||
PUBLIC_LIBRARIES
|
||||
Qt::Gui
|
||||
Qt::QuickTestUtilsPrivate
|
||||
Qt::QmlLintPrivate
|
||||
TESTDATA ${test_data}
|
||||
)
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue