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
48#define QMLLINT_BUILTIN_CATEGORIES
49 X(qmlAccessSingleton, "access-singleton-via-object", "AccessSingletonViaObject",
50 "Warn if a singleton is accessed via an object", Warning, NonEssential)
51 X(qmlAliasCycle, "alias-cycle", "AliasCycle", "Warn about alias cycles", Warning, NonEssential)
52 X(qmlAssignmentInCondition, "assignment-in-condition", "AssignmentInCondition",
53 "Warn about using assignment in conditions.", Warning, NonEssential)
54 X(qmlAttachedPropertyReuse, "attached-property-reuse", "AttachedPropertyReuse",
55 "Warn if attached types from parent components aren't reused. This is handled by the "
56 "QtQuick lint plugin. Use Quick.AttachedPropertyReuse instead.",
57 Disable, NonEssential)
58 X(qmlBlockScopeVarDeclaration, "block-scope-var-declaration", "BlockScopeVarDeclaration",
59 "Warn if a variable is declared with var inside a block scope", Warning, NonEssential)
60 X(qmlComma, "comma", "Comma", "Warn about using comma expressions.", Warning, NonEssential)
61 X(qmlCompiler, "compiler", "CompilerWarnings", "Warn about compiler issues", Disable,
63 X(qmlComponentChildrenCount, "component-children-count", "ComponentChildrenCount",
64 "Warn about Components that don't have exactly one child", Warning, NonEssential)
65 X(qmlConfusingExpressionStatement, "confusing-expression-statement",
66 "ConfusingExpressionStatement",
67 "Warn about expression statement that has no obvious effect.", Warning, NonEssential)
68 X(qmlConfusingMinuses, "confusing-minuses", "ConfusingMinuses",
69 "Warn about confusing minuses.", Warning, NonEssential)
70 X(qmlConfusingPluses, "confusing-pluses", "ConfusingPluses",
71 "Warn about confusing pluses.", Warning, NonEssential)
72 X(qmlContextProperties, "context-properties", "ContextProperties",
73 "Warn about using context properties.", Warning, NonEssential)
74 X(qmlDeferredPropertyId, "deferred-property-id", "DeferredPropertyId",
75 "Warn about making deferred properties immediate by giving them an id.", Disable,
77 X(qmlEnumsAreNotTypes, "enums-are-not-types", "EnumsAreNotTypes",
78 "Warn about the use of enumerations as types.", Warning, NonEssential)
79 X(qmlEqualityTypeCoercion, "equality-type-coercion", "EqualityTypeCoercion",
80 "Warn about coercions due to usages of '==' and '!='", Warning, NonEssential)
81 X(qmlDeprecated, "deprecated", "Deprecated", "Warn about deprecated properties and types",
82 Warning, NonEssential)
83 X(qmlDuplicateEnumEntries, "duplicate-enum-entries", "DuplicateEnumEntries",
84 "Warn about duplicate enum entries", Warning, NonEssential)
85 X(qmlDuplicateImport, "duplicate-import", "DuplicateImport", "Warn about duplicate imports",
86 Warning, NonEssential)
87 X(qmlDuplicateInlineComponent, "duplicate-inline-component", "DuplicateInlineComponent",
88 "Warn about duplicate inline components", Warning, NonEssential)
89 X(qmlDuplicatePropertyBinding, "duplicate-property-binding", "DuplicatePropertyBinding",
90 "Warn about duplicate property bindings", Warning, NonEssential)
91 X(qmlDuplicatedName, "duplicated-name", "DuplicatedName",
92 "Warn about duplicated property/signal names", Warning, NonEssential)
93 X(qmlEnumEntryMatchesEnum, "enum-entry-matches-enum", "EnumEntryMatchesEnum",
94 "Warn about enum entries named the same as the enum itself", Warning, NonEssential)
95 X(qmlEnumKeyCase, "enum-key-case", "EnumKeyCase", "Warn about lowercase enum keys", Warning,
97 X(qmlEval, "eval", "Eval", "Warn about uses of eval()", Warning, NonEssential)
98 X(qmlFunctionUsedBeforeDeclaration, "function-used-before-declaration",
99 "FunctionUsedBeforeDeclaration", "Warn if a function is used before declaration",
100 Disable, NonEssential)
101 X(qmlIdShadowsMember, "id-shadows-member", "IdShadowsMember",
102 "Warn about ids potentially shadowing members", Warning, NonEssential)
103 X(qmlImport, "import", "ImportFailure", "Warn about failing imports and deprecated qmltypes",
104 Warning, NonEssential)
105 X(qmlImportFileSelector, "import-file-selector", "ImportFileSelector",
106 "Warn about encountered file selectors during import", Disable, NonEssential)
107 X(qmlIncompatibleType, "incompatible-type", "IncompatibleType",
108 "Warn about incompatible types", Warning, NonEssential)
109 X(qmlInheritanceCycle, "inheritance-cycle", "InheritanceCycle",
110 "Warn about inheritance cycles", Warning, NonEssential)
111 X(qmlInlineComponentEnums, "inline-component-enums", "InlineComponentEnums",
112 "Warn about enum declarations inside inline components", Warning, NonEssential)
113 X(qmlInvalidLintDirective, "invalid-lint-directive", "InvalidLintDirective",
114 "Warn if an invalid qmllint comment is found", Warning, NonEssential)
115 X(qmlLiteralConstructor, "literal-constructor", "LiteralConstructor",
116 "Warn about using literal constructors, like Boolean or String for example.", Warning,
118 X(qmlMaxWarningsExceeded, "max-warnings-exceeded", "MaxWarningsExceeded",
119 "Warn when exceeding the allowed maximum number of warnings.", Error, Essential)
120 X(qmlMissingEnumEntry, "missing-enum-entry", "MissingEnumEntry",
121 "Warn about using missing enum values.", Warning, NonEssential)
122 X(qmlMissingProperty, "missing-property", "MissingProperty", "Warn about missing properties",
123 Warning, NonEssential)
124 X(qmlMissingType, "missing-type", "MissingType", "Warn about missing types", Warning,
126 X(qmlMultilineStrings, "multiline-strings", "MultilineStrings",
127 "Warn about multiline strings", Info, NonEssential)
128 X(qmlNonListProperty, "non-list-property", "NonListProperty",
129 "Warn about non-list properties", Warning, NonEssential)
130 X(qmlNonRootEnums, "non-root-enum", "NonRootEnum",
131 "Warn about enums defined outside the root component", Warning, NonEssential)
132 X(qmlPropertyOverride, "property-override", "PropertyOverride",
133 "Warn about wrongly overriding properties from a base class", Warning, NonEssential)
134 X(qmlUnterminatedCase, "unterminated-case", "UnterminatedCase", "Warn about non-empty case "
135 "blocks that are not terminated by control flow or by a fallthrough comment", Warning,
137 X(qmlPreferNonVarProperties, "prefer-non-var-properties", "PreferNonVarProperties",
138 "Warn about var properties that could use a more specific type", Warning, NonEssential)
139 X(qmlPrefixedImportType, "prefixed-import-type", "PrefixedImportType",
140 "Warn about prefixed import types", Warning, NonEssential)
141 X(qmlReadOnlyProperty, "read-only-property", "ReadOnlyProperty",
142 "Warn about writing to read-only properties", Warning, NonEssential)
143 X(qmlRecursionDepthErrors, "recursion-depth-errors", "", "", Warning, NonEssential)
144 X(qmlRedundantOptionalChaining, "redundant-optional-chaining", "RedundantOptionalChaining",
145 "Warn about optional chaining on non-voidable and non-nullable base", Warning, NonEssential)
146 X(qmlRenamedType, "renamed-type", "RenamedType",
147 "Warn when renamed types refer to themselves using their unrenamed name", Warning,
149 X(qmlRequired, "required", "RequiredProperty", "Warn about required properties", Warning,
151 X(qmlShadow, "shadow", "Shadow", "Warn about shadowing attributes from a base class", Disable,
153 X(qmlSignalHandlerNameClash, "signal-handler-name-clash", "SignalHandlerNameClash",
154 "Warn about property names that clash with signal handler names", Warning, NonEssential)
155 X(qmlSignalParameters, "signal-handler-parameters", "BadSignalHandlerParameters",
156 "Warn about bad signal handler parameters", Warning, NonEssential)
157 X(qmlStalePropertyRead, "stale-property-read", "StalePropertyRead",
158 "Warn about bindings reading non-constant and non-notifiable properties", Warning,
160 X(qmlSyntax, "syntax", "Syntax", "Syntax errors", Warning, Essential)
161 X(qmlSyntaxDuplicateIds, "syntax.duplicate-ids", "", "ID duplication", Error, NonEssential)
162 X(qmlSyntaxIdQuotation, "syntax.id-quotation", "", "ID quotation", Warning, NonEssential)
163 X(qmlTypeInstantiatedRecursively, "type-instantiated-recursively",
164 "TypeInstantiatedRecursively", "Warn when types are instantiated recursively", Warning,
166 X(qmlTopLevelComponent, "top-level-component", "TopLevelComponent",
167 "Warn if a top level Component is encountered", Warning, NonEssential)
168 X(qmlUncreatableType, "uncreatable-type", "UncreatableType",
169 "Warn if uncreatable types are created", Warning, NonEssential)
170 X(qmlUnintentionalEmptyBlock, "unintentional-empty-block", "UnintentionalEmptyBlock",
171 "Warn about bindings that contain only an empty block", Warning, NonEssential)
172 X(qmlUnqualified, "unqualified", "UnqualifiedAccess",
173 "Warn about unqualified identifiers and how to fix them", Warning, NonEssential)
174 X(qmlUnreachableCode, "unreachable-code", "UnreachableCode", "Warn about unreachable code.",
175 Warning, NonEssential)
176 X(qmlUnresolvedAlias, "unresolved-alias", "UnresolvedAlias", "Warn about unresolved aliases",
177 Warning, NonEssential)
178 X(qmlUnresolvedType, "unresolved-type", "UnresolvedType", "Warn about unresolved types",
179 Warning, NonEssential)
180 X(qmlUnusedImports, "unused-imports", "UnusedImports", "Warn about unused imports", Info,
182 X(qmlUseProperFunction, "use-proper-function", "UseProperFunction",
183 "Warn if var is used for storing functions", Disable, NonEssential)
184 X(qmlVarUsedBeforeDeclaration, "var-used-before-declaration", "VarUsedBeforeDeclaration",
185 "Warn if a variable is used before declaration", Warning, NonEssential)
186 X(qmlVoid, "void", "Void", "Warn about void expressions.", Disable, NonEssential)
187 X(qmlWith, "with", "WithStatement",
188 "Warn about with statements as they can cause NonEssential "
189 "positives when checking for unqualified access", Warning, NonEssential)
193#define X(category, name, setting, description, severity, essential)
194 const QQmlSA::LoggerWarningId category{ name };
199#define X(category, name, setting, description, severity, essential) ++i;
203constexpr bool isUnique(
const std::array<std::string_view, numCategories>& fields) {
204 for (std::size_t i = 0; i < fields.size(); ++i) {
205 for (std::size_t j = i + 1; j < fields.size(); ++j) {
206 if (!fields[i].empty() && fields[i] == fields[j]) {
214#define X(category, name, setting, description, severity, essential) std::string_view(name),
218#define X(category, name, setting, description, severity, essential) std::string_view(setting),
222#define X(category, name, setting, description, severity, essential) std::string_view(description),
227QQmlJSLogger::QQmlJSLogger()
229 static const QList<QQmlJS::LoggerCategory> cats = builtinCategories();
231 for (
const QQmlJS::LoggerCategory &category : cats)
232 registerCategory(category);
235 m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
236 m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground);
237 m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
238 m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground);
241const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::builtinCategories()
243 static const QList<QQmlJS::LoggerCategory> cats = {
244#define X(category, name, setting, description, severity, essential)
245 QQmlJS::LoggerCategory{ name##_L1, setting##_L1, description##_L1, QQmlJS::WarningSeverity::severity, QQmlJS::LoggerCategory::essential },
253bool QQmlJSFixSuggestion::operator==(
const QQmlJSFixSuggestion &other)
const
255 return m_location == other.m_location && m_description == other.m_description
256 && m_documentEdits == other.m_documentEdits && m_filename == other.m_filename
257 && m_autoApplicable == other.m_autoApplicable;
260bool QQmlJSFixSuggestion::operator!=(
const QQmlJSFixSuggestion &other)
const
262 return !(*
this == other);
265QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories()
const
267 return m_categories.values();
270void QQmlJSLogger::registerCategory(
const QQmlJS::LoggerCategory &category)
272 if (m_categories.contains(category.name())) {
273 qWarning() <<
"Trying to re-register existing logger category" << category.name();
277 m_categorySeverities[category.name()] = category.severity();
278 m_categories.insert(category.name(), category);
283 static QHash<QtMsgType,
int> level = { { QtDebugMsg, 0 },
286 { QtCriticalMsg, 3 },
288 return level[a] < level[b];
291void QQmlJSLogger::log(Message &&diagMsg,
bool showContext,
bool showFileName)
293 Q_ASSERT(m_categorySeverities.contains(diagMsg.id.toString()));
295 if (categorySeverity(diagMsg.id) == QQmlJS::WarningSeverity::Disable || isDisabled())
300 if (diagMsg.loc.isValid()
301 && m_ignoredWarnings[diagMsg.lineForDisabling()].contains(diagMsg.id.toString())) {
306 if (!m_filePath.isEmpty() && showFileName)
307 prefix = m_filePath + QStringLiteral(
":");
309 if (diagMsg.loc.isValid())
310 prefix += QStringLiteral(
"%1:%2: ").arg(diagMsg.loc.startLine).arg(diagMsg.loc.startColumn);
311 else if (!prefix.isEmpty())
312 prefix += QStringLiteral(
": ");
316 diagMsg.type = std::clamp(diagMsg.type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
320 const QString categoryName = diagMsg.id.toString();
321 const QString categoryLink = m_output.linkify(
322 "https://doc.qt.io/qt-6/qmllint-warnings-and-errors-%1.html"_L1.arg(categoryName),
323 "[%1]"_L1.arg(categoryName));
324 m_output.writePrefixedMessage(u"%1%2 %3"_s.arg(prefix, diagMsg.message, categoryLink),
327 if (diagMsg.loc.length > 0 && !m_code.isEmpty() && showContext)
328 printContext(diagMsg.loc);
330 if (diagMsg.fixSuggestion.has_value())
331 printFix(diagMsg.fixSuggestion.value());
333 if (m_inTransaction) {
334 m_pendingMessages.push_back(std::move(diagMsg));
336 countMessage(diagMsg);
337 m_currentFunctionMessages.push_back(std::move(diagMsg));
340 if (!m_inTransaction && !m_manualFlush)
341 m_output.flushBuffer();
344void QQmlJSLogger::countMessage(
const Message &message)
346 switch (message.type) {
358void QQmlJSLogger::processMessages(QSpan<
const QQmlJS::DiagnosticMessage> messages,
359 QQmlJS::LoggerWarningId id,
360 const QQmlJS::SourceLocation &sourceLocation)
362 if (messages.isEmpty() || categorySeverity(id) == QQmlJS::WarningSeverity::Disable || isDisabled())
365 m_output.write(QStringLiteral(
"---\n"));
369 for (
const QQmlJS::DiagnosticMessage &message : messages)
370 log(message.message, id, sourceLocation,
false,
false);
372 m_output.write(QStringLiteral(
"---\n\n"));
375void QQmlJSLogger::finalizeFunction()
377 Q_ASSERT(!m_inTransaction);
378 m_archivedMessages.append(std::exchange(m_currentFunctionMessages, {}));
379 m_hasCompileError =
false;
383
384
385
386
387
388
389
390
391
392
393void QQmlJSLogger::startTransaction()
395 Q_ASSERT(!m_inTransaction);
396 m_transactionOutputMark = m_output.bufferSize();
397 m_inTransaction =
true;
401
402
403
404
405void QQmlJSLogger::commit()
407 Q_ASSERT(m_inTransaction);
408 for (
const Message &message : std::as_const(m_pendingMessages))
409 countMessage(message);
411 m_currentFunctionMessages.append(std::exchange(m_pendingMessages, {}));
412 m_hasCompileError = m_hasCompileError || std::exchange(m_hasPendingCompileError,
false);
414 m_output.flushBuffer();
415 m_inTransaction =
false;
419
420
421
422
423void QQmlJSLogger::rollback()
425 Q_ASSERT(m_inTransaction);
426 m_pendingMessages.clear();
427 m_hasPendingCompileError =
false;
428 m_output.truncateBuffer(m_transactionOutputMark);
429 m_inTransaction =
false;
432void QQmlJSLogger::printContext(
const QQmlJS::SourceLocation &location)
434 QString code = m_code;
436 IssueLocationWithContext issueLocationWithContext { code, location };
437 if (
const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
438 m_output.write(beforeText);
440 bool locationMultiline = issueLocationWithContext.issueText().contains(QLatin1Char(
'\n'));
442 if (!issueLocationWithContext.issueText().isEmpty())
443 m_output.write(issueLocationWithContext.issueText().toString(), QtCriticalMsg);
444 m_output.write(issueLocationWithContext.afterText().toString() + QLatin1Char(
'\n'));
447 if (locationMultiline)
450 int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char(
'\t'));
451 int locationLength = location.length == 0 ? 1 : location.length;
452 m_output.write(QString::fromLatin1(
" ").repeated(issueLocationWithContext.beforeText().size()
454 + QString::fromLatin1(
"\t").repeated(tabCount)
455 + QString::fromLatin1(
"^").repeated(locationLength) + QLatin1Char(
'\n'));
458void QQmlJSLogger::printFix(
const QQmlJSFixSuggestion &fixItem)
460 const QString currentFileAbsPath = m_filePath;
461 QString code = m_code;
463 m_output.writePrefixedMessage(fixItem.description(), QtInfoMsg);
465 if (!fixItem.location().isValid())
468 const QString filename = fixItem.filename();
469 if (filename == currentFile) {
471 }
else if (filename.isEmpty() || filename == currentFileAbsPath) {
474 QFile file(filename);
475 const bool success = file.open(QFile::ReadOnly);
477 code = QString::fromUtf8(file.readAll());
478 currentFile = filename;
481 const auto &documentEdits = fixItem.documentEdits();
482 if (!documentEdits.empty()) {
483 m_output.write(documentEdits.size() == 1 ?
"Suggested change:\n"_L1
484 :
"Suggested changes:\n"_L1);
485 for (
const auto &documentEdit: documentEdits) {
486 if (!documentEdit.m_location.isValid())
489 IssueLocationWithContext issueLocationWithContext { code, documentEdit.m_location };
491 if (
const QStringView beforeText = issueLocationWithContext.beforeText();
492 !beforeText.isEmpty()) {
493 m_output.write(beforeText);
497 const QString replacement = documentEdit.m_replacement;
498 QStringView replacementString = replacement.isEmpty()
499 ? issueLocationWithContext.issueText()
503 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
505 if (!replacementString.isEmpty())
506 m_output.write(replacementString, QtDebugMsg);
507 m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
509 int tabCount = issueLocationWithContext.beforeText().count(u'\t');
512 if (!replacementString.contains(u'\n')) {
513 m_output.write(u" "_s.repeated(
514 issueLocationWithContext.beforeText().size() - tabCount)
515 + u"\t"_s.repeated(tabCount)
516 + u"^"_s.repeated(replacement.size()) + u'\n');
522QQmlJSFixSuggestion::QQmlJSFixSuggestion(
const QString &description,
523 const QQmlJS::SourceLocation &location,
524 const QQmlJSDocumentEdit &documentEdit)
525 : QQmlJSFixSuggestion(description, location, QList{ documentEdit })
529QQmlJSFixSuggestion::QQmlJSFixSuggestion(
const QString &description,
530 const QQmlJS::SourceLocation &location,
531 const QList<QQmlJSDocumentEdit> &documentEdits)
532 : m_description{ description }, m_location{ location }, m_documentEdits(documentEdits)
536void QQmlJSFixSuggestion::addDocumentEdit(
const QQmlJSDocumentEdit &documentEdit)
538 m_documentEdits.append(documentEdit);
541void QQmlJSFixSuggestion::setDocumentEdits(
const QList<QQmlJSDocumentEdit> &documentEdits)
543 m_documentEdits = documentEdits;
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)