Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljslogger.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
5#include <QtCore/qcompilerdetection.h>
6// GCC 11 thinks diagMsg.fixSuggestion.fixes.d.ptr is somehow uninitialized in
7// QList::emplaceBack(), probably called from QQmlJsLogger::log()
8// Ditto for GCC 12, but it emits a different warning
9QT_WARNING_PUSH
10QT_WARNING_DISABLE_GCC("-Wuninitialized")
11QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
12#include <QtCore/qlist.h>
13QT_WARNING_POP
14
15#include <private/qqmljslogger_p.h>
16#include <private/qqmlsa_p.h>
17
18#include <QtQmlCompiler/qqmljsloggingutils.h>
19
20#include <QtCore/qglobal.h>
21#include <QtCore/qfile.h>
22
23
24QT_BEGIN_NAMESPACE
25
26using namespace Qt::StringLiterals;
27
28/*
29Note: users are in full control of the severity level of a category. It is not possible to
30have a single category and use it as a warning in one context, and as an info message in
31another context.
32For that use case, create two distinct categories instead, and give them appropriate default
33warning levels.
34 */
35
36/* The X macro provides (in order):
37 category, name, setting, description, severity, essentiality
38 - category is the C++ variable name of the category
39 - name is the user visible category name, i.e. what what qmllint put in bewteen "[" and "]"
40 - setting name is the name that is used in the qmllint.ini file to configure the category
41 - description is a _short_ description of the category's use
42 - severity is the default warning level of the category (can be overriden by the user though)
43 - essentiality marks essential categories that are required for the proper function of qmllint and can't be disabled by the user
44 */
45
46// clang-format off
47// don't forget to forward-declare your logging category ID in qqmljsloggingutils.h!
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,
62 NonEssential)
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,
76 NonEssential)
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,
96 NonEssential)
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,
117 NonEssential)
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,
125 NonEssential)
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,
136 NonEssential)
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,
148 NonEssential)
149 X(qmlRequired, "required", "RequiredProperty", "Warn about required properties", Warning,
150 NonEssential)
151 X(qmlShadow, "shadow", "Shadow", "Warn about shadowing attributes from a base class", Disable,
152 NonEssential)
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,
159 NonEssential)
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,
165 NonEssential)
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,
181 NonEssential)
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)
190
191// clang-format on
192
193#define X(category, name, setting, description, severity, essential)
194 const QQmlSA::LoggerWarningId category{ name };
196#undef X
197
198
199#define X(category, name, setting, description, severity, essential) ++i;
200constexpr size_t numCategories = [] { size_t i = 0; QMLLINT_BUILTIN_CATEGORIES return i; }();
201#undef X
202
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]) {
207 return false;
208 }
209 }
210 }
211 return true;
212}
213
214#define X(category, name, setting, description, severity, essential) std::string_view(name),
215static_assert(isUnique(std::array{ QMLLINT_BUILTIN_CATEGORIES }), "Duplicate names found!");
216#undef X
217
218#define X(category, name, setting, description, severity, essential) std::string_view(setting),
219static_assert(isUnique(std::array{ QMLLINT_BUILTIN_CATEGORIES }), "Duplicate settings found!");
220#undef X
221
222#define X(category, name, setting, description, severity, essential) std::string_view(description),
223static_assert(isUnique(std::array{ QMLLINT_BUILTIN_CATEGORIES }), "Duplicate description found!");
224#undef X
225
226
227QQmlJSLogger::QQmlJSLogger()
228{
229 static const QList<QQmlJS::LoggerCategory> cats = builtinCategories();
230
231 for (const QQmlJS::LoggerCategory &category : cats)
232 registerCategory(category);
233
234 // setup color output
235 m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
236 m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground); // Yellow?
237 m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
238 m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground); // None?
239}
240
241const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::builtinCategories()
242{
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 },
247#undef X
248 };
249
250 return cats;
251}
252
253bool QQmlJSFixSuggestion::operator==(const QQmlJSFixSuggestion &other) const
254{
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;
258}
259
260bool QQmlJSFixSuggestion::operator!=(const QQmlJSFixSuggestion &other) const
261{
262 return !(*this == other);
263}
264
265QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories() const
266{
267 return m_categories.values();
268}
269
270void QQmlJSLogger::registerCategory(const QQmlJS::LoggerCategory &category)
271{
272 if (m_categories.contains(category.name())) {
273 qWarning() << "Trying to re-register existing logger category" << category.name();
274 return;
275 }
276
277 m_categorySeverities[category.name()] = category.severity();
278 m_categories.insert(category.name(), category);
279}
280
281static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
282{
283 static QHash<QtMsgType, int> level = { { QtDebugMsg, 0 },
284 { QtInfoMsg, 1 },
285 { QtWarningMsg, 2 },
286 { QtCriticalMsg, 3 },
287 { QtFatalMsg, 4 } };
288 return level[a] < level[b];
289}
290
291void QQmlJSLogger::log(Message &&diagMsg, bool showContext, bool showFileName)
292{
293 Q_ASSERT(m_categorySeverities.contains(diagMsg.id.toString()));
294
295 if (categorySeverity(diagMsg.id) == QQmlJS::WarningSeverity::Disable || isDisabled())
296 return;
297
298 // Note: assume \a type is the type we should prefer for logging
299
300 if (diagMsg.loc.isValid()
301 && m_ignoredWarnings[diagMsg.lineForDisabling()].contains(diagMsg.id.toString())) {
302 return;
303 }
304
305 QString prefix;
306 if (!m_filePath.isEmpty() && showFileName)
307 prefix = m_filePath + QStringLiteral(":");
308
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(": "); // produce double colon for Qt Creator's issues pane
313
314 // Note: we do the clamping to [Info, Critical] range since our logger only
315 // supports 3 categories
316 diagMsg.type = std::clamp(diagMsg.type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
317
318 // Note: since we clamped our \a type, the output message is not printed
319 // exactly like it was requested, bear with us
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),
325 diagMsg.type);
326
327 if (diagMsg.loc.length > 0 && !m_code.isEmpty() && showContext)
328 printContext(diagMsg.loc);
329
330 if (diagMsg.fixSuggestion.has_value())
331 printFix(diagMsg.fixSuggestion.value());
332
333 if (m_inTransaction) {
334 m_pendingMessages.push_back(std::move(diagMsg));
335 } else {
336 countMessage(diagMsg);
337 m_currentFunctionMessages.push_back(std::move(diagMsg));
338 }
339
340 if (!m_inTransaction && !m_manualFlush)
341 m_output.flushBuffer();
342}
343
344void QQmlJSLogger::countMessage(const Message &message)
345{
346 switch (message.type) {
347 case QtWarningMsg:
348 ++m_numWarnings;
349 break;
350 case QtCriticalMsg:
351 ++m_numErrors;
352 break;
353 default:
354 break;
355 }
356}
357
358void QQmlJSLogger::processMessages(QSpan<const QQmlJS::DiagnosticMessage> messages,
359 QQmlJS::LoggerWarningId id,
360 const QQmlJS::SourceLocation &sourceLocation)
361{
362 if (messages.isEmpty() || categorySeverity(id) == QQmlJS::WarningSeverity::Disable || isDisabled())
363 return;
364
365 m_output.write(QStringLiteral("---\n"));
366
367 // TODO: we should instead respect message's category here (potentially, it
368 // should hold a category instead of type)
369 for (const QQmlJS::DiagnosticMessage &message : messages)
370 log(message.message, id, sourceLocation, false, false);
371
372 m_output.write(QStringLiteral("---\n\n"));
373}
374
375void QQmlJSLogger::finalizeFunction()
376{
377 Q_ASSERT(!m_inTransaction);
378 m_archivedMessages.append(std::exchange(m_currentFunctionMessages, {}));
379 m_hasCompileError = false;
380}
381
382/*!
383 \internal
384 Starts a transaction for a compile pass. This buffers all messages until the
385 transaction completes. If you commit the transaction, the messages are printed
386 and added to the list of committed messages. If you roll it back, the logger
387 reverts to the state before the start of the transaction.
388
389 This is useful for compile passes that potentially have to be repeated, such
390 as the type propagator. We don't want to see the same messages logged multiple
391 times.
392 */
393void QQmlJSLogger::startTransaction()
394{
395 Q_ASSERT(!m_inTransaction);
396 m_transactionOutputMark = m_output.bufferSize();
397 m_inTransaction = true;
398}
399
400/*!
401 \internal
402 Commit the current transaction. Print all pending messages, and add them to
403 the list of committed messages. Then, clear the transaction flag.
404 */
405void QQmlJSLogger::commit()
406{
407 Q_ASSERT(m_inTransaction);
408 for (const Message &message : std::as_const(m_pendingMessages))
409 countMessage(message);
410
411 m_currentFunctionMessages.append(std::exchange(m_pendingMessages, {}));
412 m_hasCompileError = m_hasCompileError || std::exchange(m_hasPendingCompileError, false);
413 if (!m_manualFlush)
414 m_output.flushBuffer();
415 m_inTransaction = false;
416}
417
418/*!
419 \internal
420 Roll back the current transaction and revert the logger to the state before
421 it was started.
422 */
423void QQmlJSLogger::rollback()
424{
425 Q_ASSERT(m_inTransaction);
426 m_pendingMessages.clear();
427 m_hasPendingCompileError = false;
428 m_output.truncateBuffer(m_transactionOutputMark);
429 m_inTransaction = false;
430}
431
432void QQmlJSLogger::printContext(const QQmlJS::SourceLocation &location)
433{
434 QString code = m_code;
435
436 IssueLocationWithContext issueLocationWithContext { code, location };
437 if (const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
438 m_output.write(beforeText);
439
440 bool locationMultiline = issueLocationWithContext.issueText().contains(QLatin1Char('\n'));
441
442 if (!issueLocationWithContext.issueText().isEmpty())
443 m_output.write(issueLocationWithContext.issueText().toString(), QtCriticalMsg);
444 m_output.write(issueLocationWithContext.afterText().toString() + QLatin1Char('\n'));
445
446 // Do not draw location indicator for multiline locations
447 if (locationMultiline)
448 return;
449
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()
453 - tabCount)
454 + QString::fromLatin1("\t").repeated(tabCount)
455 + QString::fromLatin1("^").repeated(locationLength) + QLatin1Char('\n'));
456}
457
458void QQmlJSLogger::printFix(const QQmlJSFixSuggestion &fixItem)
459{
460 const QString currentFileAbsPath = m_filePath;
461 QString code = m_code;
462 QString currentFile;
463 m_output.writePrefixedMessage(fixItem.description(), QtInfoMsg);
464
465 if (!fixItem.location().isValid())
466 return;
467
468 const QString filename = fixItem.filename();
469 if (filename == currentFile) {
470 // Nothing to do in this case, we've already read the code
471 } else if (filename.isEmpty() || filename == currentFileAbsPath) {
472 code = m_code;
473 } else {
474 QFile file(filename);
475 const bool success = file.open(QFile::ReadOnly);
476 Q_ASSERT(success);
477 code = QString::fromUtf8(file.readAll());
478 currentFile = filename;
479 }
480
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())
487 continue;
488
489 IssueLocationWithContext issueLocationWithContext { code, documentEdit.m_location };
490
491 if (const QStringView beforeText = issueLocationWithContext.beforeText();
492 !beforeText.isEmpty()) {
493 m_output.write(beforeText);
494 }
495
496 // The replacement string can be empty if we're only pointing something out to the user
497 const QString replacement = documentEdit.m_replacement;
498 QStringView replacementString = replacement.isEmpty()
499 ? issueLocationWithContext.issueText()
500 : replacement;
501
502 // But if there's nothing to change it cannot be auto-applied
503 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
504
505 if (!replacementString.isEmpty())
506 m_output.write(replacementString, QtDebugMsg);
507 m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
508
509 int tabCount = issueLocationWithContext.beforeText().count(u'\t');
510
511 // Do not draw location indicator for multiline replacement strings
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');
517 }
518 }
519 }
520}
521
522QQmlJSFixSuggestion::QQmlJSFixSuggestion(const QString &description,
523 const QQmlJS::SourceLocation &location,
524 const QQmlJSDocumentEdit &documentEdit)
525 : QQmlJSFixSuggestion(description, location, QList{ documentEdit })
526{
527}
528
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)
533{
534}
535
536void QQmlJSFixSuggestion::addDocumentEdit(const QQmlJSDocumentEdit &documentEdit)
537{
538 m_documentEdits.append(documentEdit);
539}
540
541void QQmlJSFixSuggestion::setDocumentEdits(const QList<QQmlJSDocumentEdit> &documentEdits)
542{
543 m_documentEdits = documentEdits;
544}
545
546QT_END_NAMESPACE
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)