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(qmlRenamedType, "renamed-type", "RenamedType",
144 "Warn when renamed types refer to themselves using their unrenamed name", Warning,
146 X(qmlRequired, "required", "RequiredProperty", "Warn about required properties", Warning,
148 X(qmlShadow, "shadow", "Shadow", "Warn about shadowing attributes from a base class", Disable,
150 X(qmlSignalParameters, "signal-handler-parameters", "BadSignalHandlerParameters",
151 "Warn about bad signal handler parameters", Warning, NonEssential)
152 X(qmlStalePropertyRead, "stale-property-read", "StalePropertyRead",
153 "Warn about bindings reading non-constant and non-notifiable properties", Warning,
155 X(qmlSyntax, "syntax", "Syntax", "Syntax errors", Warning, Essential)
156 X(qmlSyntaxDuplicateIds, "syntax.duplicate-ids", "", "ID duplication", Error, NonEssential)
157 X(qmlSyntaxIdQuotation, "syntax.id-quotation", "", "ID quotation", Warning, NonEssential)
158 X(qmlTypeInstantiatedRecursively, "type-instantiated-recursively",
159 "TypeInstantiatedRecursively", "Warn when types are instantiated recursively", Warning,
161 X(qmlTopLevelComponent, "top-level-component", "TopLevelComponent",
162 "Warn if a top level Component is encountered", Warning, NonEssential)
163 X(qmlUncreatableType, "uncreatable-type", "UncreatableType",
164 "Warn if uncreatable types are created", Warning, NonEssential)
165 X(qmlUnintentionalEmptyBlock, "unintentional-empty-block", "UnintentionalEmptyBlock",
166 "Warn about bindings that contain only an empty block", Warning, NonEssential)
167 X(qmlUnqualified, "unqualified", "UnqualifiedAccess",
168 "Warn about unqualified identifiers and how to fix them", Warning, NonEssential)
169 X(qmlUnreachableCode, "unreachable-code", "UnreachableCode", "Warn about unreachable code.",
170 Warning, NonEssential)
171 X(qmlUnresolvedAlias, "unresolved-alias", "UnresolvedAlias", "Warn about unresolved aliases",
172 Warning, NonEssential)
173 X(qmlUnresolvedType, "unresolved-type", "UnresolvedType", "Warn about unresolved types",
174 Warning, NonEssential)
175 X(qmlUnusedImports, "unused-imports", "UnusedImports", "Warn about unused imports", Info,
177 X(qmlUseProperFunction, "use-proper-function", "UseProperFunction",
178 "Warn if var is used for storing functions", Disable, NonEssential)
179 X(qmlVarUsedBeforeDeclaration, "var-used-before-declaration", "VarUsedBeforeDeclaration",
180 "Warn if a variable is used before declaration", Warning, NonEssential)
181 X(qmlVoid, "void", "Void", "Warn about void expressions.", Disable, NonEssential)
182 X(qmlWith, "with", "WithStatement",
183 "Warn about with statements as they can cause NonEssential "
184 "positives when checking for unqualified access", Warning, NonEssential)
186#define X(category, name, setting, description, severity, essential)
187 const QQmlSA::LoggerWarningId category{ name };
192#define X(category, name, setting, description, severity, essential) ++i;
196constexpr bool isUnique(
const std::array<std::string_view, numCategories>& fields) {
197 for (std::size_t i = 0; i < fields.size(); ++i) {
198 for (std::size_t j = i + 1; j < fields.size(); ++j) {
199 if (!fields[i].empty() && fields[i] == fields[j]) {
207#define X(category, name, setting, description, severity, essential) std::string_view(name),
211#define X(category, name, setting, description, severity, essential) std::string_view(setting),
215#define X(category, name, setting, description, severity, essential) std::string_view(description),
220QQmlJSLogger::QQmlJSLogger()
222 static const QList<QQmlJS::LoggerCategory> cats = builtinCategories();
224 for (
const QQmlJS::LoggerCategory &category : cats)
225 registerCategory(category);
228 m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
229 m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground);
230 m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
231 m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground);
234const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::builtinCategories()
236 static const QList<QQmlJS::LoggerCategory> cats = {
237#define X(category, name, setting, description, severity, essential)
238 QQmlJS::LoggerCategory{ name##_L1, setting##_L1, description##_L1, QQmlJS::WarningSeverity::severity, QQmlJS::LoggerCategory::essential },
246bool QQmlJSFixSuggestion::operator==(
const QQmlJSFixSuggestion &other)
const
248 return m_location == other.m_location && m_description == other.m_description
249 && m_replacement == other.m_replacement && m_filename == other.m_filename
250 && m_autoApplicable == other.m_autoApplicable;
253bool QQmlJSFixSuggestion::operator!=(
const QQmlJSFixSuggestion &other)
const
255 return !(*
this == other);
258QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories()
const
260 return m_categories.values();
263void QQmlJSLogger::registerCategory(
const QQmlJS::LoggerCategory &category)
265 if (m_categories.contains(category.name())) {
266 qWarning() <<
"Trying to re-register existing logger category" << category.name();
270 m_categorySeverities[category.name()] = category.severity();
271 m_categories.insert(category.name(), category);
276 static QHash<QtMsgType,
int> level = { { QtDebugMsg, 0 },
279 { QtCriticalMsg, 3 },
281 return level[a] < level[b];
284void QQmlJSLogger::log(Message &&diagMsg,
bool showContext,
bool showFileName)
286 Q_ASSERT(m_categorySeverities.contains(diagMsg.id.toString()));
288 if (categorySeverity(diagMsg.id) == QQmlJS::WarningSeverity::Disable || isDisabled())
293 if (diagMsg.loc.isValid()
294 && m_ignoredWarnings[diagMsg.lineForDisabling()].contains(diagMsg.id.toString())) {
299 if (!m_filePath.isEmpty() && showFileName)
300 prefix = m_filePath + QStringLiteral(
":");
302 if (diagMsg.loc.isValid())
303 prefix += QStringLiteral(
"%1:%2: ").arg(diagMsg.loc.startLine).arg(diagMsg.loc.startColumn);
304 else if (!prefix.isEmpty())
305 prefix += QStringLiteral(
": ");
309 diagMsg.type = std::clamp(diagMsg.type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
313 m_output.writePrefixedMessage(
314 u"%1%2 [%3]"_s.arg(prefix, diagMsg.message, diagMsg.id.toString()), diagMsg.type);
316 if (diagMsg.loc.length > 0 && !m_code.isEmpty() && showContext)
317 printContext(diagMsg.loc);
319 if (diagMsg.fixSuggestion.has_value())
320 printFix(diagMsg.fixSuggestion.value());
322 if (m_inTransaction) {
323 m_pendingMessages.push_back(std::move(diagMsg));
325 countMessage(diagMsg);
326 m_currentFunctionMessages.push_back(std::move(diagMsg));
329 if (!m_inTransaction)
330 m_output.flushBuffer();
333void QQmlJSLogger::countMessage(
const Message &message)
335 switch (message.type) {
347void QQmlJSLogger::processMessages(QSpan<
const QQmlJS::DiagnosticMessage> messages,
348 QQmlJS::LoggerWarningId id,
349 const QQmlJS::SourceLocation &sourceLocation)
351 if (messages.isEmpty() || categorySeverity(id) == QQmlJS::WarningSeverity::Disable || isDisabled())
354 m_output.write(QStringLiteral(
"---\n"));
358 for (
const QQmlJS::DiagnosticMessage &message : messages)
359 log(message.message, id, sourceLocation,
false,
false);
361 m_output.write(QStringLiteral(
"---\n\n"));
364void QQmlJSLogger::finalizeFunction()
366 Q_ASSERT(!m_inTransaction);
367 m_archivedMessages.append(std::exchange(m_currentFunctionMessages, {}));
368 m_hasCompileError =
false;
372
373
374
375
376
377
378
379
380
381
382void QQmlJSLogger::startTransaction()
384 Q_ASSERT(!m_inTransaction);
385 m_inTransaction =
true;
389
390
391
392
393void QQmlJSLogger::commit()
395 Q_ASSERT(m_inTransaction);
396 for (
const Message &message : std::as_const(m_pendingMessages))
397 countMessage(message);
399 m_currentFunctionMessages.append(std::exchange(m_pendingMessages, {}));
400 m_hasCompileError = m_hasCompileError || std::exchange(m_hasPendingCompileError,
false);
401 m_output.flushBuffer();
402 m_inTransaction =
false;
406
407
408
409
410void QQmlJSLogger::rollback()
412 Q_ASSERT(m_inTransaction);
413 m_pendingMessages.clear();
414 m_hasPendingCompileError =
false;
415 m_output.discardBuffer();
416 m_inTransaction =
false;
419void QQmlJSLogger::printContext(
const QQmlJS::SourceLocation &location)
421 QString code = m_code;
423 IssueLocationWithContext issueLocationWithContext { code, location };
424 if (
const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
425 m_output.write(beforeText);
427 bool locationMultiline = issueLocationWithContext.issueText().contains(QLatin1Char(
'\n'));
429 if (!issueLocationWithContext.issueText().isEmpty())
430 m_output.write(issueLocationWithContext.issueText().toString(), QtCriticalMsg);
431 m_output.write(issueLocationWithContext.afterText().toString() + QLatin1Char(
'\n'));
434 if (locationMultiline)
437 int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char(
'\t'));
438 int locationLength = location.length == 0 ? 1 : location.length;
439 m_output.write(QString::fromLatin1(
" ").repeated(issueLocationWithContext.beforeText().size()
441 + QString::fromLatin1(
"\t").repeated(tabCount)
442 + QString::fromLatin1(
"^").repeated(locationLength) + QLatin1Char(
'\n'));
445void QQmlJSLogger::printFix(
const QQmlJSFixSuggestion &fixItem)
447 const QString currentFileAbsPath = m_filePath;
448 QString code = m_code;
450 m_output.writePrefixedMessage(fixItem.description(), QtInfoMsg);
452 if (!fixItem.location().isValid())
455 const QString filename = fixItem.filename();
456 if (filename == currentFile) {
458 }
else if (filename.isEmpty() || filename == currentFileAbsPath) {
461 QFile file(filename);
462 const bool success = file.open(QFile::ReadOnly);
464 code = QString::fromUtf8(file.readAll());
465 currentFile = filename;
468 IssueLocationWithContext issueLocationWithContext { code, fixItem.location() };
470 if (
const QStringView beforeText = issueLocationWithContext.beforeText();
471 !beforeText.isEmpty()) {
472 m_output.write(beforeText);
476 const QString replacement = fixItem.replacement();
477 QStringView replacementString = replacement.isEmpty()
478 ? issueLocationWithContext.issueText()
482 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
484 if (!replacementString.isEmpty())
485 m_output.write(replacementString, QtDebugMsg);
486 m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
488 int tabCount = issueLocationWithContext.beforeText().count(u'\t');
491 if (!replacementString.contains(u'\n')) {
492 m_output.write(u" "_s.repeated(
493 issueLocationWithContext.beforeText().size() - tabCount)
494 + u"\t"_s.repeated(tabCount)
495 + u"^"_s.repeated(replacement.size()) + u'\n');
499QQmlJSFixSuggestion::QQmlJSFixSuggestion(
const QString &description,
500 const QQmlJS::SourceLocation &location,
501 const QString &replacement)
502 : 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)