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