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(qmlMissingEnumEntry, "missing-enum-entry", "MissingEnumEntry",
119 "Warn about using missing enum values.", Warning, NonEssential)
120 X(qmlMissingProperty, "missing-property", "MissingProperty", "Warn about missing properties",
121 Warning, NonEssential)
122 X(qmlMissingType, "missing-type", "MissingType", "Warn about missing types", Warning,
123 NonEssential)
124 X(qmlMultilineStrings, "multiline-strings", "MultilineStrings",
125 "Warn about multiline strings", Info, NonEssential)
126 X(qmlNonListProperty, "non-list-property", "NonListProperty",
127 "Warn about non-list properties", Warning, NonEssential)
128 X(qmlNonRootEnums, "non-root-enum", "NonRootEnum",
129 "Warn about enums defined outside the root component", Warning, NonEssential)
130 X(qmlPropertyOverride, "property-override", "PropertyOverride",
131 "Warn about wrongly overriding properties from a base class", Warning, NonEssential)
132 X(qmlUnterminatedCase, "unterminated-case", "UnterminatedCase", "Warn about non-empty case "
133 "blocks that are not terminated by control flow or by a fallthrough comment", Warning,
134 NonEssential)
135 X(qmlPreferNonVarProperties, "prefer-non-var-properties", "PreferNonVarProperties",
136 "Warn about var properties that could use a more specific type", Warning, NonEssential)
137 X(qmlPrefixedImportType, "prefixed-import-type", "PrefixedImportType",
138 "Warn about prefixed import types", Warning, NonEssential)
139 X(qmlReadOnlyProperty, "read-only-property", "ReadOnlyProperty",
140 "Warn about writing to read-only properties", Warning, NonEssential)
141 X(qmlRecursionDepthErrors, "recursion-depth-errors", "", "", Warning, NonEssential)
142 X(qmlRedundantOptionalChaining, "redundant-optional-chaining", "RedundantOptionalChaining",
143 "Warn about optional chaining on non-voidable and non-nullable base", Warning, NonEssential)
144 X(qmlRenamedType, "renamed-type", "RenamedType",
145 "Warn when renamed types refer to themselves using their unrenamed name", Warning,
146 NonEssential)
147 X(qmlRequired, "required", "RequiredProperty", "Warn about required properties", Warning,
148 NonEssential)
149 X(qmlShadow, "shadow", "Shadow", "Warn about shadowing attributes from a base class", Disable,
150 NonEssential)
151 X(qmlSignalParameters, "signal-handler-parameters", "BadSignalHandlerParameters",
152 "Warn about bad signal handler parameters", Warning, NonEssential)
153 X(qmlStalePropertyRead, "stale-property-read", "StalePropertyRead",
154 "Warn about bindings reading non-constant and non-notifiable properties", Warning,
155 NonEssential)
156 X(qmlSyntax, "syntax", "Syntax", "Syntax errors", Warning, Essential)
157 X(qmlSyntaxDuplicateIds, "syntax.duplicate-ids", "", "ID duplication", Error, NonEssential)
158 X(qmlSyntaxIdQuotation, "syntax.id-quotation", "", "ID quotation", Warning, NonEssential)
159 X(qmlTypeInstantiatedRecursively, "type-instantiated-recursively",
160 "TypeInstantiatedRecursively", "Warn when types are instantiated recursively", Warning,
161 NonEssential)
162 X(qmlTopLevelComponent, "top-level-component", "TopLevelComponent",
163 "Warn if a top level Component is encountered", Warning, NonEssential)
164 X(qmlUncreatableType, "uncreatable-type", "UncreatableType",
165 "Warn if uncreatable types are created", Warning, NonEssential)
166 X(qmlUnintentionalEmptyBlock, "unintentional-empty-block", "UnintentionalEmptyBlock",
167 "Warn about bindings that contain only an empty block", Warning, NonEssential)
168 X(qmlUnqualified, "unqualified", "UnqualifiedAccess",
169 "Warn about unqualified identifiers and how to fix them", Warning, NonEssential)
170 X(qmlUnreachableCode, "unreachable-code", "UnreachableCode", "Warn about unreachable code.",
171 Warning, NonEssential)
172 X(qmlUnresolvedAlias, "unresolved-alias", "UnresolvedAlias", "Warn about unresolved aliases",
173 Warning, NonEssential)
174 X(qmlUnresolvedType, "unresolved-type", "UnresolvedType", "Warn about unresolved types",
175 Warning, NonEssential)
176 X(qmlUnusedImports, "unused-imports", "UnusedImports", "Warn about unused imports", Info,
177 NonEssential)
178 X(qmlUseProperFunction, "use-proper-function", "UseProperFunction",
179 "Warn if var is used for storing functions", Disable, NonEssential)
180 X(qmlVarUsedBeforeDeclaration, "var-used-before-declaration", "VarUsedBeforeDeclaration",
181 "Warn if a variable is used before declaration", Warning, NonEssential)
182 X(qmlVoid, "void", "Void", "Warn about void expressions.", Disable, NonEssential)
183 X(qmlWith, "with", "WithStatement",
184 "Warn about with statements as they can cause NonEssential "
185 "positives when checking for unqualified access", Warning, NonEssential)
186
187// clang-format on
188
189#define X(category, name, setting, description, severity, essential)
190 const QQmlSA::LoggerWarningId category{ name };
192#undef X
193
194
195#define X(category, name, setting, description, severity, essential) ++i;
196constexpr size_t numCategories = [] { size_t i = 0; QMLLINT_BUILTIN_CATEGORIES return i; }();
197#undef X
198
199constexpr bool isUnique(const std::array<std::string_view, numCategories>& fields) {
200 for (std::size_t i = 0; i < fields.size(); ++i) {
201 for (std::size_t j = i + 1; j < fields.size(); ++j) {
202 if (!fields[i].empty() && fields[i] == fields[j]) {
203 return false;
204 }
205 }
206 }
207 return true;
208}
209
210#define X(category, name, setting, description, severity, essential) std::string_view(name),
211static_assert(isUnique(std::array{ QMLLINT_BUILTIN_CATEGORIES }), "Duplicate names found!");
212#undef X
213
214#define X(category, name, setting, description, severity, essential) std::string_view(setting),
215static_assert(isUnique(std::array{ QMLLINT_BUILTIN_CATEGORIES }), "Duplicate settings found!");
216#undef X
217
218#define X(category, name, setting, description, severity, essential) std::string_view(description),
219static_assert(isUnique(std::array{ QMLLINT_BUILTIN_CATEGORIES }), "Duplicate description found!");
220#undef X
221
222
223QQmlJSLogger::QQmlJSLogger()
224{
225 static const QList<QQmlJS::LoggerCategory> cats = builtinCategories();
226
227 for (const QQmlJS::LoggerCategory &category : cats)
228 registerCategory(category);
229
230 // setup color output
231 m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
232 m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground); // Yellow?
233 m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
234 m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground); // None?
235}
236
237const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::builtinCategories()
238{
239 static const QList<QQmlJS::LoggerCategory> cats = {
240#define X(category, name, setting, description, severity, essential)
241 QQmlJS::LoggerCategory{ name##_L1, setting##_L1, description##_L1, QQmlJS::WarningSeverity::severity, QQmlJS::LoggerCategory::essential },
243#undef X
244 };
245
246 return cats;
247}
248
249bool QQmlJSFixSuggestion::operator==(const QQmlJSFixSuggestion &other) const
250{
251 return m_location == other.m_location && m_description == other.m_description
252 && m_documentEdits == other.m_documentEdits && m_filename == other.m_filename
253 && m_autoApplicable == other.m_autoApplicable;
254}
255
256bool QQmlJSFixSuggestion::operator!=(const QQmlJSFixSuggestion &other) const
257{
258 return !(*this == other);
259}
260
261QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories() const
262{
263 return m_categories.values();
264}
265
266void QQmlJSLogger::registerCategory(const QQmlJS::LoggerCategory &category)
267{
268 if (m_categories.contains(category.name())) {
269 qWarning() << "Trying to re-register existing logger category" << category.name();
270 return;
271 }
272
273 m_categorySeverities[category.name()] = category.severity();
274 m_categories.insert(category.name(), category);
275}
276
277static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
278{
279 static QHash<QtMsgType, int> level = { { QtDebugMsg, 0 },
280 { QtInfoMsg, 1 },
281 { QtWarningMsg, 2 },
282 { QtCriticalMsg, 3 },
283 { QtFatalMsg, 4 } };
284 return level[a] < level[b];
285}
286
287void QQmlJSLogger::log(Message &&diagMsg, bool showContext, bool showFileName)
288{
289 Q_ASSERT(m_categorySeverities.contains(diagMsg.id.toString()));
290
291 if (categorySeverity(diagMsg.id) == QQmlJS::WarningSeverity::Disable || isDisabled())
292 return;
293
294 // Note: assume \a type is the type we should prefer for logging
295
296 if (diagMsg.loc.isValid()
297 && m_ignoredWarnings[diagMsg.lineForDisabling()].contains(diagMsg.id.toString())) {
298 return;
299 }
300
301 QString prefix;
302 if (!m_filePath.isEmpty() && showFileName)
303 prefix = m_filePath + QStringLiteral(":");
304
305 if (diagMsg.loc.isValid())
306 prefix += QStringLiteral("%1:%2: ").arg(diagMsg.loc.startLine).arg(diagMsg.loc.startColumn);
307 else if (!prefix.isEmpty())
308 prefix += QStringLiteral(": "); // produce double colon for Qt Creator's issues pane
309
310 // Note: we do the clamping to [Info, Critical] range since our logger only
311 // supports 3 categories
312 diagMsg.type = std::clamp(diagMsg.type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
313
314 // Note: since we clamped our \a type, the output message is not printed
315 // exactly like it was requested, bear with us
316 m_output.writePrefixedMessage(
317 u"%1%2 [%3]"_s.arg(prefix, diagMsg.message, diagMsg.id.toString()), diagMsg.type);
318
319 if (diagMsg.loc.length > 0 && !m_code.isEmpty() && showContext)
320 printContext(diagMsg.loc);
321
322 if (diagMsg.fixSuggestion.has_value())
323 printFix(diagMsg.fixSuggestion.value());
324
325 if (m_inTransaction) {
326 m_pendingMessages.push_back(std::move(diagMsg));
327 } else {
328 countMessage(diagMsg);
329 m_currentFunctionMessages.push_back(std::move(diagMsg));
330 }
331
332 if (!m_inTransaction && !m_manualFlush)
333 m_output.flushBuffer();
334}
335
336void QQmlJSLogger::countMessage(const Message &message)
337{
338 switch (message.type) {
339 case QtWarningMsg:
340 ++m_numWarnings;
341 break;
342 case QtCriticalMsg:
343 ++m_numErrors;
344 break;
345 default:
346 break;
347 }
348}
349
350void QQmlJSLogger::processMessages(QSpan<const QQmlJS::DiagnosticMessage> messages,
351 QQmlJS::LoggerWarningId id,
352 const QQmlJS::SourceLocation &sourceLocation)
353{
354 if (messages.isEmpty() || categorySeverity(id) == QQmlJS::WarningSeverity::Disable || isDisabled())
355 return;
356
357 m_output.write(QStringLiteral("---\n"));
358
359 // TODO: we should instead respect message's category here (potentially, it
360 // should hold a category instead of type)
361 for (const QQmlJS::DiagnosticMessage &message : messages)
362 log(message.message, id, sourceLocation, false, false);
363
364 m_output.write(QStringLiteral("---\n\n"));
365}
366
367void QQmlJSLogger::finalizeFunction()
368{
369 Q_ASSERT(!m_inTransaction);
370 m_archivedMessages.append(std::exchange(m_currentFunctionMessages, {}));
371 m_hasCompileError = false;
372}
373
374/*!
375 \internal
376 Starts a transaction for a compile pass. This buffers all messages until the
377 transaction completes. If you commit the transaction, the messages are printed
378 and added to the list of committed messages. If you roll it back, the logger
379 reverts to the state before the start of the transaction.
380
381 This is useful for compile passes that potentially have to be repeated, such
382 as the type propagator. We don't want to see the same messages logged multiple
383 times.
384 */
385void QQmlJSLogger::startTransaction()
386{
387 Q_ASSERT(!m_inTransaction);
388 m_inTransaction = true;
389}
390
391/*!
392 \internal
393 Commit the current transaction. Print all pending messages, and add them to
394 the list of committed messages. Then, clear the transaction flag.
395 */
396void QQmlJSLogger::commit()
397{
398 Q_ASSERT(m_inTransaction);
399 for (const Message &message : std::as_const(m_pendingMessages))
400 countMessage(message);
401
402 m_currentFunctionMessages.append(std::exchange(m_pendingMessages, {}));
403 m_hasCompileError = m_hasCompileError || std::exchange(m_hasPendingCompileError, false);
404 if (!m_manualFlush)
405 m_output.flushBuffer();
406 m_inTransaction = false;
407}
408
409/*!
410 \internal
411 Roll back the current transaction and revert the logger to the state before
412 it was started.
413 */
414void QQmlJSLogger::rollback()
415{
416 Q_ASSERT(m_inTransaction);
417 m_pendingMessages.clear();
418 m_hasPendingCompileError = false;
419 m_output.discardBuffer();
420 m_inTransaction = false;
421}
422
423void QQmlJSLogger::printContext(const QQmlJS::SourceLocation &location)
424{
425 QString code = m_code;
426
427 IssueLocationWithContext issueLocationWithContext { code, location };
428 if (const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
429 m_output.write(beforeText);
430
431 bool locationMultiline = issueLocationWithContext.issueText().contains(QLatin1Char('\n'));
432
433 if (!issueLocationWithContext.issueText().isEmpty())
434 m_output.write(issueLocationWithContext.issueText().toString(), QtCriticalMsg);
435 m_output.write(issueLocationWithContext.afterText().toString() + QLatin1Char('\n'));
436
437 // Do not draw location indicator for multiline locations
438 if (locationMultiline)
439 return;
440
441 int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char('\t'));
442 int locationLength = location.length == 0 ? 1 : location.length;
443 m_output.write(QString::fromLatin1(" ").repeated(issueLocationWithContext.beforeText().size()
444 - tabCount)
445 + QString::fromLatin1("\t").repeated(tabCount)
446 + QString::fromLatin1("^").repeated(locationLength) + QLatin1Char('\n'));
447}
448
449void QQmlJSLogger::printFix(const QQmlJSFixSuggestion &fixItem)
450{
451 const QString currentFileAbsPath = m_filePath;
452 QString code = m_code;
453 QString currentFile;
454 m_output.writePrefixedMessage(fixItem.description(), QtInfoMsg);
455
456 if (!fixItem.location().isValid())
457 return;
458
459 const QString filename = fixItem.filename();
460 if (filename == currentFile) {
461 // Nothing to do in this case, we've already read the code
462 } else if (filename.isEmpty() || filename == currentFileAbsPath) {
463 code = m_code;
464 } else {
465 QFile file(filename);
466 const bool success = file.open(QFile::ReadOnly);
467 Q_ASSERT(success);
468 code = QString::fromUtf8(file.readAll());
469 currentFile = filename;
470 }
471
472 const auto &documentEdits = fixItem.documentEdits();
473 if (!documentEdits.empty()) {
474 m_output.write(documentEdits.size() == 1 ? "Suggested change:\n"_L1
475 : "Suggested changes:\n"_L1);
476 for (const auto &documentEdit: documentEdits) {
477 if (!documentEdit.m_location.isValid())
478 continue;
479
480 IssueLocationWithContext issueLocationWithContext { code, documentEdit.m_location };
481
482 if (const QStringView beforeText = issueLocationWithContext.beforeText();
483 !beforeText.isEmpty()) {
484 m_output.write(beforeText);
485 }
486
487 // The replacement string can be empty if we're only pointing something out to the user
488 const QString replacement = documentEdit.m_replacement;
489 QStringView replacementString = replacement.isEmpty()
490 ? issueLocationWithContext.issueText()
491 : replacement;
492
493 // But if there's nothing to change it cannot be auto-applied
494 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
495
496 if (!replacementString.isEmpty())
497 m_output.write(replacementString, QtDebugMsg);
498 m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
499
500 int tabCount = issueLocationWithContext.beforeText().count(u'\t');
501
502 // Do not draw location indicator for multiline replacement strings
503 if (!replacementString.contains(u'\n')) {
504 m_output.write(u" "_s.repeated(
505 issueLocationWithContext.beforeText().size() - tabCount)
506 + u"\t"_s.repeated(tabCount)
507 + u"^"_s.repeated(replacement.size()) + u'\n');
508 }
509 }
510 }
511}
512
513QQmlJSFixSuggestion::QQmlJSFixSuggestion(const QString &description,
514 const QQmlJS::SourceLocation &location,
515 const QQmlJSDocumentEdit &documentEdit)
516 : QQmlJSFixSuggestion(description, location, QList{ documentEdit })
517{
518}
519
520QQmlJSFixSuggestion::QQmlJSFixSuggestion(const QString &description,
521 const QQmlJS::SourceLocation &location,
522 const QList<QQmlJSDocumentEdit> &documentEdits)
523 : m_description{ description }, m_location{ location }, m_documentEdits(documentEdits)
524{
525}
526
527void QQmlJSFixSuggestion::addDocumentEdit(const QQmlJSDocumentEdit &documentEdit)
528{
529 m_documentEdits.append(documentEdit);
530}
531
532void QQmlJSFixSuggestion::setDocumentEdits(const QList<QQmlJSDocumentEdit> &documentEdits)
533{
534 m_documentEdits = documentEdits;
535}
536
537QT_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)