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