aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljslogger.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler/qqmljslogger.cpp')
-rw-r--r--src/qmlcompiler/qqmljslogger.cpp403
1 files changed, 403 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljslogger.cpp b/src/qmlcompiler/qqmljslogger.cpp
new file mode 100644
index 0000000000..7bf686018d
--- /dev/null
+++ b/src/qmlcompiler/qqmljslogger.cpp
@@ -0,0 +1,403 @@
+// 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 <QtCore/qcompilerdetection.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 <QtCore/qlist.h>
+QT_WARNING_POP
+
+#include <private/qqmljslogger_p.h>
+#include <private/qqmlsa_p.h>
+
+#include <QtQmlCompiler/qqmljsloggingutils.h>
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+const QQmlSA::LoggerWarningId qmlRequired{ "required" };
+const QQmlSA::LoggerWarningId qmlAliasCycle{ "alias-cycle" };
+const QQmlSA::LoggerWarningId qmlUnresolvedAlias{ "unresolved-alias" };
+const QQmlSA::LoggerWarningId qmlImport{ "import" };
+const QQmlSA::LoggerWarningId qmlRecursionDepthErrors{ "recursion-depth-errors" };
+const QQmlSA::LoggerWarningId qmlWith{ "with" };
+const QQmlSA::LoggerWarningId qmlInheritanceCycle{ "inheritance-cycle" };
+const QQmlSA::LoggerWarningId qmlDeprecated{ "deprecated" };
+const QQmlSA::LoggerWarningId qmlSignalParameters{ "signal-handler-parameters" };
+const QQmlSA::LoggerWarningId qmlMissingType{ "missing-type" };
+const QQmlSA::LoggerWarningId qmlUnresolvedType{ "unresolved-type" };
+const QQmlSA::LoggerWarningId qmlRestrictedType{ "restricted-type" };
+const QQmlSA::LoggerWarningId qmlPrefixedImportType{ "prefixed-import-type" };
+const QQmlSA::LoggerWarningId qmlIncompatibleType{ "incompatible-type" };
+const QQmlSA::LoggerWarningId qmlMissingProperty{ "missing-property" };
+const QQmlSA::LoggerWarningId qmlNonListProperty{ "non-list-property" };
+const QQmlSA::LoggerWarningId qmlReadOnlyProperty{ "read-only-property" };
+const QQmlSA::LoggerWarningId qmlDuplicatePropertyBinding{ "duplicate-property-binding" };
+const QQmlSA::LoggerWarningId qmlDuplicatedName{ "duplicated-name" };
+const QQmlSA::LoggerWarningId qmlDeferredPropertyId{ "deferred-property-id" };
+const QQmlSA::LoggerWarningId qmlUnqualified{ "unqualified" };
+const QQmlSA::LoggerWarningId qmlUnusedImports{ "unused-imports" };
+const QQmlSA::LoggerWarningId qmlMultilineStrings{ "multiline-strings" };
+const QQmlSA::LoggerWarningId qmlSyntax{ "syntax" };
+const QQmlSA::LoggerWarningId qmlSyntaxIdQuotation{ "syntax.id-quotation" };
+const QQmlSA::LoggerWarningId qmlSyntaxDuplicateIds{ "syntax.duplicate-ids" };
+const QQmlSA::LoggerWarningId qmlCompiler{ "compiler" };
+const QQmlSA::LoggerWarningId qmlAttachedPropertyReuse{ "attached-property-reuse" };
+const QQmlSA::LoggerWarningId qmlPlugin{ "plugin" };
+const QQmlSA::LoggerWarningId qmlVarUsedBeforeDeclaration{ "var-used-before-declaration" };
+const QQmlSA::LoggerWarningId qmlInvalidLintDirective{ "invalid-lint-directive" };
+const QQmlSA::LoggerWarningId qmlUseProperFunction{ "use-proper-function" };
+const QQmlSA::LoggerWarningId qmlAccessSingleton{ "access-singleton-via-object" };
+const QQmlSA::LoggerWarningId qmlTopLevelComponent{ "top-level-component" };
+const QQmlSA::LoggerWarningId qmlUncreatableType{ "uncreatable-type" };
+const QQmlSA::LoggerWarningId qmlMissingEnumEntry{ "missing-enum-entry" };
+
+QQmlJSLogger::QQmlJSLogger()
+{
+ static const QList<QQmlJS::LoggerCategory> cats = defaultCategories();
+
+ for (const QQmlJS::LoggerCategory &category : cats)
+ registerCategory(category);
+
+ // setup color output
+ m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
+ m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground); // Yellow?
+ m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
+ m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground); // None?
+}
+
+const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::defaultCategories()
+{
+ static const QList<QQmlJS::LoggerCategory> cats = {
+ QQmlJS::LoggerCategory{ qmlRequired.name().toString(), QStringLiteral("RequiredProperty"),
+ QStringLiteral("Warn about required properties"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlAliasCycle.name().toString(),
+ QStringLiteral("PropertyAliasCycles"),
+ QStringLiteral("Warn about alias cycles"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlUnresolvedAlias.name().toString(),
+ QStringLiteral("PropertyAliasCycles"),
+ QStringLiteral("Warn about unresolved aliases"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlImport.name().toString(), QStringLiteral("ImportFailure"),
+ QStringLiteral("Warn about failing imports and deprecated qmltypes"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlRecursionDepthErrors.name().toString(), QStringLiteral("ImportFailure"),
+ QStringLiteral("Warn about failing imports and deprecated qmltypes"), QtWarningMsg,
+ false, true },
+ QQmlJS::LoggerCategory{ qmlWith.name().toString(), QStringLiteral("WithStatement"),
+ QStringLiteral("Warn about with statements as they can cause false "
+ "positives when checking for unqualified access"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlInheritanceCycle.name().toString(),
+ QStringLiteral("InheritanceCycle"),
+ QStringLiteral("Warn about inheritance cycles"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlDeprecated.name().toString(), QStringLiteral("Deprecated"),
+ QStringLiteral("Warn about deprecated properties and types"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlSignalParameters.name().toString(), QStringLiteral("BadSignalHandlerParameters"),
+ QStringLiteral("Warn about bad signal handler parameters"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlMissingType.name().toString(), QStringLiteral("MissingType"),
+ QStringLiteral("Warn about missing types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlUnresolvedType.name().toString(),
+ QStringLiteral("UnresolvedType"),
+ QStringLiteral("Warn about unresolved types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlRestrictedType.name().toString(),
+ QStringLiteral("RestrictedType"),
+ QStringLiteral("Warn about restricted types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlPrefixedImportType.name().toString(),
+ QStringLiteral("PrefixedImportType"),
+ QStringLiteral("Warn about prefixed import types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlIncompatibleType.name().toString(),
+ QStringLiteral("IncompatibleType"),
+ QStringLiteral("Warn about missing types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlMissingProperty.name().toString(),
+ QStringLiteral("MissingProperty"),
+ QStringLiteral("Warn about missing properties"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlNonListProperty.name().toString(),
+ QStringLiteral("NonListProperty"),
+ QStringLiteral("Warn about non-list properties"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlReadOnlyProperty.name().toString(), QStringLiteral("ReadOnlyProperty"),
+ QStringLiteral("Warn about writing to read-only properties"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlDuplicatePropertyBinding.name().toString(),
+ QStringLiteral("DuplicatePropertyBinding"),
+ QStringLiteral("Warn about duplicate property bindings"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlDuplicatedName.name().toString(), QStringLiteral("DuplicatedName"),
+ QStringLiteral("Warn about duplicated property/signal names"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlDeferredPropertyId.name().toString(), QStringLiteral("DeferredPropertyId"),
+ QStringLiteral(
+ "Warn about making deferred properties immediate by giving them an id."),
+ QtInfoMsg, true, true },
+ QQmlJS::LoggerCategory{
+ qmlUnqualified.name().toString(), QStringLiteral("UnqualifiedAccess"),
+ QStringLiteral("Warn about unqualified identifiers and how to fix them"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlUnusedImports.name().toString(), QStringLiteral("UnusedImports"),
+ QStringLiteral("Warn about unused imports"), QtInfoMsg },
+ QQmlJS::LoggerCategory{ qmlMultilineStrings.name().toString(),
+ QStringLiteral("MultilineStrings"),
+ QStringLiteral("Warn about multiline strings"), QtInfoMsg },
+ QQmlJS::LoggerCategory{ qmlSyntax.name().toString(), QString(),
+ QStringLiteral("Syntax errors"), QtWarningMsg, false, true },
+ QQmlJS::LoggerCategory{ qmlSyntaxIdQuotation.name().toString(), QString(),
+ QStringLiteral("ID quotation"), QtWarningMsg, false, true },
+ QQmlJS::LoggerCategory{ qmlSyntaxDuplicateIds.name().toString(), QString(),
+ QStringLiteral("ID duplication"), QtCriticalMsg, false, true },
+ QQmlJS::LoggerCategory{ qmlCompiler.name().toString(), QStringLiteral("CompilerWarnings"),
+ QStringLiteral("Warn about compiler issues"), QtWarningMsg, true },
+ QQmlJS::LoggerCategory{
+ qmlAttachedPropertyReuse.name().toString(), QStringLiteral("AttachedPropertyReuse"),
+ QStringLiteral("Warn if attached types from parent components "
+ "aren't reused. This is handled by the QtQuick "
+ "lint plugin. Use Quick.AttachedPropertyReuse instead."),
+ QtCriticalMsg, true },
+ QQmlJS::LoggerCategory{ qmlPlugin.name().toString(), QStringLiteral("LintPluginWarnings"),
+ QStringLiteral("Warn if a qmllint plugin finds an issue"),
+ QtWarningMsg, true },
+ QQmlJS::LoggerCategory{ qmlVarUsedBeforeDeclaration.name().toString(),
+ QStringLiteral("VarUsedBeforeDeclaration"),
+ QStringLiteral("Warn if a variable is used before declaration"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlInvalidLintDirective.name().toString(), QStringLiteral("InvalidLintDirective"),
+ QStringLiteral("Warn if an invalid qmllint comment is found"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlUseProperFunction.name().toString(), QStringLiteral("UseProperFunction"),
+ QStringLiteral("Warn if var is used for storing functions"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlAccessSingleton.name().toString(), QStringLiteral("AccessSingletonViaObject"),
+ QStringLiteral("Warn if a singleton is accessed via an object"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlTopLevelComponent.name().toString(), QStringLiteral("TopLevelComponent"),
+ QStringLiteral("Fail when a top level Component are encountered"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlUncreatableType.name().toString(), QStringLiteral("UncreatableType"),
+ QStringLiteral("Warn if uncreatable types are created"), QtWarningMsg }
+ };
+
+ return cats;
+}
+
+bool QQmlJSFixSuggestion::operator==(const QQmlJSFixSuggestion &other) const
+{
+ return m_location == other.m_location && m_fixDescription == other.m_fixDescription
+ && m_replacement == other.m_replacement && m_filename == other.m_filename
+ && m_hint == other.m_hint && m_autoApplicable == other.m_autoApplicable;
+}
+
+bool QQmlJSFixSuggestion::operator!=(const QQmlJSFixSuggestion &other) const
+{
+ return !(*this == other);
+}
+
+QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories() const
+{
+ return m_categories.values();
+}
+
+void QQmlJSLogger::registerCategory(const QQmlJS::LoggerCategory &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.isIgnored();
+ m_categories.insert(category.name(), category);
+}
+
+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, QQmlJS::LoggerWarningId id,
+ const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext,
+ bool showFileName, const std::optional<QQmlJSFixSuggestion> &suggestion,
+ const QString overrideFileName)
+{
+ Q_ASSERT(m_categoryLevels.contains(id.name().toString()));
+
+ if (isCategoryIgnored(id))
+ return;
+
+ // 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(' '));
+
+ // Note: we do the clamping to [Info, Critical] range since our logger only
+ // supports 3 categories
+ type = std::clamp(type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
+
+ // Note: since we clamped our \a type, the output message is not printed
+ // exactly like it was requested, bear with us
+ m_output.writePrefixedMessage(u"%1%2 [%3]"_s.arg(prefix, message, id.name().toString()), type);
+
+ Message diagMsg;
+ diagMsg.message = message;
+ diagMsg.id = id.name();
+ diagMsg.loc = srcLocation;
+ diagMsg.type = type;
+ diagMsg.fixSuggestion = suggestion;
+
+ 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.length > 0 && !m_code.isEmpty() && showContext)
+ printContext(overrideFileName, srcLocation);
+
+ if (suggestion.has_value())
+ printFix(suggestion.value());
+}
+
+void QQmlJSLogger::processMessages(const QList<QQmlJS::DiagnosticMessage> &messages,
+ QQmlJS::LoggerWarningId id)
+{
+ if (messages.isEmpty() || isCategoryIgnored(id))
+ return;
+
+ m_output.write(QStringLiteral("---\n"));
+
+ // 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().size()
+ - tabCount)
+ + QString::fromLatin1("\t").repeated(tabCount)
+ + QString::fromLatin1("^").repeated(locationLength) + QLatin1Char('\n'));
+}
+
+void QQmlJSLogger::printFix(const QQmlJSFixSuggestion &fixItem)
+{
+ const QString currentFileAbsPath = QFileInfo(m_fileName).absolutePath();
+ QString code = m_code;
+ QString currentFile;
+ m_output.writePrefixedMessage(fixItem.fixDescription(), QtInfoMsg);
+
+ if (!fixItem.location().isValid())
+ return;
+
+ const QString filename = fixItem.filename();
+ if (filename == currentFile) {
+ // Nothing to do in this case, we've already read the code
+ } else if (filename.isEmpty() || filename == currentFileAbsPath) {
+ code = m_code;
+ } else {
+ QFile file(filename);
+ const bool success = file.open(QFile::ReadOnly);
+ Q_ASSERT(success);
+ code = QString::fromUtf8(file.readAll());
+ currentFile = filename;
+ }
+
+ IssueLocationWithContext issueLocationWithContext { code, fixItem.location() };
+
+ 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
+ const QString replacement = fixItem.replacement();
+ QStringView replacementString = replacement.isEmpty()
+ ? issueLocationWithContext.issueText()
+ : replacement;
+
+ // But if there's nothing to change it cannot be auto-applied
+ Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
+
+ 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'))
+ return;
+
+ m_output.write(u" "_s.repeated(
+ issueLocationWithContext.beforeText().size() - tabCount)
+ + u"\t"_s.repeated(tabCount)
+ + u"^"_s.repeated(replacement.size()) + u'\n');
+}
+
+QQmlJSFixSuggestion::QQmlJSFixSuggestion(const QString &fixDescription,
+ const QQmlJS::SourceLocation &location,
+ const QString &replacement)
+ : m_location{ location }, m_fixDescription{ fixDescription }, m_replacement{ replacement }
+{
+}
+
+QT_END_NAMESPACE