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// don't forget to forward-declare your logging category ID in qqmljsloggingutils.h!
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,
61 NonEssential)
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,
75 NonEssential)
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,
95 NonEssential)
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,
116 NonEssential)
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,
122 NonEssential)
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,
133 NonEssential)
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,
145 NonEssential)
146 X(qmlRequired, "required", "RequiredProperty", "Warn about required properties", Warning,
147 NonEssential)
148 X(qmlShadow, "shadow", "Shadow", "Warn about shadowing attributes from a base class", Disable,
149 NonEssential)
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,
154 NonEssential)
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,
160 NonEssential)
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,
176 NonEssential)
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)
185
186#define X(category, name, setting, description, severity, essential)
187 const QQmlSA::LoggerWarningId category{ name };
189#undef X
190
191
192#define X(category, name, setting, description, severity, essential) ++i;
193constexpr size_t numCategories = [] { size_t i = 0; QMLLINT_BUILTIN_CATEGORIES return i; }();
194#undef X
195
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]) {
200 return false;
201 }
202 }
203 }
204 return true;
205}
206
207#define X(category, name, setting, description, severity, essential) std::string_view(name),
208static_assert(isUnique(std::array{ QMLLINT_BUILTIN_CATEGORIES }), "Duplicate names found!");
209#undef X
210
211#define X(category, name, setting, description, severity, essential) std::string_view(setting),
212static_assert(isUnique(std::array{ QMLLINT_BUILTIN_CATEGORIES }), "Duplicate settings found!");
213#undef X
214
215#define X(category, name, setting, description, severity, essential) std::string_view(description),
216static_assert(isUnique(std::array{ QMLLINT_BUILTIN_CATEGORIES }), "Duplicate description found!");
217#undef X
218
219
220QQmlJSLogger::QQmlJSLogger()
221{
222 static const QList<QQmlJS::LoggerCategory> cats = builtinCategories();
223
224 for (const QQmlJS::LoggerCategory &category : cats)
225 registerCategory(category);
226
227 // setup color output
228 m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
229 m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground); // Yellow?
230 m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
231 m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground); // None?
232}
233
234const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::builtinCategories()
235{
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 },
240#undef X
241 };
242
243 return cats;
244}
245
246bool QQmlJSFixSuggestion::operator==(const QQmlJSFixSuggestion &other) const
247{
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;
251}
252
253bool QQmlJSFixSuggestion::operator!=(const QQmlJSFixSuggestion &other) const
254{
255 return !(*this == other);
256}
257
258QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories() const
259{
260 return m_categories.values();
261}
262
263void QQmlJSLogger::registerCategory(const QQmlJS::LoggerCategory &category)
264{
265 if (m_categories.contains(category.name())) {
266 qWarning() << "Trying to re-register existing logger category" << category.name();
267 return;
268 }
269
270 m_categorySeverities[category.name()] = category.severity();
271 m_categories.insert(category.name(), category);
272}
273
274static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
275{
276 static QHash<QtMsgType, int> level = { { QtDebugMsg, 0 },
277 { QtInfoMsg, 1 },
278 { QtWarningMsg, 2 },
279 { QtCriticalMsg, 3 },
280 { QtFatalMsg, 4 } };
281 return level[a] < level[b];
282}
283
284void QQmlJSLogger::log(Message &&diagMsg, bool showContext, bool showFileName)
285{
286 Q_ASSERT(m_categorySeverities.contains(diagMsg.id.toString()));
287
288 if (categorySeverity(diagMsg.id) == QQmlJS::WarningSeverity::Disable || isDisabled())
289 return;
290
291 // Note: assume \a type is the type we should prefer for logging
292
293 if (diagMsg.loc.isValid()
294 && m_ignoredWarnings[diagMsg.lineForDisabling()].contains(diagMsg.id.toString())) {
295 return;
296 }
297
298 QString prefix;
299 if (!m_filePath.isEmpty() && showFileName)
300 prefix = m_filePath + QStringLiteral(":");
301
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(": "); // produce double colon for Qt Creator's issues pane
306
307 // Note: we do the clamping to [Info, Critical] range since our logger only
308 // supports 3 categories
309 diagMsg.type = std::clamp(diagMsg.type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
310
311 // Note: since we clamped our \a type, the output message is not printed
312 // exactly like it was requested, bear with us
313 m_output.writePrefixedMessage(
314 u"%1%2 [%3]"_s.arg(prefix, diagMsg.message, diagMsg.id.toString()), diagMsg.type);
315
316 if (diagMsg.loc.length > 0 && !m_code.isEmpty() && showContext)
317 printContext(diagMsg.loc);
318
319 if (diagMsg.fixSuggestion.has_value())
320 printFix(diagMsg.fixSuggestion.value());
321
322 if (m_inTransaction) {
323 m_pendingMessages.push_back(std::move(diagMsg));
324 } else {
325 countMessage(diagMsg);
326 m_currentFunctionMessages.push_back(std::move(diagMsg));
327 }
328
329 if (!m_inTransaction)
330 m_output.flushBuffer();
331}
332
333void QQmlJSLogger::countMessage(const Message &message)
334{
335 switch (message.type) {
336 case QtWarningMsg:
337 ++m_numWarnings;
338 break;
339 case QtCriticalMsg:
340 ++m_numErrors;
341 break;
342 default:
343 break;
344 }
345}
346
347void QQmlJSLogger::processMessages(QSpan<const QQmlJS::DiagnosticMessage> messages,
348 QQmlJS::LoggerWarningId id,
349 const QQmlJS::SourceLocation &sourceLocation)
350{
351 if (messages.isEmpty() || categorySeverity(id) == QQmlJS::WarningSeverity::Disable || isDisabled())
352 return;
353
354 m_output.write(QStringLiteral("---\n"));
355
356 // TODO: we should instead respect message's category here (potentially, it
357 // should hold a category instead of type)
358 for (const QQmlJS::DiagnosticMessage &message : messages)
359 log(message.message, id, sourceLocation, false, false);
360
361 m_output.write(QStringLiteral("---\n\n"));
362}
363
364void QQmlJSLogger::finalizeFunction()
365{
366 Q_ASSERT(!m_inTransaction);
367 m_archivedMessages.append(std::exchange(m_currentFunctionMessages, {}));
368 m_hasCompileError = false;
369}
370
371/*!
372 \internal
373 Starts a transaction for a compile pass. This buffers all messages until the
374 transaction completes. If you commit the transaction, the messages are printed
375 and added to the list of committed messages. If you roll it back, the logger
376 reverts to the state before the start of the transaction.
377
378 This is useful for compile passes that potentially have to be repeated, such
379 as the type propagator. We don't want to see the same messages logged multiple
380 times.
381 */
382void QQmlJSLogger::startTransaction()
383{
384 Q_ASSERT(!m_inTransaction);
385 m_inTransaction = true;
386}
387
388/*!
389 \internal
390 Commit the current transaction. Print all pending messages, and add them to
391 the list of committed messages. Then, clear the transaction flag.
392 */
393void QQmlJSLogger::commit()
394{
395 Q_ASSERT(m_inTransaction);
396 for (const Message &message : std::as_const(m_pendingMessages))
397 countMessage(message);
398
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;
403}
404
405/*!
406 \internal
407 Roll back the current transaction and revert the logger to the state before
408 it was started.
409 */
410void QQmlJSLogger::rollback()
411{
412 Q_ASSERT(m_inTransaction);
413 m_pendingMessages.clear();
414 m_hasPendingCompileError = false;
415 m_output.discardBuffer();
416 m_inTransaction = false;
417}
418
419void QQmlJSLogger::printContext(const QQmlJS::SourceLocation &location)
420{
421 QString code = m_code;
422
423 IssueLocationWithContext issueLocationWithContext { code, location };
424 if (const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
425 m_output.write(beforeText);
426
427 bool locationMultiline = issueLocationWithContext.issueText().contains(QLatin1Char('\n'));
428
429 if (!issueLocationWithContext.issueText().isEmpty())
430 m_output.write(issueLocationWithContext.issueText().toString(), QtCriticalMsg);
431 m_output.write(issueLocationWithContext.afterText().toString() + QLatin1Char('\n'));
432
433 // Do not draw location indicator for multiline locations
434 if (locationMultiline)
435 return;
436
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()
440 - tabCount)
441 + QString::fromLatin1("\t").repeated(tabCount)
442 + QString::fromLatin1("^").repeated(locationLength) + QLatin1Char('\n'));
443}
444
445void QQmlJSLogger::printFix(const QQmlJSFixSuggestion &fixItem)
446{
447 const QString currentFileAbsPath = m_filePath;
448 QString code = m_code;
449 QString currentFile;
450 m_output.writePrefixedMessage(fixItem.description(), QtInfoMsg);
451
452 if (!fixItem.location().isValid())
453 return;
454
455 const QString filename = fixItem.filename();
456 if (filename == currentFile) {
457 // Nothing to do in this case, we've already read the code
458 } else if (filename.isEmpty() || filename == currentFileAbsPath) {
459 code = m_code;
460 } else {
461 QFile file(filename);
462 const bool success = file.open(QFile::ReadOnly);
463 Q_ASSERT(success);
464 code = QString::fromUtf8(file.readAll());
465 currentFile = filename;
466 }
467
468 IssueLocationWithContext issueLocationWithContext { code, fixItem.location() };
469
470 if (const QStringView beforeText = issueLocationWithContext.beforeText();
471 !beforeText.isEmpty()) {
472 m_output.write(beforeText);
473 }
474
475 // The replacement string can be empty if we're only pointing something out to the user
476 const QString replacement = fixItem.replacement();
477 QStringView replacementString = replacement.isEmpty()
478 ? issueLocationWithContext.issueText()
479 : replacement;
480
481 // But if there's nothing to change it cannot be auto-applied
482 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
483
484 if (!replacementString.isEmpty())
485 m_output.write(replacementString, QtDebugMsg);
486 m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
487
488 int tabCount = issueLocationWithContext.beforeText().count(u'\t');
489
490 // Do not draw location indicator for multiline replacement strings
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');
496 }
497}
498
499QQmlJSFixSuggestion::QQmlJSFixSuggestion(const QString &description,
500 const QQmlJS::SourceLocation &location,
501 const QString &replacement)
502 : m_location{ location }, m_description{ description }, m_replacement{ replacement }
503{
504}
505
506QT_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)