5#include <QtCore/qcompilerdetection.h>
10QT_WARNING_DISABLE_GCC(
"-Wuninitialized")
11QT_WARNING_DISABLE_GCC(
"-Wmaybe-uninitialized")
12#include <QtCore/qlist.h>
15#include <private/qqmljslogger_p.h>
16#include <private/qqmlsa_p.h>
18#include <QtQmlCompiler/qqmljsloggingutils.h>
20#include <QtCore/qglobal.h>
21#include <QtCore/qfile.h>
26using namespace Qt::StringLiterals;
29
30
31
32
33
34
37
38
39
40
41
42
43
44
47#define QMLLINT_BUILTIN_CATEGORIES
48 X(qmlAccessSingleton, "access-singleton-via-object", "AccessSingletonViaObject",
49 "Warn if a singleton is accessed via an object", Warning, NonEssential)
50 X(qmlAliasCycle, "alias-cycle", "AliasCycle", "Warn about alias cycles", Warning, NonEssential)
51 X(qmlAssignmentInCondition, "assignment-in-condition", "AssignmentInCondition",
52 "Warn about using assignment in conditions.", Warning, NonEssential)
53 X(qmlAttachedPropertyReuse, "attached-property-reuse", "AttachedPropertyReuse",
54 "Warn if attached types from parent components aren't reused. This is handled by the "
55 "QtQuick lint plugin. Use Quick.AttachedPropertyReuse instead.",
56 Disable, NonEssential)
57 X(qmlBlockScopeVarDeclaration, "block-scope-var-declaration", "BlockScopeVarDeclaration",
58 "Warn if a variable is declared with var inside a block scope", Warning, NonEssential)
59 X(qmlComma, "comma", "Comma", "Warn about using comma expressions.", Warning, NonEssential)
60 X(qmlCompiler, "compiler", "CompilerWarnings", "Warn about compiler issues", Disable,
62 X(qmlComponentChildrenCount, "component-children-count", "ComponentChildrenCount",
63 "Warn about Components that don't have exactly one child", Warning, NonEssential)
64 X(qmlConfusingExpressionStatement, "confusing-expression-statement",
65 "ConfusingExpressionStatement",
66 "Warn about expression statement that has no obvious effect.", Warning, NonEssential)
67 X(qmlConfusingMinuses, "confusing-minuses", "ConfusingMinuses",
68 "Warn about confusing minuses.", Warning, NonEssential)
69 X(qmlConfusingPluses, "confusing-pluses", "ConfusingPluses",
70 "Warn about confusing pluses.", Warning, NonEssential)
71 X(qmlContextProperties, "context-properties", "ContextProperties",
72 "Warn about using context properties.", Warning, NonEssential)
73 X(qmlDeferredPropertyId, "deferred-property-id", "DeferredPropertyId",
74 "Warn about making deferred properties immediate by giving them an id.", Disable,
76 X(qmlEnumsAreNotTypes, "enums-are-not-types", "EnumsAreNotTypes",
77 "Warn about the use of enumerations as types.", Warning, NonEssential)
78 X(qmlEqualityTypeCoercion, "equality-type-coercion", "EqualityTypeCoercion",
79 "Warn about coercions due to usages of '==' and '!='", Warning, NonEssential)
80 X(qmlDeprecated, "deprecated", "Deprecated", "Warn about deprecated properties and types",
81 Warning, NonEssential)
82 X(qmlDuplicateEnumEntries, "duplicate-enum-entries", "DuplicateEnumEntries",
83 "Warn about duplicate enum entries", Warning, NonEssential)
84 X(qmlDuplicateImport, "duplicate-import", "DuplicateImport", "Warn about duplicate imports",
85 Warning, NonEssential)
86 X(qmlDuplicateInlineComponent, "duplicate-inline-component", "DuplicateInlineComponent",
87 "Warn about duplicate inline components", Warning, NonEssential)
88 X(qmlDuplicatePropertyBinding, "duplicate-property-binding", "DuplicatePropertyBinding",
89 "Warn about duplicate property bindings", Warning, NonEssential)
90 X(qmlDuplicatedName, "duplicated-name", "DuplicatedName",
91 "Warn about duplicated property/signal names", Warning, NonEssential)
92 X(qmlEnumEntryMatchesEnum, "enum-entry-matches-enum", "EnumEntryMatchesEnum",
93 "Warn about enum entries named the same as the enum itself", Warning, NonEssential)
94 X(qmlEnumKeyCase, "enum-key-case", "EnumKeyCase", "Warn about lowercase enum keys", Warning,
96 X(qmlEval, "eval", "Eval", "Warn about uses of eval()", Warning, NonEssential)
97 X(qmlFunctionUsedBeforeDeclaration, "function-used-before-declaration",
98 "FunctionUsedBeforeDeclaration", "Warn if a function is used before declaration",
99 Disable, NonEssential)
100 X(qmlIdShadowsMember, "id-shadows-member", "IdShadowsMember",
101 "Warn about ids potentially shadowing members", Warning, NonEssential)
102 X(qmlImport, "import", "ImportFailure", "Warn about failing imports and deprecated qmltypes",
103 Warning, NonEssential)
104 X(qmlImportFileSelector, "import-file-selector", "ImportFileSelector",
105 "Warn about encountered file selectors during import", Disable, NonEssential)
106 X(qmlIncompatibleType, "incompatible-type", "IncompatibleType",
107 "Warn about incompatible types", Warning, NonEssential)
108 X(qmlInheritanceCycle, "inheritance-cycle", "InheritanceCycle",
109 "Warn about inheritance cycles", Warning, NonEssential)
110 X(qmlInlineComponentEnums, "inline-component-enums", "InlineComponentEnums",
111 "Warn about enum declarations inside inline components", Warning, NonEssential)
112 X(qmlInvalidLintDirective, "invalid-lint-directive", "InvalidLintDirective",
113 "Warn if an invalid qmllint comment is found", Warning, NonEssential)
114 X(qmlLiteralConstructor, "literal-constructor", "LiteralConstructor",
115 "Warn about using literal constructors, like Boolean or String for example.", Warning,
117 X(qmlMissingEnumEntry, "missing-enum-entry", "MissingEnumEntry",
118 "Warn about using missing enum values.", Warning, NonEssential)
119 X(qmlMissingProperty, "missing-property", "MissingProperty", "Warn about missing properties",
120 Warning, NonEssential)
121 X(qmlMissingType, "missing-type", "MissingType", "Warn about missing types", Warning,
123 X(qmlMultilineStrings, "multiline-strings", "MultilineStrings",
124 "Warn about multiline strings", Info, NonEssential)
125 X(qmlNonListProperty, "non-list-property", "NonListProperty",
126 "Warn about non-list properties", Warning, NonEssential)
127 X(qmlNonRootEnums, "non-root-enum", "NonRootEnum",
128 "Warn about enums defined outside the root component", Warning, NonEssential)
129 X(qmlPropertyOverride, "property-override", "PropertyOverride",
130 "Warn about wrongly overriding properties from a base class", Warning, NonEssential)
131 X(qmlUnterminatedCase, "unterminated-case", "UnterminatedCase", "Warn about non-empty case "
132 "blocks that are not terminated by control flow or by a fallthrough comment", Warning,
134 X(qmlPreferNonVarProperties, "prefer-non-var-properties", "PreferNonVarProperties",
135 "Warn about var properties that could use a more specific type", Warning, NonEssential)
136 X(qmlPrefixedImportType, "prefixed-import-type", "PrefixedImportType",
137 "Warn about prefixed import types", Warning, NonEssential)
138 X(qmlReadOnlyProperty, "read-only-property", "ReadOnlyProperty",
139 "Warn about writing to read-only properties", Warning, NonEssential)
140 X(qmlRecursionDepthErrors, "recursion-depth-errors", "", "", Warning, NonEssential)
141 X(qmlRedundantOptionalChaining, "redundant-optional-chaining", "RedundantOptionalChaining",
142 "Warn about optional chaining on non-voidable and non-nullable base", Warning, NonEssential)
143 X(qmlRequired, "required", "RequiredProperty", "Warn about required properties", Warning,
145 X(qmlShadow, "shadow", "Shadow", "Warn about shadowing attributes from a base class", Disable,
147 X(qmlSignalParameters, "signal-handler-parameters", "BadSignalHandlerParameters",
148 "Warn about bad signal handler parameters", Warning, NonEssential)
149 X(qmlStalePropertyRead, "stale-property-read", "StalePropertyRead",
150 "Warn about bindings reading non-constant and non-notifiable properties", Warning,
152 X(qmlSyntax, "syntax", "Syntax", "Syntax errors", Warning, Essential)
153 X(qmlSyntaxDuplicateIds, "syntax.duplicate-ids", "", "ID duplication", Error, NonEssential)
154 X(qmlSyntaxIdQuotation, "syntax.id-quotation", "", "ID quotation", Warning, NonEssential)
155 X(qmlTopLevelComponent, "top-level-component", "TopLevelComponent",
156 "Warn if a top level Component is encountered", Warning, NonEssential)
157 X(qmlUncreatableType, "uncreatable-type", "UncreatableType",
158 "Warn if uncreatable types are created", Warning, NonEssential)
159 X(qmlUnintentionalEmptyBlock, "unintentional-empty-block", "UnintentionalEmptyBlock",
160 "Warn about bindings that contain only an empty block", Warning, NonEssential)
161 X(qmlUnqualified, "unqualified", "UnqualifiedAccess",
162 "Warn about unqualified identifiers and how to fix them", Warning, NonEssential)
163 X(qmlUnreachableCode, "unreachable-code", "UnreachableCode", "Warn about unreachable code.",
164 Warning, NonEssential)
165 X(qmlUnresolvedAlias, "unresolved-alias", "UnresolvedAlias", "Warn about unresolved aliases",
166 Warning, NonEssential)
167 X(qmlUnresolvedType, "unresolved-type", "UnresolvedType", "Warn about unresolved types",
168 Warning, NonEssential)
169 X(qmlUnusedImports, "unused-imports", "UnusedImports", "Warn about unused imports", Info,
171 X(qmlUseProperFunction, "use-proper-function", "UseProperFunction",
172 "Warn if var is used for storing functions", Disable, NonEssential)
173 X(qmlVarUsedBeforeDeclaration, "var-used-before-declaration", "VarUsedBeforeDeclaration",
174 "Warn if a variable is used before declaration", Warning, NonEssential)
175 X(qmlVoid, "void", "Void", "Warn about void expressions.", Disable, NonEssential)
176 X(qmlWith, "with", "WithStatement",
177 "Warn about with statements as they can cause NonEssential "
178 "positives when checking for unqualified access", Warning, NonEssential)
180#define X(category, name, setting, description, severity, essential)
181 const QQmlSA::LoggerWarningId category{ name };
186#define X(category, name, setting, description, severity, essential) ++i;
190constexpr bool isUnique(
const std::array<std::string_view, numCategories>& fields) {
191 for (std::size_t i = 0; i < fields.size(); ++i) {
192 for (std::size_t j = i + 1; j < fields.size(); ++j) {
193 if (!fields[i].empty() && fields[i] == fields[j]) {
201#define X(category, name, setting, description, severity, essential) std::string_view(name),
205#define X(category, name, setting, description, severity, essential) std::string_view(setting),
209#define X(category, name, setting, description, severity, essential) std::string_view(description),
214QQmlJSLogger::QQmlJSLogger()
216 static const QList<QQmlJS::LoggerCategory> cats = builtinCategories();
218 for (
const QQmlJS::LoggerCategory &category : cats)
219 registerCategory(category);
222 m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
223 m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground);
224 m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
225 m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground);
228const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::builtinCategories()
230 static const QList<QQmlJS::LoggerCategory> cats = {
231#define X(category, name, setting, description, severity, essential)
232 QQmlJS::LoggerCategory{ name##_L1, setting##_L1, description##_L1, QQmlJS::WarningSeverity::severity, QQmlJS::LoggerCategory::essential },
240bool QQmlJSFixSuggestion::operator==(
const QQmlJSFixSuggestion &other)
const
242 return m_location == other.m_location && m_description == other.m_description
243 && m_replacement == other.m_replacement && m_filename == other.m_filename
244 && m_autoApplicable == other.m_autoApplicable;
247bool QQmlJSFixSuggestion::operator!=(
const QQmlJSFixSuggestion &other)
const
249 return !(*
this == other);
252QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories()
const
254 return m_categories.values();
257void QQmlJSLogger::registerCategory(
const QQmlJS::LoggerCategory &category)
259 if (m_categories.contains(category.name())) {
260 qWarning() <<
"Trying to re-register existing logger category" << category.name();
264 m_categorySeverities[category.name()] = category.severity();
265 m_categories.insert(category.name(), category);
270 static QHash<QtMsgType,
int> level = { { QtDebugMsg, 0 },
273 { QtCriticalMsg, 3 },
275 return level[a] < level[b];
278void QQmlJSLogger::log(Message &&diagMsg,
bool showContext,
bool showFileName)
280 Q_ASSERT(m_categorySeverities.contains(diagMsg.id.toString()));
282 if (categorySeverity(diagMsg.id) == QQmlJS::WarningSeverity::Disable || isDisabled())
287 if (diagMsg.loc.isValid()
288 && m_ignoredWarnings[diagMsg.lineForDisabling()].contains(diagMsg.id.toString())) {
293 if (!m_filePath.isEmpty() && showFileName)
294 prefix = m_filePath + QStringLiteral(
":");
296 if (diagMsg.loc.isValid())
297 prefix += QStringLiteral(
"%1:%2: ").arg(diagMsg.loc.startLine).arg(diagMsg.loc.startColumn);
298 else if (!prefix.isEmpty())
299 prefix += QStringLiteral(
": ");
303 diagMsg.type = std::clamp(diagMsg.type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
307 m_output.writePrefixedMessage(
308 u"%1%2 [%3]"_s.arg(prefix, diagMsg.message, diagMsg.id.toString()), diagMsg.type);
310 if (diagMsg.loc.length > 0 && !m_code.isEmpty() && showContext)
311 printContext(diagMsg.loc);
313 if (diagMsg.fixSuggestion.has_value())
314 printFix(diagMsg.fixSuggestion.value());
316 if (m_inTransaction) {
317 m_pendingMessages.push_back(std::move(diagMsg));
319 countMessage(diagMsg);
320 m_currentFunctionMessages.push_back(std::move(diagMsg));
323 if (!m_inTransaction)
324 m_output.flushBuffer();
327void QQmlJSLogger::countMessage(
const Message &message)
329 switch (message.type) {
341void QQmlJSLogger::processMessages(QSpan<
const QQmlJS::DiagnosticMessage> messages,
342 QQmlJS::LoggerWarningId id,
343 const QQmlJS::SourceLocation &sourceLocation)
345 if (messages.isEmpty() || categorySeverity(id) == QQmlJS::WarningSeverity::Disable || isDisabled())
348 m_output.write(QStringLiteral(
"---\n"));
352 for (
const QQmlJS::DiagnosticMessage &message : messages)
353 log(message.message, id, sourceLocation,
false,
false);
355 m_output.write(QStringLiteral(
"---\n\n"));
358void QQmlJSLogger::finalizeFunction()
360 Q_ASSERT(!m_inTransaction);
361 m_archivedMessages.append(std::exchange(m_currentFunctionMessages, {}));
362 m_hasCompileError =
false;
366
367
368
369
370
371
372
373
374
375
376void QQmlJSLogger::startTransaction()
378 Q_ASSERT(!m_inTransaction);
379 m_inTransaction =
true;
383
384
385
386
387void QQmlJSLogger::commit()
389 Q_ASSERT(m_inTransaction);
390 for (
const Message &message : std::as_const(m_pendingMessages))
391 countMessage(message);
393 m_currentFunctionMessages.append(std::exchange(m_pendingMessages, {}));
394 m_hasCompileError = m_hasCompileError || std::exchange(m_hasPendingCompileError,
false);
395 m_output.flushBuffer();
396 m_inTransaction =
false;
400
401
402
403
404void QQmlJSLogger::rollback()
406 Q_ASSERT(m_inTransaction);
407 m_pendingMessages.clear();
408 m_hasPendingCompileError =
false;
409 m_output.discardBuffer();
410 m_inTransaction =
false;
413void QQmlJSLogger::printContext(
const QQmlJS::SourceLocation &location)
415 QString code = m_code;
417 IssueLocationWithContext issueLocationWithContext { code, location };
418 if (
const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
419 m_output.write(beforeText);
421 bool locationMultiline = issueLocationWithContext.issueText().contains(QLatin1Char(
'\n'));
423 if (!issueLocationWithContext.issueText().isEmpty())
424 m_output.write(issueLocationWithContext.issueText().toString(), QtCriticalMsg);
425 m_output.write(issueLocationWithContext.afterText().toString() + QLatin1Char(
'\n'));
428 if (locationMultiline)
431 int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char(
'\t'));
432 int locationLength = location.length == 0 ? 1 : location.length;
433 m_output.write(QString::fromLatin1(
" ").repeated(issueLocationWithContext.beforeText().size()
435 + QString::fromLatin1(
"\t").repeated(tabCount)
436 + QString::fromLatin1(
"^").repeated(locationLength) + QLatin1Char(
'\n'));
439void QQmlJSLogger::printFix(
const QQmlJSFixSuggestion &fixItem)
441 const QString currentFileAbsPath = m_filePath;
442 QString code = m_code;
444 m_output.writePrefixedMessage(fixItem.description(), QtInfoMsg);
446 if (!fixItem.location().isValid())
449 const QString filename = fixItem.filename();
450 if (filename == currentFile) {
452 }
else if (filename.isEmpty() || filename == currentFileAbsPath) {
455 QFile file(filename);
456 const bool success = file.open(QFile::ReadOnly);
458 code = QString::fromUtf8(file.readAll());
459 currentFile = filename;
462 IssueLocationWithContext issueLocationWithContext { code, fixItem.location() };
464 if (
const QStringView beforeText = issueLocationWithContext.beforeText();
465 !beforeText.isEmpty()) {
466 m_output.write(beforeText);
470 const QString replacement = fixItem.replacement();
471 QStringView replacementString = replacement.isEmpty()
472 ? issueLocationWithContext.issueText()
476 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
478 if (!replacementString.isEmpty())
479 m_output.write(replacementString, QtDebugMsg);
480 m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
482 int tabCount = issueLocationWithContext.beforeText().count(u'\t');
485 if (!replacementString.contains(u'\n')) {
486 m_output.write(u" "_s.repeated(
487 issueLocationWithContext.beforeText().size() - tabCount)
488 + u"\t"_s.repeated(tabCount)
489 + u"^"_s.repeated(replacement.size()) + u'\n');
493QQmlJSFixSuggestion::QQmlJSFixSuggestion(
const QString &description,
494 const QQmlJS::SourceLocation &location,
495 const QString &replacement)
496 : m_location{ location }, m_description{ description }, m_replacement{ replacement }
constexpr bool isUnique(const std::array< std::string_view, numCategories > &fields)
constexpr size_t numCategories
#define QMLLINT_BUILTIN_CATEGORIES
static bool isMsgTypeLess(QtMsgType a, QtMsgType b)