qtdeclarative/src/qmlcompiler/qqmljslogger.cpp

325 lines
14 KiB
C++
Raw Normal View History

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <qglobal.h>
// GCC 11 thinks diagMsg.fixSuggestion.fixes.d.ptr is somehow uninitialized in
// QList::emplaceBack(), probably called from QQmlJsLogger::log()
// Ditto for GCC 12, but it emits a different warning
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wuninitialized")
QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
#include <qlist.h>
QT_WARNING_POP
#include "qqmljslogger_p.h"
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
const LoggerWarningId qmlRequired { "required" };
const LoggerWarningId qmlAlias { "alias" };
const LoggerWarningId qmlImport { "import" };
const LoggerWarningId qmlRecursionDepthErrors { "recursion-depth-errors" };
const LoggerWarningId qmlWith { "with" };
const LoggerWarningId qmlInheritanceCycle { "inheritance-cycle" };
const LoggerWarningId qmlDeprecated { "deprecated" };
const LoggerWarningId qmlSignal { "signal" };
const LoggerWarningId qmlType { "type" };
const LoggerWarningId qmlProperty { "property" };
const LoggerWarningId qmlDeferredPropertyId { "deferred-property-id" };
const LoggerWarningId qmlUnqualified { "unqualified" };
const LoggerWarningId qmlUnusedImports { "unused-imports" };
const LoggerWarningId qmlMultilineStrings { "multiline-strings" };
const LoggerWarningId qmlSyntax { "syntax" };
const LoggerWarningId qmlSyntaxIdQuotation { "syntax.id-quotation" };
const LoggerWarningId qmlSyntaxDuplicateIds { "syntax.duplicate-ids" };
const LoggerWarningId qmlCompiler { "compiler" };
const LoggerWarningId qmlControlsSanity { "controls-sanity" };
const LoggerWarningId qmlAttachedPropertyReuse { "attached-property-reuse" };
const LoggerWarningId qmlPlugin { "plugin" };
const QList<QQmlJSLogger::Category> &QQmlJSLogger::defaultCategories()
{
static const QList<QQmlJSLogger::Category> cats = {
QQmlJSLogger::Category { qmlRequired.name().toString(), QStringLiteral("RequiredProperty"),
QStringLiteral("Warn about required properties"), QtWarningMsg },
QQmlJSLogger::Category { qmlAlias.name().toString(), QStringLiteral("PropertyAlias"),
QStringLiteral("Warn about alias errors"), QtWarningMsg },
QQmlJSLogger::Category {
qmlImport.name().toString(), QStringLiteral("ImportFailure"),
QStringLiteral("Warn about failing imports and deprecated qmltypes"),
QtWarningMsg },
QQmlJSLogger::Category {
qmlRecursionDepthErrors.name().toString(), QStringLiteral("ImportFailure"),
QStringLiteral("Warn about failing imports and deprecated qmltypes"), QtWarningMsg,
false, true },
QQmlJSLogger::Category {
qmlWith.name().toString(), QStringLiteral("WithStatement"),
QStringLiteral("Warn about with statements as they can cause false "
"positives when checking for unqualified access"),
QtWarningMsg },
QQmlJSLogger::Category { qmlInheritanceCycle.name().toString(),
QStringLiteral("InheritanceCycle"),
QStringLiteral("Warn about inheritance cycles"), QtWarningMsg },
QQmlJSLogger::Category { qmlDeprecated.name().toString(), QStringLiteral("Deprecated"),
QStringLiteral("Warn about deprecated properties and types"),
QtWarningMsg },
QQmlJSLogger::Category { qmlSignal.name().toString(), QStringLiteral("BadSignalHandler"),
QStringLiteral("Warn about bad signal handler parameters"),
QtWarningMsg },
QQmlJSLogger::Category {
qmlType.name().toString(), QStringLiteral("TypeError"),
QStringLiteral("Warn about unresolvable types and type mismatches"), QtWarningMsg },
QQmlJSLogger::Category { qmlProperty.name().toString(), QStringLiteral("UnknownProperty"),
QStringLiteral("Warn about unknown properties"), QtWarningMsg },
QQmlJSLogger::Category {
qmlDeferredPropertyId.name().toString(), QStringLiteral("DeferredPropertyId"),
QStringLiteral(
"Warn about making deferred properties immediate by giving them an id."),
QtWarningMsg },
QQmlJSLogger::Category {
qmlUnqualified.name().toString(), QStringLiteral("UnqualifiedAccess"),
QStringLiteral("Warn about unqualified identifiers and how to fix them"),
QtWarningMsg },
QQmlJSLogger::Category { qmlUnusedImports.name().toString(),
QStringLiteral("UnusedImports"),
QStringLiteral("Warn about unused imports"), QtInfoMsg },
QQmlJSLogger::Category { qmlMultilineStrings.name().toString(),
QStringLiteral("MultilineStrings"),
QStringLiteral("Warn about multiline strings"), QtInfoMsg },
QQmlJSLogger::Category { qmlSyntax.name().toString(), QString(),
QStringLiteral("Syntax errors"), QtWarningMsg, false, true },
QQmlJSLogger::Category { qmlSyntaxIdQuotation.name().toString(), QString(),
QStringLiteral("ID quotation"), QtWarningMsg, false, true },
QQmlJSLogger::Category { qmlSyntaxDuplicateIds.name().toString(), QString(),
QStringLiteral("ID duplication"), QtCriticalMsg, false, true },
QQmlJSLogger::Category { qmlCompiler.name().toString(), QStringLiteral("CompilerWarnings"),
QStringLiteral("Warn about compiler issues"), QtWarningMsg, true },
QQmlJSLogger::Category {
qmlControlsSanity.name().toString(), QStringLiteral("ControlsSanity"),
QStringLiteral("Performance checks used for QuickControl's implementation"),
QtCriticalMsg, true },
QQmlJSLogger::Category {
qmlAttachedPropertyReuse.name().toString(), QStringLiteral("AttachedPropertyReuse"),
QStringLiteral("Warn if attached types from parent components aren't reused"),
QtCriticalMsg, true },
QQmlJSLogger::Category { qmlPlugin.name().toString(), QStringLiteral("LintPluginWarnings"),
QStringLiteral("Warn if a qmllint plugin finds an issue"),
QtWarningMsg, true }
};
return cats;
}
const QList<QQmlJSLogger::Category> QQmlJSLogger::categories() const
{
return m_categories.values();
}
void QQmlJSLogger::registerCategory(const QQmlJSLogger::Category &category)
{
if (m_categories.contains(category.name)) {
qWarning() << "Trying to re-register existing logger category" << category.name;
return;
}
m_categoryLevels[category.name] = category.level;
m_categoryIgnored[category.name] = category.ignored;
m_categories.insert(category.name, category);
}
QQmlJSLogger::QQmlJSLogger()
{
static const QList<QQmlJSLogger::Category> cats = defaultCategories();
for (const QQmlJSLogger::Category &category : cats)
registerCategory(category);
// setup color output
m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
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_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground); // Yellow?
m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
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_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground); // None?
}
static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
{
static QHash<QtMsgType, int> level = { { QtDebugMsg, 0 },
{ QtInfoMsg, 1 },
{ QtWarningMsg, 2 },
{ QtCriticalMsg, 3 },
{ QtFatalMsg, 4 } };
return level[a] < level[b];
}
void QQmlJSLogger::log(const QString &message, LoggerWarningId id,
const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext,
bool showFileName, const std::optional<FixSuggestion> &suggestion,
const QString overrideFileName)
{
Q_ASSERT(m_categoryLevels.contains(id.name().toString()));
if (isCategoryIgnored(id))
return;
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
// Note: assume \a type is the type we should prefer for logging
if (srcLocation.isValid()
&& m_ignoredWarnings[srcLocation.startLine].contains(id.name().toString()))
return;
QString prefix;
if ((!overrideFileName.isEmpty() || !m_fileName.isEmpty()) && showFileName)
prefix =
(!overrideFileName.isEmpty() ? overrideFileName : m_fileName) + QStringLiteral(":");
if (srcLocation.isValid())
prefix += QStringLiteral("%1:%2:").arg(srcLocation.startLine).arg(srcLocation.startColumn);
if (!prefix.isEmpty())
prefix.append(QLatin1Char(' '));
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
// Note: we do the clamping to [Info, Critical] range since our logger only
// supports 3 categories
type = std::clamp(type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
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
// Note: since we clamped our \a type, the output message is not printed
// exactly like it was requested, bear with us
m_output.writePrefixedMessage(prefix + message, type);
Message diagMsg;
diagMsg.message = message;
diagMsg.loc = srcLocation;
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
diagMsg.type = type;
diagMsg.fixSuggestion = suggestion;
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
switch (type) {
case QtWarningMsg: m_warnings.push_back(diagMsg); break;
case QtCriticalMsg: m_errors.push_back(diagMsg); break;
case QtInfoMsg: m_infos.push_back(diagMsg); break;
default: break;
}
if (srcLocation.isValid() && !m_code.isEmpty() && showContext)
printContext(overrideFileName, srcLocation);
if (suggestion.has_value())
printFix(suggestion.value());
}
void QQmlJSLogger::processMessages(const QList<QQmlJS::DiagnosticMessage> &messages,
LoggerWarningId id)
{
if (messages.isEmpty() || isCategoryIgnored(id))
return;
m_output.write(QStringLiteral("---\n"));
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
// TODO: we should instead respect message's category here (potentially, it
// should hold a category instead of type)
for (const QQmlJS::DiagnosticMessage &message : messages)
log(message.message, id, QQmlJS::SourceLocation(), false, false);
m_output.write(QStringLiteral("---\n\n"));
}
void QQmlJSLogger::printContext(const QString &overrideFileName,
const QQmlJS::SourceLocation &location)
{
QString code = m_code;
if (!overrideFileName.isEmpty() && overrideFileName != QFileInfo(m_fileName).absolutePath()) {
QFile file(overrideFileName);
const bool success = file.open(QFile::ReadOnly);
Q_ASSERT(success);
code = QString::fromUtf8(file.readAll());
}
IssueLocationWithContext issueLocationWithContext { code, location };
if (const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
m_output.write(beforeText);
bool locationMultiline = issueLocationWithContext.issueText().contains(QLatin1Char('\n'));
if (!issueLocationWithContext.issueText().isEmpty())
m_output.write(issueLocationWithContext.issueText().toString(), QtCriticalMsg);
m_output.write(issueLocationWithContext.afterText().toString() + QLatin1Char('\n'));
// Do not draw location indicator for multiline locations
if (locationMultiline)
return;
int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char('\t'));
int locationLength = location.length == 0 ? 1 : location.length;
m_output.write(QString::fromLatin1(" ").repeated(issueLocationWithContext.beforeText().length()
- tabCount)
+ QString::fromLatin1("\t").repeated(tabCount)
+ QString::fromLatin1("^").repeated(locationLength) + QLatin1Char('\n'));
}
void QQmlJSLogger::printFix(const FixSuggestion &fix)
{
const QString currentFileAbsPath = QFileInfo(m_fileName).absolutePath();
QString code = m_code;
QString currentFile;
for (const auto &fixItem : fix.fixes) {
m_output.writePrefixedMessage(fixItem.message, QtInfoMsg);
if (!fixItem.cutLocation.isValid())
continue;
if (fixItem.fileName == currentFile) {
// Nothing to do in this case, we've already read the code
} else if (fixItem.fileName.isEmpty() || fixItem.fileName == currentFileAbsPath) {
code = m_code;
} else {
QFile file(fixItem.fileName);
const bool success = file.open(QFile::ReadOnly);
Q_ASSERT(success);
code = QString::fromUtf8(file.readAll());
currentFile = fixItem.fileName;
}
IssueLocationWithContext issueLocationWithContext { code, fixItem.cutLocation };
if (const QStringView beforeText = issueLocationWithContext.beforeText();
!beforeText.isEmpty()) {
m_output.write(beforeText);
}
// The replacement string can be empty if we're only pointing something out to the user
QStringView replacementString = fixItem.replacementString.isEmpty()
? issueLocationWithContext.issueText()
: fixItem.replacementString;
// But if there's nothing to change it has to be a hint
if (fixItem.replacementString.isEmpty())
Q_ASSERT(fixItem.isHint);
m_output.write(replacementString, QtDebugMsg);
m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
int tabCount = issueLocationWithContext.beforeText().count(u'\t');
// Do not draw location indicator for multiline replacement strings
if (replacementString.contains(u'\n'))
continue;
m_output.write(u" "_s.repeated(
issueLocationWithContext.beforeText().length() - tabCount)
+ u"\t"_s.repeated(tabCount)
+ u"^"_s.repeated(fixItem.replacementString.length()) + u'\n');
}
}
QT_END_NAMESPACE