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
4#include <QtCore/qcompilerdetection.h>
5// GCC 11 thinks diagMsg.fixSuggestion.fixes.d.ptr is somehow uninitialized in
6// QList::emplaceBack(), probably called from QQmlJsLogger::log()
7// Ditto for GCC 12, but it emits a different warning
8QT_WARNING_PUSH
9QT_WARNING_DISABLE_GCC("-Wuninitialized")
10QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
11#include <QtCore/qlist.h>
12QT_WARNING_POP
13
14#include <private/qqmljslogger_p.h>
15#include <private/qqmlsa_p.h>
16
17#include <QtQmlCompiler/qqmljsloggingutils.h>
18
19#include <QtCore/qglobal.h>
20#include <QtCore/qfile.h>
21
22
23QT_BEGIN_NAMESPACE
24
25using namespace Qt::StringLiterals;
26
27// don't forget to forward-declare your logging category ID in qqmljsloggingutils.h!
28#define QMLLINT_DEFAULT_CATEGORIES \
29 X(qmlAccessSingleton, "access-singleton-via-object", "AccessSingletonViaObject", \
30 "Warn if a singleton is accessed via an object", QtWarningMsg, false, false) \
31 X(qmlAliasCycle, "alias-cycle", "AliasCycle", "Warn about alias cycles", QtWarningMsg, false, \
32 false) \
33 X(qmlAssignmentInCondition, "assignment-in-condition", "AssignmentInCondition", \
34 "Warn about using assignment in conditions.", QtWarningMsg, false, false) \
35 X(qmlAttachedPropertyReuse, "attached-property-reuse", "AttachedPropertyReuse", \
36 "Warn if attached types from parent components aren't reused. This is handled by the " \
37 "QtQuick lint plugin. Use Quick.AttachedPropertyReuse instead.", \
38 QtCriticalMsg, true, false) \
39 X(qmlComma, "comma", "Comma", "Warn about using comma expressions.", QtWarningMsg, false, \
40 false) \
41 X(qmlCompiler, "compiler", "CompilerWarnings", "Warn about compiler issues", QtWarningMsg, \
42 true, false) \
43 X(qmlComponentChildrenCount, "component-children-count", "ComponentChildrenCount", \
44 "Warn about Components that don't have exactly one child", QtWarningMsg, false, false) \
45 X(qmlConfusingExpressionStatement, "confusing-expression-statement", \
46 "ConfusingExpressionStatement", \
47 "Warn about expression statement that has no obvious effect.", QtWarningMsg, false, false) \
48 X(qmlConfusingMinuses, "confusing-minuses", "ConfusingMinuses", \
49 "Warn about confusing minuses.", QtWarningMsg, false, false) \
50 X(qmlConfusingPluses, "confusing-pluses", "ConfusingPluses", \
51 "Warn about confusing pluses.", QtWarningMsg, false, false) \
52 X(qmlContextProperties, "context-properties", "ContextProperties", \
53 "Warn about using context properties.", QtWarningMsg, false, false) \
54 X(qmlDeferredPropertyId, "deferred-property-id", "DeferredPropertyId", \
55 "Warn about making deferred properties immediate by giving them an id.", QtInfoMsg, true, \
56 true) \
57 X(qmlEnumsAreNotTypes, "enums-are-not-types", "EnumsAreNotTypes", \
58 "Warn about the use of enumerations as types.", QtWarningMsg, false, false) \
59 X(qmlEqualityTypeCoercion, "equality-type-coercion", "EqualityTypeCoercion", \
60 "Warn about coercions due to usages of '==' and '!='", QtWarningMsg, false, false) \
61 X(qmlDeprecated, "deprecated", "Deprecated", "Warn about deprecated properties and types", \
62 QtWarningMsg, false, false) \
63 X(qmlDuplicateEnumEntries, "duplicate-enum-entries", "DuplicateEnumEntries", \
64 "Warn about duplicate enum entries", QtWarningMsg, false, false) \
65 X(qmlDuplicateImport, "duplicate-import", "DuplicateImport", "Warn about duplicate imports", \
66 QtWarningMsg, false, false) \
67 X(qmlDuplicateInlineComponent, "duplicate-inline-component", "DuplicateInlineComponent", \
68 "Warn about duplicate inline components", QtWarningMsg, false, false) \
69 X(qmlDuplicatePropertyBinding, "duplicate-property-binding", "DuplicatePropertyBinding", \
70 "Warn about duplicate property bindings", QtWarningMsg, false, false) \
71 X(qmlDuplicatedName, "duplicated-name", "DuplicatedName", \
72 "Warn about duplicated property/signal names", QtWarningMsg, false, false) \
73 X(qmlEnumEntryMatchesEnum, "enum-entry-matches-enum", "EnumEntryMatchesEnum", \
74 "Warn about enum entries named the same as the enum itself", QtWarningMsg, false, false) \
75 X(qmlEval, "eval", "Eval", "Warn about uses of eval()", QtWarningMsg, false, false) \
76 X(qmlFunctionUsedBeforeDeclaration, "function-used-before-declaration", \
77 "FunctionUsedBeforeDeclaration", "Warn if a function is used before declaration", \
78 QtWarningMsg, true, false) \
79 X(qmlImport, "import", "ImportFailure", "Warn about failing imports and deprecated qmltypes", \
80 QtWarningMsg, false, false) \
81 X(qmlImportFileSelector, "import-file-selector", "ImportFileSelector", \
82 "Warn about encountered file selectors during import", QtInfoMsg, true, false) \
83 X(qmlIncompatibleType, "incompatible-type", "IncompatibleType", \
84 "Warn about incompatible types", QtWarningMsg, false, false) \
85 X(qmlInheritanceCycle, "inheritance-cycle", "InheritanceCycle", \
86 "Warn about inheritance cycles", QtWarningMsg, false, false) \
87 X(qmlInvalidLintDirective, "invalid-lint-directive", "InvalidLintDirective", \
88 "Warn if an invalid qmllint comment is found", QtWarningMsg, false, false) \
89 X(qmlLiteralConstructor, "literal-constructor", "LiteralConstructor", \
90 "Warn about using literal constructors, like Boolean or String for example.", QtWarningMsg, \
91 false, false) \
92 X(qmlMissingEnumEntry, "missing-enum-entry", "MissingEnumEntry", \
93 "Warn about using missing enum values.", QtWarningMsg, false, false) \
94 X(qmlMissingProperty, "missing-property", "MissingProperty", "Warn about missing properties", \
95 QtWarningMsg, false, false) \
96 X(qmlMissingType, "missing-type", "MissingType", "Warn about missing types", QtWarningMsg, \
97 false, false) \
98 X(qmlMultilineStrings, "multiline-strings", "MultilineStrings", \
99 "Warn about multiline strings", QtInfoMsg, false, false) \
100 X(qmlNonListProperty, "non-list-property", "NonListProperty", \
101 "Warn about non-list properties", QtWarningMsg, false, false) \
102 X(qmlNonRootEnums, "non-root-enum", "NonRootEnum", \
103 "Warn about enums defined outside the root component", QtWarningMsg, false, false) \
104 X(qmlUnterminatedCase, "unterminated-case", "UnterminatedCase", "Warn about non-empty case " \
105 "blocks that are not terminated by control flow or by a fallthrough comment", \
106 QtWarningMsg, false, false) \
107 X(qmlPlugin, "plugin", "LintPluginWarnings", "Warn if a qmllint plugin finds an issue", \
108 QtWarningMsg, true, false) \
109 X(qmlPreferNonVarProperties, "prefer-non-var-properties", "PreferNonVarProperties", \
110 "Warn about var properties that could use a more specific type", QtWarningMsg, false, false) \
111 X(qmlPrefixedImportType, "prefixed-import-type", "PrefixedImportType", \
112 "Warn about prefixed import types", QtWarningMsg, false, false) \
113 X(qmlReadOnlyProperty, "read-only-property", "ReadOnlyProperty", \
114 "Warn about writing to read-only properties", QtWarningMsg, false, false) \
115 X(qmlRecursionDepthErrors, "recursion-depth-errors", "", "", QtWarningMsg, false, true) \
116 X(qmlRedundantOptionalChaining, "redundant-optional-chaining", "RedundantOptionalChaining", \
117 "Warn about optional chaining on non-voidable and non-nullable base", QtWarningMsg, false, \
118 false) \
119 X(qmlRequired, "required", "RequiredProperty", "Warn about required properties", QtWarningMsg, \
120 false, false) \
121 X(qmlRestrictedType, "restricted-type", "RestrictedType", "Warn about restricted types", \
122 QtWarningMsg, false, false) \
123 X(qmlSignalParameters, "signal-handler-parameters", "BadSignalHandlerParameters", \
124 "Warn about bad signal handler parameters", QtWarningMsg, false, false) \
125 X(qmlStalePropertyRead, "stale-property-read", "StalePropertyRead", \
126 "Warn about bindings reading non-constant and non-notifiable properties", QtWarningMsg, \
127 false, false) \
128 X(qmlSyntax, "syntax", "", "Syntax errors", QtWarningMsg, false, true) \
129 X(qmlSyntaxDuplicateIds, "syntax.duplicate-ids", "", "ID duplication", QtCriticalMsg, false, \
130 true) \
131 X(qmlSyntaxIdQuotation, "syntax.id-quotation", "", "ID quotation", QtWarningMsg, false, true) \
132 X(qmlTopLevelComponent, "top-level-component", "TopLevelComponent", \
133 "Warn if a top level Component is encountered", QtWarningMsg, false, false) \
134 X(qmlTranslationFunctionMismatch, "translation-function-mismatch", \
135 "TranslationFunctionMismatch", \
136 "Warn about usages of ID and non-ID translation functions in the same file.", QtWarningMsg, \
137 false, false) \
138 X(qmlUncreatableType, "uncreatable-type", "UncreatableType", \
139 "Warn if uncreatable types are created", QtWarningMsg, false, false) \
140 X(qmlUnintentionalEmptyBlock, "unintentional-empty-block", "UnintentionalEmptyBlock", \
141 "Warn about bindings that contain only an empty block", QtWarningMsg, false, false) \
142 X(qmlUnqualified, "unqualified", "UnqualifiedAccess", \
143 "Warn about unqualified identifiers and how to fix them", QtWarningMsg, false, false) \
144 X(qmlUnreachableCode, "unreachable-code", "UnreachableCode", "Warn about unreachable code.", \
145 QtWarningMsg, false, false) \
146 X(qmlUnresolvedAlias, "unresolved-alias", "UnresolvedAlias", "Warn about unresolved aliases", \
147 QtWarningMsg, false, false) \
148 X(qmlUnresolvedType, "unresolved-type", "UnresolvedType", "Warn about unresolved types", \
149 QtWarningMsg, false, false) \
150 X(qmlUnusedImports, "unused-imports", "UnusedImports", "Warn about unused imports", QtInfoMsg, \
151 false, false) \
152 X(qmlUseProperFunction, "use-proper-function", "UseProperFunction", \
153 "Warn if var is used for storing functions", QtWarningMsg, false, false) \
154 X(qmlVarUsedBeforeDeclaration, "var-used-before-declaration", "VarUsedBeforeDeclaration", \
155 "Warn if a variable is used before declaration", QtWarningMsg, false, false) \
156 X(qmlVoid, "void", "Void", "Warn about void expressions.", QtWarningMsg, true, false) \
157 X(qmlWith, "with", "WithStatement", \
158 "Warn about with statements as they can cause false " \
159 "positives when checking for unqualified access", \
160 QtWarningMsg, false, false)
161
162#define X(category, name, setting, description, level, ignored, isDefault) \
163 const QQmlSA::LoggerWarningId category{ name };
164QMLLINT_DEFAULT_CATEGORIES
165#undef X
166
167
168#define X(category, name, setting, description, level, ignored, isDefault) ++i;
169constexpr size_t numCategories = [] { size_t i = 0; QMLLINT_DEFAULT_CATEGORIES return i; }();
170#undef X
171
172constexpr bool isUnique(const std::array<std::string_view, numCategories>& fields) {
173 for (std::size_t i = 0; i < fields.size(); ++i) {
174 for (std::size_t j = i + 1; j < fields.size(); ++j) {
175 if (!fields[i].empty() && fields[i] == fields[j]) {
176 return false;
177 }
178 }
179 }
180 return true;
181}
182
183#define X(category, name, setting, description, level, ignored, isDefault) std::string_view(name),
184static_assert(isUnique(fields: std::array{ QMLLINT_DEFAULT_CATEGORIES }), "Duplicate names found!");
185#undef X
186
187#define X(category, name, setting, description, level, ignored, isDefault) std::string_view(setting),
188static_assert(isUnique(fields: std::array{ QMLLINT_DEFAULT_CATEGORIES }), "Duplicate settings found!");
189#undef X
190
191#define X(category, name, setting, description, level, ignored, isDefault) std::string_view(description),
192static_assert(isUnique(fields: std::array{ QMLLINT_DEFAULT_CATEGORIES }), "Duplicate description found!");
193#undef X
194
195
196QQmlJSLogger::QQmlJSLogger()
197{
198 static const QList<QQmlJS::LoggerCategory> cats = defaultCategories();
199
200 for (const QQmlJS::LoggerCategory &category : cats)
201 registerCategory(category);
202
203 // setup color output
204 m_output.insertMapping(colorID: QtCriticalMsg, colorCode: QColorOutput::RedForeground);
205 m_output.insertMapping(colorID: QtWarningMsg, colorCode: QColorOutput::PurpleForeground); // Yellow?
206 m_output.insertMapping(colorID: QtInfoMsg, colorCode: QColorOutput::BlueForeground);
207 m_output.insertMapping(colorID: QtDebugMsg, colorCode: QColorOutput::GreenForeground); // None?
208}
209
210const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::defaultCategories()
211{
212 static const QList<QQmlJS::LoggerCategory> cats = {
213#define X(category, name, setting, description, level, ignored, isDefault) \
214 QQmlJS::LoggerCategory{ name##_L1, setting##_L1, description##_L1, level, ignored, isDefault },
215 QMLLINT_DEFAULT_CATEGORIES
216#undef X
217 };
218
219 return cats;
220}
221
222bool QQmlJSFixSuggestion::operator==(const QQmlJSFixSuggestion &other) const
223{
224 return m_location == other.m_location && m_fixDescription == other.m_fixDescription
225 && m_replacement == other.m_replacement && m_filename == other.m_filename
226 && m_hint == other.m_hint && m_autoApplicable == other.m_autoApplicable;
227}
228
229bool QQmlJSFixSuggestion::operator!=(const QQmlJSFixSuggestion &other) const
230{
231 return !(*this == other);
232}
233
234QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories() const
235{
236 return m_categories.values();
237}
238
239void QQmlJSLogger::registerCategory(const QQmlJS::LoggerCategory &category)
240{
241 if (m_categories.contains(key: category.name())) {
242 qWarning() << "Trying to re-register existing logger category" << category.name();
243 return;
244 }
245
246 m_categoryLevels[category.name()] = category.level();
247 m_categoryIgnored[category.name()] = category.isIgnored();
248 m_categories.insert(key: category.name(), value: category);
249}
250
251static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
252{
253 static QHash<QtMsgType, int> level = { { QtDebugMsg, 0 },
254 { QtInfoMsg, 1 },
255 { QtWarningMsg, 2 },
256 { QtCriticalMsg, 3 },
257 { QtFatalMsg, 4 } };
258 return level[a] < level[b];
259}
260
261void QQmlJSLogger::log(
262 Message diagMsg, bool showContext, bool showFileName, const QString overrideFileName)
263{
264 Q_ASSERT(m_categoryLevels.contains(diagMsg.id.toString()));
265
266 if (isCategoryIgnored(id: diagMsg.id) || isDisabled())
267 return;
268
269 // Note: assume \a type is the type we should prefer for logging
270
271 if (diagMsg.loc.isValid()
272 && m_ignoredWarnings[diagMsg.lineForDisabling()].contains(value: diagMsg.id.toString())) {
273 return;
274 }
275
276 QString prefix;
277
278 if ((!overrideFileName.isEmpty() || !m_filePath.isEmpty()) && showFileName) {
279 prefix = (!overrideFileName.isEmpty() ? overrideFileName : m_filePath)
280 + QStringLiteral(":");
281 }
282
283 if (diagMsg.loc.isValid())
284 prefix += QStringLiteral("%1:%2: ").arg(a: diagMsg.loc.startLine).arg(a: diagMsg.loc.startColumn);
285 else if (!prefix.isEmpty())
286 prefix += QStringLiteral(": "); // produce double colon for Qt Creator's issues pane
287
288 // Note: we do the clamping to [Info, Critical] range since our logger only
289 // supports 3 categories
290 diagMsg.type = std::clamp(val: diagMsg.type, lo: QtInfoMsg, hi: QtCriticalMsg, comp: isMsgTypeLess);
291
292 // Note: since we clamped our \a type, the output message is not printed
293 // exactly like it was requested, bear with us
294 m_output.writePrefixedMessage(
295 message: u"%1%2 [%3]"_s.arg(args&: prefix, args&: diagMsg.message, args: diagMsg.id.toString()), type: diagMsg.type);
296
297 if (diagMsg.loc.length > 0 && !m_code.isEmpty() && showContext)
298 printContext(overrideFileName, location: diagMsg.loc);
299
300 if (diagMsg.fixSuggestion.has_value())
301 printFix(fix: diagMsg.fixSuggestion.value());
302
303 if (m_inTransaction) {
304 m_pendingMessages.push_back(t: std::move(diagMsg));
305 } else {
306 countMessage(message: diagMsg);
307 m_currentFunctionMessages.push_back(t: std::move(diagMsg));
308 }
309
310 if (!m_inTransaction)
311 m_output.flushBuffer();
312}
313
314void QQmlJSLogger::countMessage(const Message &message)
315{
316 switch (message.type) {
317 case QtWarningMsg:
318 ++m_numWarnings;
319 break;
320 case QtCriticalMsg:
321 ++m_numErrors;
322 break;
323 default:
324 break;
325 }
326}
327
328void QQmlJSLogger::processMessages(const QList<QQmlJS::DiagnosticMessage> &messages,
329 QQmlJS::LoggerWarningId id,
330 const QQmlJS::SourceLocation &sourceLocation)
331{
332 if (messages.isEmpty() || isCategoryIgnored(id) || isDisabled())
333 return;
334
335 m_output.write(QStringLiteral("---\n"));
336
337 // TODO: we should instead respect message's category here (potentially, it
338 // should hold a category instead of type)
339 for (const QQmlJS::DiagnosticMessage &message : messages)
340 log(message: message.message, id, srcLocation: sourceLocation, showContext: false, showFileName: false);
341
342 m_output.write(QStringLiteral("---\n\n"));
343}
344
345void QQmlJSLogger::finalizeFuction()
346{
347 Q_ASSERT(!m_inTransaction);
348 m_archivedMessages.append(other: std::exchange(obj&: m_currentFunctionMessages, new_val: {}));
349 m_hasCompileError = false;
350}
351
352/*!
353 \internal
354 Starts a transaction for a compile pass. This buffers all messages until the
355 transaction completes. If you commit the transaction, the messages are printed
356 and added to the list of committed messages. If you roll it back, the logger
357 reverts to the state before the start of the transaction.
358
359 This is useful for compile passes that potentially have to be repeated, such
360 as the type propagator. We don't want to see the same messages logged multiple
361 times.
362 */
363void QQmlJSLogger::startTransaction()
364{
365 Q_ASSERT(!m_inTransaction);
366 m_inTransaction = true;
367}
368
369/*!
370 \internal
371 Commit the current transaction. Print all pending messages, and add them to
372 the list of committed messages. Then, clear the transaction flag.
373 */
374void QQmlJSLogger::commit()
375{
376 Q_ASSERT(m_inTransaction);
377 for (const Message &message : std::as_const(t&: m_pendingMessages))
378 countMessage(message);
379
380 m_currentFunctionMessages.append(other: std::exchange(obj&: m_pendingMessages, new_val: {}));
381 m_hasCompileError = m_hasCompileError || std::exchange(obj&: m_hasPendingCompileError, new_val: false);
382 m_output.flushBuffer();
383 m_inTransaction = false;
384}
385
386/*!
387 \internal
388 Roll back the current transaction and revert the logger to the state before
389 it was started.
390 */
391void QQmlJSLogger::rollback()
392{
393 Q_ASSERT(m_inTransaction);
394 m_pendingMessages.clear();
395 m_hasPendingCompileError = false;
396 m_output.discardBuffer();
397 m_inTransaction = false;
398}
399
400void QQmlJSLogger::printContext(const QString &overrideFileName,
401 const QQmlJS::SourceLocation &location)
402{
403 QString code = m_code;
404
405 if (!overrideFileName.isEmpty() && overrideFileName != m_filePath) {
406 QFile file(overrideFileName);
407 const bool success = file.open(flags: QFile::ReadOnly);
408 Q_ASSERT(success);
409 code = QString::fromUtf8(ba: file.readAll());
410 }
411
412 IssueLocationWithContext issueLocationWithContext { code, location };
413 if (const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
414 m_output.write(message: beforeText);
415
416 bool locationMultiline = issueLocationWithContext.issueText().contains(c: QLatin1Char('\n'));
417
418 if (!issueLocationWithContext.issueText().isEmpty())
419 m_output.write(message: issueLocationWithContext.issueText().toString(), color: QtCriticalMsg);
420 m_output.write(message: issueLocationWithContext.afterText().toString() + QLatin1Char('\n'));
421
422 // Do not draw location indicator for multiline locations
423 if (locationMultiline)
424 return;
425
426 int tabCount = issueLocationWithContext.beforeText().count(c: QLatin1Char('\t'));
427 int locationLength = location.length == 0 ? 1 : location.length;
428 m_output.write(message: QString::fromLatin1(ba: " ").repeated(times: issueLocationWithContext.beforeText().size()
429 - tabCount)
430 + QString::fromLatin1(ba: "\t").repeated(times: tabCount)
431 + QString::fromLatin1(ba: "^").repeated(times: locationLength) + QLatin1Char('\n'));
432}
433
434void QQmlJSLogger::printFix(const QQmlJSFixSuggestion &fixItem)
435{
436 const QString currentFileAbsPath = m_filePath;
437 QString code = m_code;
438 QString currentFile;
439 m_output.writePrefixedMessage(message: fixItem.fixDescription(), type: QtInfoMsg);
440
441 if (!fixItem.location().isValid())
442 return;
443
444 const QString filename = fixItem.filename();
445 if (filename == currentFile) {
446 // Nothing to do in this case, we've already read the code
447 } else if (filename.isEmpty() || filename == currentFileAbsPath) {
448 code = m_code;
449 } else {
450 QFile file(filename);
451 const bool success = file.open(flags: QFile::ReadOnly);
452 Q_ASSERT(success);
453 code = QString::fromUtf8(ba: file.readAll());
454 currentFile = filename;
455 }
456
457 IssueLocationWithContext issueLocationWithContext { code, fixItem.location() };
458
459 if (const QStringView beforeText = issueLocationWithContext.beforeText();
460 !beforeText.isEmpty()) {
461 m_output.write(message: beforeText);
462 }
463
464 // The replacement string can be empty if we're only pointing something out to the user
465 const QString replacement = fixItem.replacement();
466 QStringView replacementString = replacement.isEmpty()
467 ? issueLocationWithContext.issueText()
468 : replacement;
469
470 // But if there's nothing to change it cannot be auto-applied
471 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
472
473 if (!replacementString.isEmpty())
474 m_output.write(message: replacementString, color: QtDebugMsg);
475 m_output.write(message: issueLocationWithContext.afterText().toString() + u'\n');
476
477 int tabCount = issueLocationWithContext.beforeText().count(c: u'\t');
478
479 // Do not draw location indicator for multiline replacement strings
480 if (!replacementString.contains(c: u'\n')) {
481 m_output.write(message: u" "_s.repeated(
482 times: issueLocationWithContext.beforeText().size() - tabCount)
483 + u"\t"_s.repeated(times: tabCount)
484 + u"^"_s.repeated(times: replacement.size()) + u'\n');
485 }
486
487 if (!fixItem.hint().isEmpty())
488 m_output.write(message: " "_L1 + fixItem.hint());
489}
490
491QQmlJSFixSuggestion::QQmlJSFixSuggestion(const QString &fixDescription,
492 const QQmlJS::SourceLocation &location,
493 const QString &replacement)
494 : m_location{ location }, m_fixDescription{ fixDescription }, m_replacement{ replacement }
495{
496}
497
498QT_END_NAMESPACE
499

source code of qtdeclarative/src/qmlcompiler/qqmljslogger.cpp