2022-05-13 13:12:05 +00:00
|
|
|
// Copyright (C) 2021 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2021-11-17 10:25:36 +00:00
|
|
|
|
2022-02-03 11:01:35 +00:00
|
|
|
#include "qqmljslinter_p.h"
|
2021-11-17 10:25:36 +00:00
|
|
|
|
2022-02-03 11:01:35 +00:00
|
|
|
#include "qqmljslintercodegen_p.h"
|
2021-11-17 10:25:36 +00:00
|
|
|
|
|
|
|
#include <QtQmlCompiler/private/qqmljsimporter_p.h>
|
2021-12-16 16:15:21 +00:00
|
|
|
#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h>
|
2022-04-27 07:19:54 +00:00
|
|
|
#include <QtQmlCompiler/private/qqmljsliteralbindingcheck_p.h>
|
2024-10-15 09:09:20 +00:00
|
|
|
#include <QtQmlCompiler/private/qqmljstranslationfunctionmismatchcheck_p.h>
|
2021-11-17 10:25:36 +00:00
|
|
|
|
|
|
|
#include <QtCore/qjsonobject.h>
|
|
|
|
#include <QtCore/qfileinfo.h>
|
|
|
|
#include <QtCore/qloggingcategory.h>
|
2022-03-16 12:19:54 +00:00
|
|
|
#include <QtCore/qpluginloader.h>
|
|
|
|
#include <QtCore/qlibraryinfo.h>
|
|
|
|
#include <QtCore/qdir.h>
|
2022-04-11 09:33:52 +00:00
|
|
|
#include <QtCore/private/qduplicatetracker_p.h>
|
2022-05-02 14:51:01 +00:00
|
|
|
#include <QtCore/qscopedpointer.h>
|
2022-04-11 09:33:52 +00:00
|
|
|
|
2022-03-16 12:19:54 +00:00
|
|
|
#include <QtQmlCompiler/private/qqmlsa_p.h>
|
2023-05-05 07:30:27 +00:00
|
|
|
#include <QtQmlCompiler/private/qqmljsloggingutils_p.h>
|
2022-03-16 12:19:54 +00:00
|
|
|
|
|
|
|
#if QT_CONFIG(library)
|
|
|
|
# include <QtCore/qdiriterator.h>
|
|
|
|
# include <QtCore/qlibrary.h>
|
|
|
|
#endif
|
2021-11-17 10:25:36 +00:00
|
|
|
|
|
|
|
#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>
|
|
|
|
|
2022-03-16 12:19:54 +00:00
|
|
|
|
2021-11-17 10:25:36 +00:00
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2022-02-03 11:01:35 +00:00
|
|
|
class CodegenWarningInterface final : public QV4::Compiler::CodegenWarningInterface
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
CodegenWarningInterface(QQmlJSLogger *logger) : m_logger(logger) { }
|
|
|
|
|
|
|
|
void reportVarUsedBeforeDeclaration(const QString &name, const QString &fileName,
|
|
|
|
QQmlJS::SourceLocation declarationLocation,
|
|
|
|
QQmlJS::SourceLocation accessLocation) override
|
|
|
|
{
|
|
|
|
Q_UNUSED(fileName)
|
2025-03-31 14:27:06 +00:00
|
|
|
|
|
|
|
m_logger->log("Identifier '%1' is used here before its declaration."_L1.arg(name),
|
|
|
|
qmlVarUsedBeforeDeclaration, accessLocation);
|
|
|
|
m_logger->log("Note: declaration of '%1' here"_L1.arg(name), qmlVarUsedBeforeDeclaration,
|
|
|
|
declarationLocation, true, true, {}, {}, accessLocation.startLine);
|
2022-02-03 11:01:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
QQmlJSLogger *m_logger;
|
|
|
|
};
|
|
|
|
|
2024-10-01 16:02:10 +00:00
|
|
|
QQmlJSLinter::QQmlJSLinter(const QStringList &importPaths, const QStringList &extraPluginPaths,
|
2022-03-16 12:19:54 +00:00
|
|
|
bool useAbsolutePath)
|
|
|
|
: m_useAbsolutePath(useAbsolutePath),
|
|
|
|
m_enablePlugins(true),
|
2024-08-26 14:59:42 +00:00
|
|
|
m_importer(importPaths, nullptr, UseOptionalImports)
|
2022-03-16 12:19:54 +00:00
|
|
|
{
|
2024-10-01 16:02:10 +00:00
|
|
|
m_plugins = loadPlugins(extraPluginPaths);
|
2022-03-16 12:19:54 +00:00
|
|
|
}
|
|
|
|
|
2022-04-06 14:59:24 +00:00
|
|
|
QQmlJSLinter::Plugin::Plugin(QQmlJSLinter::Plugin &&plugin) noexcept
|
2023-01-13 14:00:56 +00:00
|
|
|
: m_name(std::move(plugin.m_name))
|
|
|
|
, m_description(std::move(plugin.m_description))
|
|
|
|
, m_version(std::move(plugin.m_version))
|
|
|
|
, m_author(std::move(plugin.m_author))
|
|
|
|
, m_categories(std::move(plugin.m_categories))
|
|
|
|
, m_instance(std::move(plugin.m_instance))
|
|
|
|
, m_loader(std::move(plugin.m_loader))
|
|
|
|
, m_isBuiltin(std::move(plugin.m_isBuiltin))
|
|
|
|
, m_isInternal(std::move(plugin.m_isInternal))
|
|
|
|
, m_isValid(std::move(plugin.m_isValid))
|
2022-03-16 12:19:54 +00:00
|
|
|
{
|
|
|
|
// Mark the old Plugin as invalid and make sure it doesn't delete the loader
|
2023-01-13 14:00:56 +00:00
|
|
|
Q_ASSERT(!plugin.m_loader);
|
2022-03-16 12:19:54 +00:00
|
|
|
plugin.m_instance = nullptr;
|
|
|
|
plugin.m_isValid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if QT_CONFIG(library)
|
|
|
|
QQmlJSLinter::Plugin::Plugin(QString path)
|
|
|
|
{
|
2023-01-13 14:00:56 +00:00
|
|
|
m_loader = std::make_unique<QPluginLoader>(path);
|
2022-03-16 12:19:54 +00:00
|
|
|
if (!parseMetaData(m_loader->metaData(), path))
|
|
|
|
return;
|
|
|
|
|
|
|
|
QObject *object = m_loader->instance();
|
|
|
|
if (!object)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_instance = qobject_cast<QQmlSA::LintPlugin *>(object);
|
|
|
|
if (!m_instance)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_isValid = true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
QQmlJSLinter::Plugin::Plugin(const QStaticPlugin &staticPlugin)
|
2021-11-17 10:25:36 +00:00
|
|
|
{
|
2022-03-21 09:21:18 +00:00
|
|
|
if (!parseMetaData(staticPlugin.metaData(), u"built-in"_s))
|
2022-03-16 12:19:54 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
m_instance = qobject_cast<QQmlSA::LintPlugin *>(staticPlugin.instance());
|
|
|
|
if (!m_instance)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_isValid = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
QQmlJSLinter::Plugin::~Plugin()
|
|
|
|
{
|
|
|
|
#if QT_CONFIG(library)
|
|
|
|
if (m_loader != nullptr) {
|
|
|
|
m_loader->unload();
|
|
|
|
m_loader->deleteLater();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QQmlJSLinter::Plugin::parseMetaData(const QJsonObject &metaData, QString pluginName)
|
|
|
|
{
|
|
|
|
const QString pluginIID = QStringLiteral(QmlLintPluginInterface_iid);
|
|
|
|
|
|
|
|
if (metaData[u"IID"].toString() != pluginIID)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
QJsonObject pluginMetaData = metaData[u"MetaData"].toObject();
|
|
|
|
|
2022-05-30 12:52:03 +00:00
|
|
|
for (const QString &requiredKey :
|
|
|
|
{ u"name"_s, u"version"_s, u"author"_s, u"loggingCategories"_s }) {
|
2022-03-16 12:19:54 +00:00
|
|
|
if (!pluginMetaData.contains(requiredKey)) {
|
|
|
|
qWarning() << pluginName << "is missing the required " << requiredKey
|
|
|
|
<< "metadata, skipping";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_name = pluginMetaData[u"name"].toString();
|
|
|
|
m_author = pluginMetaData[u"author"].toString();
|
|
|
|
m_version = pluginMetaData[u"version"].toString();
|
2022-03-21 09:21:18 +00:00
|
|
|
m_description = pluginMetaData[u"description"].toString(u"-/-"_s);
|
2022-05-30 12:52:03 +00:00
|
|
|
m_isInternal = pluginMetaData[u"isInternal"].toBool(false);
|
|
|
|
|
|
|
|
if (!pluginMetaData[u"loggingCategories"].isArray()) {
|
|
|
|
qWarning() << pluginName << "has loggingCategories which are not an array, skipping";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonArray categories = pluginMetaData[u"loggingCategories"].toArray();
|
|
|
|
|
2025-03-21 10:38:56 +00:00
|
|
|
for (const QJsonValue &value : std::as_const(categories)) {
|
2022-05-30 12:52:03 +00:00
|
|
|
if (!value.isObject()) {
|
|
|
|
qWarning() << pluginName << "has invalid loggingCategories entries, skipping";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-02-03 13:58:26 +00:00
|
|
|
const QJsonObject object = value.toObject();
|
2022-05-30 12:52:03 +00:00
|
|
|
|
|
|
|
for (const QString &requiredKey : { u"name"_s, u"description"_s }) {
|
|
|
|
if (!object.contains(requiredKey)) {
|
|
|
|
qWarning() << pluginName << " logging category is missing the required "
|
|
|
|
<< requiredKey << "metadata, skipping";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-03 13:58:26 +00:00
|
|
|
const auto it = object.find("enabled"_L1);
|
|
|
|
const bool ignored = (it != object.end() && !it->toBool());
|
|
|
|
|
2024-10-21 13:22:50 +00:00
|
|
|
const QString prefix = (m_isInternal ? u""_s : u"Plugin."_s).append(m_name).append(u'.');
|
2022-05-30 12:52:03 +00:00
|
|
|
const QString categoryId =
|
2024-10-21 13:22:50 +00:00
|
|
|
prefix + object[u"name"].toString();
|
2023-09-01 06:33:34 +00:00
|
|
|
const auto settingsNameIt = object.constFind(u"settingsName");
|
|
|
|
const QString settingsName = (settingsNameIt == object.constEnd())
|
|
|
|
? categoryId
|
2024-10-21 13:22:50 +00:00
|
|
|
: prefix + settingsNameIt->toString(categoryId);
|
2023-09-01 06:33:34 +00:00
|
|
|
m_categories << QQmlJS::LoggerCategory{ categoryId, settingsName,
|
2023-05-05 07:30:27 +00:00
|
|
|
object["description"_L1].toString(), QtWarningMsg,
|
|
|
|
ignored };
|
2024-10-22 12:54:08 +00:00
|
|
|
const auto itLevel = object.find("defaultLevel"_L1);
|
|
|
|
if (itLevel == object.end())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const QString level = itLevel->toString();
|
|
|
|
if (!QQmlJS::LoggingUtils::applyLevelToCategory(level, m_categories.last())) {
|
|
|
|
qWarning() << "Invalid logging level" << level << "provided for"
|
|
|
|
<< m_categories.last().id().name().toString()
|
2024-10-22 12:54:08 +00:00
|
|
|
<< "(allowed are: disable, info, warning, error) found in plugin metadata.";
|
2024-10-22 12:54:08 +00:00
|
|
|
}
|
2022-05-30 12:52:03 +00:00
|
|
|
}
|
2022-03-16 12:19:54 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-10-01 16:02:10 +00:00
|
|
|
std::vector<QQmlJSLinter::Plugin> QQmlJSLinter::loadPlugins(QStringList extraPluginPaths)
|
2022-03-16 12:19:54 +00:00
|
|
|
{
|
|
|
|
std::vector<Plugin> plugins;
|
|
|
|
|
2022-04-11 09:33:52 +00:00
|
|
|
QDuplicateTracker<QString> seenPlugins;
|
|
|
|
|
2025-03-21 10:38:56 +00:00
|
|
|
const auto &staticPlugins = QPluginLoader::staticPlugins();
|
|
|
|
for (const QStaticPlugin &staticPlugin : staticPlugins) {
|
2022-03-16 12:19:54 +00:00
|
|
|
Plugin plugin(staticPlugin);
|
2022-04-11 09:33:52 +00:00
|
|
|
if (!plugin.isValid())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (seenPlugins.hasSeen(plugin.name().toLower())) {
|
|
|
|
qWarning() << "Two plugins named" << plugin.name()
|
|
|
|
<< "present, make sure no plugins are duplicated. The second plugin will "
|
|
|
|
"not be loaded.";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
plugins.push_back(std::move(plugin));
|
2022-03-16 12:19:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#if QT_CONFIG(library)
|
2024-10-01 16:02:10 +00:00
|
|
|
const QStringList paths = [&extraPluginPaths]() {
|
|
|
|
QStringList result{ extraPluginPaths };
|
|
|
|
const QStringList libraryPaths = QCoreApplication::libraryPaths();
|
|
|
|
for (const auto &path : libraryPaths) {
|
|
|
|
result.append(path + u"/qmllint"_s);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}();
|
2022-03-16 12:19:54 +00:00
|
|
|
for (const QString &pluginDir : paths) {
|
2024-10-21 13:19:29 +00:00
|
|
|
QDirIterator it{ pluginDir, QDir::Files };
|
2022-03-16 12:19:54 +00:00
|
|
|
|
|
|
|
while (it.hasNext()) {
|
|
|
|
auto potentialPlugin = it.next();
|
|
|
|
|
|
|
|
if (!QLibrary::isLibrary(potentialPlugin))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Plugin plugin(potentialPlugin);
|
|
|
|
|
2022-04-11 09:33:52 +00:00
|
|
|
if (!plugin.isValid())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (seenPlugins.hasSeen(plugin.name().toLower())) {
|
|
|
|
qWarning() << "Two plugins named" << plugin.name()
|
|
|
|
<< "present, make sure no plugins are duplicated. The second plugin "
|
|
|
|
"will not be loaded.";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
plugins.push_back(std::move(plugin));
|
2022-03-16 12:19:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2024-10-01 16:02:10 +00:00
|
|
|
Q_UNUSED(extraPluginPaths)
|
2022-03-16 12:19:54 +00:00
|
|
|
return plugins;
|
2021-11-17 10:25:36 +00:00
|
|
|
}
|
|
|
|
|
2022-02-03 11:01:35 +00:00
|
|
|
void QQmlJSLinter::parseComments(QQmlJSLogger *logger,
|
|
|
|
const QList<QQmlJS::SourceLocation> &comments)
|
2021-12-16 16:15:21 +00:00
|
|
|
{
|
2022-05-30 12:52:03 +00:00
|
|
|
QHash<int, QSet<QString>> disablesPerLine;
|
|
|
|
QHash<int, QSet<QString>> enablesPerLine;
|
|
|
|
QHash<int, QSet<QString>> oneLineDisablesPerLine;
|
2021-12-16 16:15:21 +00:00
|
|
|
|
|
|
|
const QString code = logger->code();
|
|
|
|
const QStringList lines = code.split(u'\n');
|
2022-09-15 09:08:33 +00:00
|
|
|
const auto loggerCategories = logger->categories();
|
2021-12-16 16:15:21 +00:00
|
|
|
|
|
|
|
for (const auto &loc : comments) {
|
|
|
|
const QString comment = code.mid(loc.offset, loc.length);
|
|
|
|
if (!comment.startsWith(u" qmllint ") && !comment.startsWith(u"qmllint "))
|
|
|
|
continue;
|
|
|
|
|
2022-09-15 09:08:33 +00:00
|
|
|
QStringList words = comment.split(u' ', Qt::SkipEmptyParts);
|
|
|
|
if (words.size() < 2)
|
|
|
|
continue;
|
2021-12-16 16:15:21 +00:00
|
|
|
|
2022-05-30 12:52:03 +00:00
|
|
|
QSet<QString> categories;
|
2021-12-16 16:15:21 +00:00
|
|
|
for (qsizetype i = 2; i < words.size(); i++) {
|
|
|
|
const QString category = words.at(i);
|
2022-05-30 12:52:03 +00:00
|
|
|
const auto categoryExists = std::any_of(
|
|
|
|
loggerCategories.cbegin(), loggerCategories.cend(),
|
2023-05-05 07:30:27 +00:00
|
|
|
[&](const QQmlJS::LoggerCategory &cat) { return cat.id().name() == category; });
|
2022-05-30 12:52:03 +00:00
|
|
|
|
|
|
|
if (categoryExists)
|
|
|
|
categories << category;
|
2021-12-16 16:15:21 +00:00
|
|
|
else
|
2022-03-21 09:21:18 +00:00
|
|
|
logger->log(u"qmllint directive on unknown category \"%1\""_s.arg(category),
|
2022-07-06 08:15:30 +00:00
|
|
|
qmlInvalidLintDirective, loc);
|
2021-12-16 16:15:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (categories.isEmpty()) {
|
2025-03-21 10:38:56 +00:00
|
|
|
const auto &loggerCategories = logger->categories();
|
|
|
|
for (const auto &option : loggerCategories)
|
2022-05-30 12:52:03 +00:00
|
|
|
categories << option.id().name().toString();
|
2021-12-16 16:15:21 +00:00
|
|
|
}
|
|
|
|
|
2022-09-15 09:08:33 +00:00
|
|
|
const QString command = words.at(1);
|
2022-03-21 09:21:18 +00:00
|
|
|
if (command == u"disable"_s) {
|
2022-09-15 09:08:33 +00:00
|
|
|
if (const qsizetype lineIndex = loc.startLine - 1; lineIndex < lines.size()) {
|
|
|
|
const QString line = lines[lineIndex];
|
|
|
|
const QString preComment = line.left(line.indexOf(comment) - 2);
|
|
|
|
|
|
|
|
bool lineHasContent = false;
|
|
|
|
for (qsizetype i = 0; i < preComment.size(); i++) {
|
|
|
|
if (!preComment[i].isSpace()) {
|
|
|
|
lineHasContent = true;
|
|
|
|
break;
|
|
|
|
}
|
2021-12-16 16:15:21 +00:00
|
|
|
}
|
|
|
|
|
2022-09-15 09:08:33 +00:00
|
|
|
if (lineHasContent)
|
|
|
|
oneLineDisablesPerLine[loc.startLine] |= categories;
|
|
|
|
else
|
|
|
|
disablesPerLine[loc.startLine] |= categories;
|
|
|
|
}
|
2022-03-21 09:21:18 +00:00
|
|
|
} else if (command == u"enable"_s) {
|
2021-12-16 16:15:21 +00:00
|
|
|
enablesPerLine[loc.startLine + 1] |= categories;
|
|
|
|
} else {
|
2022-07-06 08:15:30 +00:00
|
|
|
logger->log(u"Invalid qmllint directive \"%1\" provided"_s.arg(command),
|
|
|
|
qmlInvalidLintDirective, loc);
|
2021-12-16 16:15:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (disablesPerLine.isEmpty() && oneLineDisablesPerLine.isEmpty())
|
|
|
|
return;
|
|
|
|
|
2022-05-30 12:52:03 +00:00
|
|
|
QSet<QString> currentlyDisabled;
|
2022-10-05 05:29:16 +00:00
|
|
|
for (qsizetype i = 1; i <= lines.size(); i++) {
|
2021-12-16 16:15:21 +00:00
|
|
|
currentlyDisabled.unite(disablesPerLine[i]).subtract(enablesPerLine[i]);
|
|
|
|
|
|
|
|
currentlyDisabled.unite(oneLineDisablesPerLine[i]);
|
|
|
|
|
|
|
|
if (!currentlyDisabled.isEmpty())
|
|
|
|
logger->ignoreWarnings(i, currentlyDisabled);
|
|
|
|
|
|
|
|
currentlyDisabled.subtract(oneLineDisablesPerLine[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-12 08:39:31 +00:00
|
|
|
static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage &message,
|
2023-02-06 11:14:38 +00:00
|
|
|
QAnyStringView id, const std::optional<QQmlJSFixSuggestion> &suggestion = {})
|
2022-07-12 08:39:31 +00:00
|
|
|
{
|
|
|
|
QJsonObject jsonMessage;
|
|
|
|
|
|
|
|
QString type;
|
|
|
|
switch (message.type) {
|
|
|
|
case QtDebugMsg:
|
|
|
|
type = u"debug"_s;
|
|
|
|
break;
|
|
|
|
case QtWarningMsg:
|
|
|
|
type = u"warning"_s;
|
|
|
|
break;
|
|
|
|
case QtCriticalMsg:
|
|
|
|
type = u"critical"_s;
|
|
|
|
break;
|
|
|
|
case QtFatalMsg:
|
|
|
|
type = u"fatal"_s;
|
|
|
|
break;
|
|
|
|
case QtInfoMsg:
|
|
|
|
type = u"info"_s;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
type = u"unknown"_s;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonMessage[u"type"_s] = type;
|
|
|
|
jsonMessage[u"id"_s] = id.toString();
|
|
|
|
|
|
|
|
if (message.loc.isValid()) {
|
|
|
|
jsonMessage[u"line"_s] = static_cast<int>(message.loc.startLine);
|
|
|
|
jsonMessage[u"column"_s] = static_cast<int>(message.loc.startColumn);
|
|
|
|
jsonMessage[u"charOffset"_s] = static_cast<int>(message.loc.offset);
|
|
|
|
jsonMessage[u"length"_s] = static_cast<int>(message.loc.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonMessage[u"message"_s] = message.message;
|
|
|
|
|
|
|
|
QJsonArray suggestions;
|
2023-02-06 11:14:38 +00:00
|
|
|
const auto convertLocation = [](const QQmlJS::SourceLocation &source, QJsonObject *target) {
|
|
|
|
target->insert("line"_L1, int(source.startLine));
|
|
|
|
target->insert("column"_L1, int(source.startColumn));
|
|
|
|
target->insert("charOffset"_L1, int(source.offset));
|
|
|
|
target->insert("length"_L1, int(source.length));
|
|
|
|
};
|
2022-07-12 08:39:31 +00:00
|
|
|
if (suggestion.has_value()) {
|
2023-02-06 11:14:38 +00:00
|
|
|
QJsonObject jsonFix {
|
|
|
|
{ "message"_L1, suggestion->fixDescription() },
|
|
|
|
{ "replacement"_L1, suggestion->replacement() },
|
|
|
|
{ "isHint"_L1, !suggestion->isAutoApplicable() },
|
|
|
|
};
|
|
|
|
convertLocation(suggestion->location(), &jsonFix);
|
|
|
|
const QString filename = suggestion->filename();
|
|
|
|
if (!filename.isEmpty())
|
|
|
|
jsonFix.insert("fileName"_L1, filename);
|
|
|
|
suggestions << jsonFix;
|
|
|
|
|
|
|
|
const QString hint = suggestion->hint();
|
|
|
|
if (!hint.isEmpty()) {
|
|
|
|
// We need to keep compatibility with the JSON format.
|
|
|
|
// Therefore the overly verbose encoding of the hint.
|
|
|
|
QJsonObject jsonHint {
|
|
|
|
{ "message"_L1, hint },
|
|
|
|
{ "replacement"_L1, QString() },
|
|
|
|
{ "isHint"_L1, true }
|
|
|
|
};
|
|
|
|
convertLocation(QQmlJS::SourceLocation(), &jsonHint);
|
|
|
|
suggestions << jsonHint;
|
2022-07-12 08:39:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
jsonMessage[u"suggestions"] = suggestions;
|
|
|
|
|
|
|
|
warnings << jsonMessage;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void QQmlJSLinter::processMessages(QJsonArray &warnings)
|
|
|
|
{
|
2025-01-20 17:08:23 +00:00
|
|
|
m_logger->iterateAllMessages([&](const Message &message) {
|
2025-01-20 13:02:11 +00:00
|
|
|
addJsonWarning(warnings, message, message.id, message.fixSuggestion);
|
2025-01-20 17:08:23 +00:00
|
|
|
});
|
2022-07-12 08:39:31 +00:00
|
|
|
}
|
|
|
|
|
2022-02-28 14:51:24 +00:00
|
|
|
QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
|
|
|
|
const QString *fileContents, const bool silent,
|
|
|
|
QJsonArray *json, const QStringList &qmlImportPaths,
|
|
|
|
const QStringList &qmldirFiles,
|
|
|
|
const QStringList &resourceFiles,
|
2023-05-05 07:30:27 +00:00
|
|
|
const QList<QQmlJS::LoggerCategory> &categories)
|
2021-11-17 10:25:36 +00:00
|
|
|
{
|
2021-11-23 14:52:44 +00:00
|
|
|
// Make sure that we don't expose an old logger if we return before a new one is created.
|
|
|
|
m_logger.reset();
|
|
|
|
|
2021-11-17 10:25:36 +00:00
|
|
|
QJsonArray warnings;
|
|
|
|
QJsonObject result;
|
|
|
|
|
2024-10-22 12:54:08 +00:00
|
|
|
LintResult success = LintSuccess;
|
2021-11-17 10:25:36 +00:00
|
|
|
|
|
|
|
QScopeGuard jsonOutput([&] {
|
|
|
|
if (!json)
|
|
|
|
return;
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
result[u"filename"_s] = QFileInfo(filename).absoluteFilePath();
|
2021-11-17 10:25:36 +00:00
|
|
|
result[u"warnings"] = warnings;
|
2024-10-22 12:54:08 +00:00
|
|
|
result[u"success"] = success == LintSuccess;
|
2021-11-17 10:25:36 +00:00
|
|
|
|
|
|
|
json->append(result);
|
|
|
|
});
|
|
|
|
|
2021-11-23 14:52:44 +00:00
|
|
|
QString code;
|
|
|
|
|
|
|
|
if (fileContents == nullptr) {
|
|
|
|
QFile file(filename);
|
|
|
|
if (!file.open(QFile::ReadOnly)) {
|
|
|
|
if (json) {
|
2022-02-03 11:01:35 +00:00
|
|
|
addJsonWarning(
|
2022-07-12 08:39:31 +00:00
|
|
|
warnings,
|
2022-02-03 11:01:35 +00:00
|
|
|
QQmlJS::DiagnosticMessage { QStringLiteral("Failed to open file %1: %2")
|
|
|
|
.arg(filename, file.errorString()),
|
2022-06-30 10:48:12 +00:00
|
|
|
QtCriticalMsg, QQmlJS::SourceLocation() },
|
|
|
|
qmlImport.name());
|
2021-11-23 14:52:44 +00:00
|
|
|
} else if (!silent) {
|
|
|
|
qWarning() << "Failed to open file" << filename << file.error();
|
|
|
|
}
|
2024-10-22 12:54:08 +00:00
|
|
|
success = FailedToOpen;
|
|
|
|
return success;
|
2021-11-17 10:25:36 +00:00
|
|
|
}
|
|
|
|
|
2021-11-23 14:52:44 +00:00
|
|
|
code = QString::fromUtf8(file.readAll());
|
|
|
|
file.close();
|
|
|
|
} else {
|
|
|
|
code = *fileContents;
|
|
|
|
}
|
2021-11-17 10:25:36 +00:00
|
|
|
|
2022-02-28 14:51:24 +00:00
|
|
|
m_fileContents = code;
|
|
|
|
|
2021-11-17 10:25:36 +00:00
|
|
|
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);
|
|
|
|
|
2024-10-22 12:54:08 +00:00
|
|
|
if (!(isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram())
|
|
|
|
: parser.parse())) {
|
|
|
|
success = FailedToParse;
|
2021-11-17 10:25:36 +00:00
|
|
|
const auto diagnosticMessages = parser.diagnosticMessages();
|
|
|
|
for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
|
|
|
|
if (json) {
|
2022-07-12 08:39:31 +00:00
|
|
|
addJsonWarning(warnings, m, qmlSyntax.name());
|
2021-12-07 17:48:08 +00:00
|
|
|
} else if (!silent) {
|
|
|
|
qWarning().noquote() << QString::fromLatin1("%1:%2:%3: %4")
|
2021-11-17 10:25:36 +00:00
|
|
|
.arg(filename)
|
|
|
|
.arg(m.loc.startLine)
|
2021-12-07 17:48:08 +00:00
|
|
|
.arg(m.loc.startColumn)
|
2021-11-17 10:25:36 +00:00
|
|
|
.arg(m.message);
|
|
|
|
}
|
|
|
|
}
|
2024-10-22 12:54:08 +00:00
|
|
|
return success;
|
2021-11-17 10:25:36 +00:00
|
|
|
}
|
|
|
|
|
2024-10-22 12:54:08 +00:00
|
|
|
if (!isJavaScript) {
|
2021-11-17 10:25:36 +00:00
|
|
|
const auto check = [&](QQmlJSResourceFileMapper *mapper) {
|
|
|
|
if (m_importer.importPaths() != qmlImportPaths)
|
|
|
|
m_importer.setImportPaths(qmlImportPaths);
|
|
|
|
|
|
|
|
m_importer.setResourceFileMapper(mapper);
|
|
|
|
|
2021-11-18 13:26:29 +00:00
|
|
|
m_logger.reset(new QQmlJSLogger);
|
2024-10-14 10:31:16 +00:00
|
|
|
m_logger->setFilePath(m_useAbsolutePath ? info.absoluteFilePath() : filename);
|
2021-11-18 13:26:29 +00:00
|
|
|
m_logger->setCode(code);
|
|
|
|
m_logger->setSilent(silent || json);
|
2022-03-31 09:26:09 +00:00
|
|
|
QQmlJSScope::Ptr target = QQmlJSScope::create();
|
|
|
|
QQmlJSImportVisitor v { target, &m_importer, m_logger.get(),
|
2021-12-16 16:15:21 +00:00
|
|
|
QQmlJSImportVisitor::implicitImportDirectory(
|
2024-10-14 10:31:16 +00:00
|
|
|
m_logger->filePath(), m_importer.resourceFileMapper()),
|
2021-12-16 16:15:21 +00:00
|
|
|
qmldirFiles };
|
|
|
|
|
2022-05-30 12:52:03 +00:00
|
|
|
if (m_enablePlugins) {
|
|
|
|
for (const Plugin &plugin : m_plugins) {
|
2023-05-05 07:30:27 +00:00
|
|
|
for (const QQmlJS::LoggerCategory &category : plugin.categories())
|
2022-05-30 12:52:03 +00:00
|
|
|
m_logger->registerCategory(category);
|
|
|
|
}
|
|
|
|
}
|
2021-11-17 10:25:36 +00:00
|
|
|
|
2022-05-30 12:52:03 +00:00
|
|
|
for (auto it = categories.cbegin(); it != categories.cend(); ++it) {
|
2023-05-05 07:30:27 +00:00
|
|
|
if (auto logger = *it; !QQmlJS::LoggerCategoryPrivate::get(&logger)->hasChanged())
|
2022-01-10 13:52:30 +00:00
|
|
|
continue;
|
|
|
|
|
2023-05-05 07:30:27 +00:00
|
|
|
m_logger->setCategoryIgnored(it->id(), it->isIgnored());
|
|
|
|
m_logger->setCategoryLevel(it->id(), it->level());
|
2021-11-17 10:25:36 +00:00
|
|
|
}
|
|
|
|
|
2022-05-30 12:52:03 +00:00
|
|
|
parseComments(m_logger.get(), engine.comments());
|
|
|
|
|
2021-11-18 13:26:29 +00:00
|
|
|
QQmlJSTypeResolver typeResolver(&m_importer);
|
2021-11-23 15:05:05 +00:00
|
|
|
|
2022-02-03 11:01:35 +00:00
|
|
|
// Type resolving is using document parent mode here so that it produces fewer false
|
|
|
|
// positives on the "parent" property of QQuickItem. It does produce a few false
|
|
|
|
// negatives this way because items can be reparented. Furthermore, even if items are
|
|
|
|
// not reparented, the document parent may indeed not be their visual parent. See
|
|
|
|
// QTBUG-95530. Eventually, we'll need cleverer logic to deal with this.
|
2021-11-18 13:26:29 +00:00
|
|
|
typeResolver.setParentMode(QQmlJSTypeResolver::UseDocumentParent);
|
2022-05-06 12:38:36 +00:00
|
|
|
// We don't need to create tracked types and such as we are just linting the code here
|
|
|
|
// and not actually compiling it. The duplicated scopes would cause issues during
|
|
|
|
// linting.
|
|
|
|
typeResolver.setCloneMode(QQmlJSTypeResolver::DoNotCloneTypes);
|
2021-11-23 15:05:05 +00:00
|
|
|
|
2021-11-18 13:26:29 +00:00
|
|
|
typeResolver.init(&v, parser.rootNode());
|
2022-03-16 12:19:54 +00:00
|
|
|
|
2024-08-09 14:38:18 +00:00
|
|
|
const QStringList resourcePaths = mapper
|
|
|
|
? mapper->resourcePaths(QQmlJSResourceFileMapper::localFileFilter(filename))
|
|
|
|
: QStringList();
|
|
|
|
const QString resolvedPath =
|
|
|
|
(resourcePaths.size() == 1) ? u':' + resourcePaths.first() : filename;
|
|
|
|
|
|
|
|
QQmlJSLinterCodegen codegen{ &m_importer, resolvedPath, qmldirFiles, m_logger.get() };
|
|
|
|
codegen.setTypeResolver(std::move(typeResolver));
|
|
|
|
|
2023-10-13 13:42:18 +00:00
|
|
|
using PassManagerPtr = std::unique_ptr<
|
|
|
|
QQmlSA::PassManager, decltype(&QQmlSA::PassManagerPrivate::deletePassManager)>;
|
2024-08-09 14:38:18 +00:00
|
|
|
PassManagerPtr passMan(
|
|
|
|
QQmlSA::PassManagerPrivate::createPassManager(&v, codegen.typeResolver()),
|
|
|
|
&QQmlSA::PassManagerPrivate::deletePassManager);
|
2023-10-26 15:49:14 +00:00
|
|
|
passMan->registerPropertyPass(
|
|
|
|
std::make_unique<QQmlJSLiteralBindingCheck>(passMan.get()), QString(),
|
|
|
|
QString(), QString());
|
2024-10-15 09:09:20 +00:00
|
|
|
passMan->registerPropertyPass(
|
|
|
|
std::make_unique<QQmlJSTranslationFunctionMismatchCheck>(passMan.get()),
|
|
|
|
QString(), QString(), QString());
|
2022-05-02 14:51:01 +00:00
|
|
|
|
2025-03-25 15:08:43 +00:00
|
|
|
QQmlSA::PropertyPassBuilder(passMan.get())
|
|
|
|
.withOnCall([](QQmlSA::PropertyPass *self, const QQmlSA::Element &,
|
|
|
|
const QString &, const QQmlSA::Element &,
|
|
|
|
QQmlSA::SourceLocation location) {
|
|
|
|
self->emitWarning("Do not use 'eval'", qmlEval, location);
|
|
|
|
})
|
|
|
|
.registerOnBuiltin("GlobalObject", "eval");
|
|
|
|
|
2022-04-12 08:05:37 +00:00
|
|
|
if (m_enablePlugins) {
|
|
|
|
for (const Plugin &plugin : m_plugins) {
|
2022-04-11 15:48:36 +00:00
|
|
|
if (!plugin.isValid() || !plugin.isEnabled())
|
2022-04-12 08:05:37 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
QQmlSA::LintPlugin *instance = plugin.m_instance;
|
|
|
|
Q_ASSERT(instance);
|
2023-05-05 07:30:27 +00:00
|
|
|
instance->registerPasses(passMan.get(),
|
|
|
|
QQmlJSScope::createQQmlSAElement(v.result()));
|
2022-04-12 08:05:37 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-26 15:49:14 +00:00
|
|
|
passMan->analyze(QQmlJSScope::createQQmlSAElement(v.result()));
|
2022-03-16 12:19:54 +00:00
|
|
|
|
2021-12-15 09:25:40 +00:00
|
|
|
if (m_logger->hasErrors()) {
|
2024-10-22 12:54:08 +00:00
|
|
|
success = HasErrors;
|
2022-07-12 08:39:31 +00:00
|
|
|
if (json)
|
|
|
|
processMessages(warnings);
|
2021-11-17 10:25:36 +00:00
|
|
|
return;
|
2024-10-22 12:54:08 +00:00
|
|
|
} else if (m_logger->hasWarnings())
|
|
|
|
success = HasWarnings;
|
2021-11-17 10:25:36 +00:00
|
|
|
|
2024-07-01 10:39:53 +00:00
|
|
|
if (passMan) {
|
|
|
|
// passMan now has a pointer to the moved from type resolver
|
|
|
|
// we fix this in setPassManager
|
2022-05-02 14:51:01 +00:00
|
|
|
codegen.setPassManager(passMan.get());
|
2024-07-01 10:39:53 +00:00
|
|
|
}
|
2021-11-17 10:25:36 +00:00
|
|
|
QQmlJSSaveFunction saveFunction = [](const QV4::CompiledData::SaveableUnitPointer &,
|
|
|
|
const QQmlJSAotFunctionMap &,
|
|
|
|
QString *) { return true; };
|
|
|
|
|
|
|
|
QQmlJSCompileError error;
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
QLoggingCategory::setFilterRules(u"qt.qml.compiler=false"_s);
|
2021-11-17 10:25:36 +00:00
|
|
|
|
2021-11-23 14:52:44 +00:00
|
|
|
CodegenWarningInterface interface(m_logger.get());
|
2021-12-13 12:19:33 +00:00
|
|
|
qCompileQmlFile(filename, saveFunction, &codegen, &error, true, &interface,
|
|
|
|
fileContents);
|
2021-11-17 10:25:36 +00:00
|
|
|
|
2022-07-12 08:39:31 +00:00
|
|
|
QList<QQmlJS::DiagnosticMessage> globalWarnings = m_importer.takeGlobalWarnings();
|
2022-01-18 12:08:09 +00:00
|
|
|
|
2022-07-12 08:39:31 +00:00
|
|
|
if (!globalWarnings.isEmpty()) {
|
Redesign QQmlJSLogger internals
High-level goal: be able to reuse existing infrastructure
"as is" to configure semantic analysis categories in tools
(qmllint, qmltc, qmlsc, etc.)
To achieve that, simplify the logging to always "log"
something, without explicitly specifying the severity. The
severity is now baked into the category (and we can extend
those to cover different cases)
One slight deviation is the cache generation which likes
to do its own thing at present. Provide a "forced logging"
option where we can specify which severify we want. The
hope is that this gets removed at some point
Particular list of (noteworthy) changes:
* No more "thresholding" by the level (this is rarely needed
and is actually questionable). Instead, we can ignore a
particular category explicitly
* Category levels are repurposed as category severities
(at least from the high-level picture that always should've
been this way)
* log{Warning,Info,Critical} removed. We use category severity
instead
* "category error" makes zero sense so removed: if our severity
is:
- QtWarningMsg (qmllint), it is already an "error"
- QtCriticalMsg (compilers), it is already an "error"
* Align m_output and m_{infos,warnings,errors} stored information
* Accept the fact that we don't support QtDebugMsg and QtFatalMsg
* Additional categories added to cover for places where the same
category would be both an error and not an error
Task-number: QTBUG-100052
Change-Id: I3cd5d17d58be204f48428877bed053f756ac40a8
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-02-03 12:45:18 +00:00
|
|
|
m_logger->log(QStringLiteral("Type warnings occurred while evaluating file:"),
|
2022-05-30 12:52:03 +00:00
|
|
|
qmlImport, QQmlJS::SourceLocation());
|
2022-07-12 08:39:31 +00:00
|
|
|
m_logger->processMessages(globalWarnings, qmlImport);
|
2022-01-18 12:08:09 +00:00
|
|
|
}
|
|
|
|
|
2024-10-22 12:54:08 +00:00
|
|
|
if (m_logger->hasErrors())
|
|
|
|
success = HasErrors;
|
|
|
|
else if (m_logger->hasWarnings())
|
|
|
|
success = HasWarnings;
|
2021-11-17 10:25:36 +00:00
|
|
|
|
2022-07-12 08:39:31 +00:00
|
|
|
if (json)
|
|
|
|
processMessages(warnings);
|
2021-11-17 10:25:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if (resourceFiles.isEmpty()) {
|
|
|
|
check(nullptr);
|
|
|
|
} else {
|
|
|
|
QQmlJSResourceFileMapper mapper(resourceFiles);
|
|
|
|
check(&mapper);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-22 12:54:08 +00:00
|
|
|
return success;
|
2022-02-28 14:51:24 +00:00
|
|
|
}
|
|
|
|
|
2023-02-06 07:08:05 +00:00
|
|
|
QQmlJSLinter::LintResult QQmlJSLinter::lintModule(
|
|
|
|
const QString &module, const bool silent, QJsonArray *json,
|
|
|
|
const QStringList &qmlImportPaths, const QStringList &resourceFiles)
|
2022-07-12 08:39:31 +00:00
|
|
|
{
|
|
|
|
// Make sure that we don't expose an old logger if we return before a new one is created.
|
|
|
|
m_logger.reset();
|
|
|
|
|
|
|
|
// We can't lint properly if a module has already been pre-cached
|
|
|
|
m_importer.clearCache();
|
|
|
|
|
2023-02-06 07:08:05 +00:00
|
|
|
if (m_importer.importPaths() != qmlImportPaths)
|
|
|
|
m_importer.setImportPaths(qmlImportPaths);
|
|
|
|
|
|
|
|
QQmlJSResourceFileMapper mapper(resourceFiles);
|
|
|
|
if (!resourceFiles.isEmpty())
|
|
|
|
m_importer.setResourceFileMapper(&mapper);
|
|
|
|
else
|
|
|
|
m_importer.setResourceFileMapper(nullptr);
|
|
|
|
|
2022-07-12 08:39:31 +00:00
|
|
|
QJsonArray warnings;
|
|
|
|
QJsonObject result;
|
|
|
|
|
|
|
|
bool success = true;
|
|
|
|
|
|
|
|
QScopeGuard jsonOutput([&] {
|
|
|
|
if (!json)
|
|
|
|
return;
|
|
|
|
|
|
|
|
result[u"module"_s] = module;
|
|
|
|
|
|
|
|
result[u"warnings"] = warnings;
|
|
|
|
result[u"success"] = success;
|
|
|
|
|
|
|
|
json->append(result);
|
|
|
|
});
|
|
|
|
|
|
|
|
m_logger.reset(new QQmlJSLogger);
|
2024-10-14 10:31:16 +00:00
|
|
|
m_logger->setFilePath(module);
|
2022-07-12 08:39:31 +00:00
|
|
|
m_logger->setCode(u""_s);
|
|
|
|
m_logger->setSilent(silent || json);
|
|
|
|
|
|
|
|
const QQmlJSImporter::ImportedTypes types = m_importer.importModule(module);
|
|
|
|
|
|
|
|
QList<QQmlJS::DiagnosticMessage> importWarnings =
|
2024-09-12 09:45:53 +00:00
|
|
|
m_importer.takeGlobalWarnings() + types.warnings();
|
2022-07-12 08:39:31 +00:00
|
|
|
|
|
|
|
if (!importWarnings.isEmpty()) {
|
|
|
|
m_logger->log(QStringLiteral("Warnings occurred while importing module:"), qmlImport,
|
|
|
|
QQmlJS::SourceLocation());
|
|
|
|
m_logger->processMessages(importWarnings, qmlImport);
|
|
|
|
}
|
|
|
|
|
|
|
|
QMap<QString, QSet<QString>> missingTypes;
|
|
|
|
QMap<QString, QSet<QString>> partiallyResolvedTypes;
|
|
|
|
|
|
|
|
const QString modulePrefix = u"$module$."_s;
|
|
|
|
const QString internalPrefix = u"$internal$."_s;
|
|
|
|
|
2022-10-19 11:07:56 +00:00
|
|
|
for (auto &&[typeName, importedScope] : types.types().asKeyValueRange()) {
|
2022-08-31 06:52:42 +00:00
|
|
|
QString name = typeName;
|
|
|
|
const QQmlJSScope::ConstPtr scope = importedScope.scope;
|
2022-07-12 08:39:31 +00:00
|
|
|
|
|
|
|
if (name.startsWith(modulePrefix))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (name.startsWith(internalPrefix)) {
|
|
|
|
name = name.mid(internalPrefix.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scope.isNull()) {
|
|
|
|
if (!missingTypes.contains(name))
|
|
|
|
missingTypes[name] = {};
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!scope->isFullyResolved()) {
|
|
|
|
if (!partiallyResolvedTypes.contains(name))
|
|
|
|
partiallyResolvedTypes[name] = {};
|
|
|
|
}
|
2025-03-21 10:38:56 +00:00
|
|
|
const auto &ownProperties = scope->ownProperties();
|
|
|
|
for (const auto &property : ownProperties) {
|
2022-07-12 08:39:31 +00:00
|
|
|
if (property.typeName().isEmpty()) {
|
|
|
|
// If the type name is empty, then it's an intentional vaguery i.e. for some
|
|
|
|
// builtins
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (property.type().isNull()) {
|
|
|
|
missingTypes[property.typeName()]
|
|
|
|
<< scope->internalName() + u'.' + property.propertyName();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!property.type()->isFullyResolved()) {
|
|
|
|
partiallyResolvedTypes[property.typeName()]
|
|
|
|
<< scope->internalName() + u'.' + property.propertyName();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (scope->attachedType() && !scope->attachedType()->isFullyResolved()) {
|
|
|
|
m_logger->log(u"Attached type of \"%1\" not fully resolved"_s.arg(name),
|
|
|
|
qmlUnresolvedType, scope->sourceLocation());
|
|
|
|
}
|
2022-07-27 13:54:48 +00:00
|
|
|
|
2025-03-21 10:38:56 +00:00
|
|
|
const auto &ownMethods = scope->ownMethods();
|
|
|
|
for (const auto &method : ownMethods) {
|
2022-07-27 13:54:48 +00:00
|
|
|
if (method.returnTypeName().isEmpty())
|
|
|
|
continue;
|
|
|
|
if (method.returnType().isNull()) {
|
|
|
|
missingTypes[method.returnTypeName()] << u"return type of "_s
|
|
|
|
+ scope->internalName() + u'.' + method.methodName() + u"()"_s;
|
|
|
|
} else if (!method.returnType()->isFullyResolved()) {
|
|
|
|
partiallyResolvedTypes[method.returnTypeName()] << u"return type of "_s
|
|
|
|
+ scope->internalName() + u'.' + method.methodName() + u"()"_s;
|
|
|
|
}
|
|
|
|
|
2022-11-10 10:38:51 +00:00
|
|
|
const auto parameters = method.parameters();
|
|
|
|
for (qsizetype i = 0; i < parameters.size(); i++) {
|
|
|
|
auto ¶meter = parameters[i];
|
|
|
|
const QString typeName = parameter.typeName();
|
|
|
|
const QSharedPointer<const QQmlJSScope> type = parameter.type();
|
|
|
|
if (typeName.isEmpty())
|
2022-07-27 13:54:48 +00:00
|
|
|
continue;
|
2022-11-10 10:38:51 +00:00
|
|
|
if (type.isNull()) {
|
|
|
|
missingTypes[typeName] << u"parameter %1 of "_s.arg(i + 1)
|
2022-07-27 13:54:48 +00:00
|
|
|
+ scope->internalName() + u'.' + method.methodName() + u"()"_s;
|
|
|
|
continue;
|
|
|
|
}
|
2022-11-10 10:38:51 +00:00
|
|
|
if (!type->isFullyResolved()) {
|
|
|
|
partiallyResolvedTypes[typeName] << u"parameter %1 of "_s.arg(i + 1)
|
2022-07-27 13:54:48 +00:00
|
|
|
+ scope->internalName() + u'.' + method.methodName() + u"()"_s;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-12 08:39:31 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 06:52:42 +00:00
|
|
|
for (auto &&[name, uses] : missingTypes.asKeyValueRange()) {
|
2022-07-12 08:39:31 +00:00
|
|
|
QString message = u"Type \"%1\" not found"_s.arg(name);
|
|
|
|
|
2022-08-31 06:52:42 +00:00
|
|
|
if (!uses.isEmpty()) {
|
|
|
|
const QStringList usesList = QStringList(uses.begin(), uses.end());
|
|
|
|
message += u". Used in %1"_s.arg(usesList.join(u", "_s));
|
|
|
|
}
|
2022-07-12 08:39:31 +00:00
|
|
|
|
|
|
|
m_logger->log(message, qmlUnresolvedType, QQmlJS::SourceLocation());
|
|
|
|
}
|
|
|
|
|
2022-08-31 06:52:42 +00:00
|
|
|
for (auto &&[name, uses] : partiallyResolvedTypes.asKeyValueRange()) {
|
2022-07-12 08:39:31 +00:00
|
|
|
QString message = u"Type \"%1\" is not fully resolved"_s.arg(name);
|
|
|
|
|
2022-08-31 06:52:42 +00:00
|
|
|
if (!uses.isEmpty()) {
|
|
|
|
const QStringList usesList = QStringList(uses.begin(), uses.end());
|
|
|
|
message += u". Used in %1"_s.arg(usesList.join(u", "_s));
|
|
|
|
}
|
2022-07-12 08:39:31 +00:00
|
|
|
|
|
|
|
m_logger->log(message, qmlUnresolvedType, QQmlJS::SourceLocation());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (json)
|
|
|
|
processMessages(warnings);
|
|
|
|
|
|
|
|
success &= !m_logger->hasWarnings() && !m_logger->hasErrors();
|
|
|
|
|
|
|
|
return success ? LintSuccess : HasWarnings;
|
|
|
|
}
|
|
|
|
|
2022-02-28 14:51:24 +00:00
|
|
|
QQmlJSLinter::FixResult QQmlJSLinter::applyFixes(QString *fixedCode, bool silent)
|
|
|
|
{
|
|
|
|
Q_ASSERT(fixedCode != nullptr);
|
|
|
|
|
|
|
|
// This means that the necessary analysis for applying fixes hasn't run for some reason
|
|
|
|
// (because it was JS file, a syntax error etc.). We can't procede without it and if an error
|
|
|
|
// has occurred that has to be handled by the caller of lintFile(). Just say that there is
|
|
|
|
// nothing to fix.
|
|
|
|
if (m_logger == nullptr)
|
|
|
|
return NothingToFix;
|
|
|
|
|
|
|
|
QString code = m_fileContents;
|
|
|
|
|
2023-02-06 11:14:38 +00:00
|
|
|
QList<QQmlJSFixSuggestion> fixesToApply;
|
2022-02-28 14:51:24 +00:00
|
|
|
|
2024-10-14 10:31:16 +00:00
|
|
|
QFileInfo info(m_logger->filePath());
|
2022-02-28 14:51:24 +00:00
|
|
|
const QString currentFileAbsolutePath = info.absoluteFilePath();
|
|
|
|
|
|
|
|
const QString lowerSuffix = info.suffix().toLower();
|
|
|
|
const bool isESModule = lowerSuffix == QLatin1String("mjs");
|
|
|
|
const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js");
|
|
|
|
|
|
|
|
if (isESModule || isJavaScript)
|
|
|
|
return NothingToFix;
|
|
|
|
|
2025-01-20 17:08:23 +00:00
|
|
|
m_logger->iterateAllMessages([&](const Message &msg) {
|
2025-01-20 13:02:11 +00:00
|
|
|
if (!msg.fixSuggestion.has_value() || !msg.fixSuggestion->isAutoApplicable())
|
2025-01-20 17:08:23 +00:00
|
|
|
return;
|
2023-02-06 11:14:38 +00:00
|
|
|
|
2025-01-20 13:02:11 +00:00
|
|
|
// Ignore fix suggestions for other files
|
|
|
|
const QString filename = msg.fixSuggestion->filename();
|
|
|
|
if (!filename.isEmpty()
|
|
|
|
&& QFileInfo(filename).absoluteFilePath() != currentFileAbsolutePath) {
|
2025-01-20 17:08:23 +00:00
|
|
|
return;
|
2022-02-28 14:51:24 +00:00
|
|
|
}
|
|
|
|
|
2025-01-20 13:02:11 +00:00
|
|
|
fixesToApply << msg.fixSuggestion.value();
|
2025-01-20 17:08:23 +00:00
|
|
|
});
|
2025-01-20 13:02:11 +00:00
|
|
|
|
2022-02-28 14:51:24 +00:00
|
|
|
if (fixesToApply.isEmpty())
|
|
|
|
return NothingToFix;
|
|
|
|
|
|
|
|
std::sort(fixesToApply.begin(), fixesToApply.end(),
|
2023-02-06 11:14:38 +00:00
|
|
|
[](const QQmlJSFixSuggestion &a, const QQmlJSFixSuggestion &b) {
|
|
|
|
return a.location().offset < b.location().offset;
|
2022-02-28 14:51:24 +00:00
|
|
|
});
|
|
|
|
|
2024-02-19 16:09:24 +00:00
|
|
|
const auto dupes = std::unique(fixesToApply.begin(), fixesToApply.end());
|
|
|
|
fixesToApply.erase(dupes, fixesToApply.end());
|
|
|
|
|
2022-02-28 14:51:24 +00:00
|
|
|
for (auto it = fixesToApply.begin(); it + 1 != fixesToApply.end(); it++) {
|
2023-02-06 11:14:38 +00:00
|
|
|
const QQmlJS::SourceLocation srcLocA = it->location();
|
|
|
|
const QQmlJS::SourceLocation srcLocB = (it + 1)->location();
|
2022-02-28 14:51:24 +00:00
|
|
|
if (srcLocA.offset + srcLocA.length > srcLocB.offset) {
|
|
|
|
if (!silent)
|
|
|
|
qWarning() << "Fixes for two warnings are overlapping, aborting. Please file a bug "
|
|
|
|
"report.";
|
|
|
|
return FixError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int offsetChange = 0;
|
|
|
|
|
2025-03-21 10:38:56 +00:00
|
|
|
for (const auto &fix : std::as_const(fixesToApply)) {
|
2023-02-06 11:14:38 +00:00
|
|
|
const QQmlJS::SourceLocation fixLocation = fix.location();
|
|
|
|
qsizetype cutLocation = fixLocation.offset + offsetChange;
|
|
|
|
const QString before = code.left(cutLocation);
|
|
|
|
const QString after = code.mid(cutLocation + fixLocation.length);
|
|
|
|
|
|
|
|
const QString replacement = fix.replacement();
|
|
|
|
code = before + replacement + after;
|
|
|
|
offsetChange += replacement.size() - fixLocation.length;
|
2022-02-28 14:51:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QQmlJS::Engine engine;
|
|
|
|
QQmlJS::Lexer lexer(&engine);
|
|
|
|
|
|
|
|
lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/!isJavaScript);
|
|
|
|
QQmlJS::Parser parser(&engine);
|
|
|
|
|
|
|
|
bool success = parser.parse();
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
const auto diagnosticMessages = parser.diagnosticMessages();
|
|
|
|
|
|
|
|
if (!silent) {
|
|
|
|
qDebug() << "File became unparseable after suggestions were applied. Please file a bug "
|
|
|
|
"report.";
|
|
|
|
} else {
|
|
|
|
return FixError;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
|
|
|
|
qWarning().noquote() << QString::fromLatin1("%1:%2:%3: %4")
|
2024-10-14 10:31:16 +00:00
|
|
|
.arg(m_logger->filePath())
|
2022-02-28 14:51:24 +00:00
|
|
|
.arg(m.loc.startLine)
|
|
|
|
.arg(m.loc.startColumn)
|
|
|
|
.arg(m.message);
|
|
|
|
}
|
|
|
|
return FixError;
|
|
|
|
}
|
|
|
|
|
|
|
|
*fixedCode = code;
|
|
|
|
return FixSuccess;
|
2021-11-17 10:25:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QT_END_NAMESPACE
|