5#include <QtCore/qcompilerdetection.h>
10QT_WARNING_DISABLE_GCC(
"-Wuninitialized")
11QT_WARNING_DISABLE_GCC(
"-Wmaybe-uninitialized")
12#include <QtCore/qlist.h>
15#include <private/qqmljslogger_p.h>
16#include <private/qqmlsa_p.h>
18#include <QtQmlCompiler/qqmljsloggingutils.h>
20#include <QtCore/qglobal.h>
21#include <QtCore/qfile.h>
26using namespace Qt::StringLiterals;
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,
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,
42 X(qmlCompiler, "compiler", "CompilerWarnings", "Warn about compiler issues", QtWarningMsg,
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,
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,
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,
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,
120 X(qmlRequired, "required", "RequiredProperty", "Warn about required properties", QtWarningMsg,
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,
129 X(qmlSyntax, "syntax", "", "Syntax errors", QtWarningMsg, false, true)
130 X(qmlSyntaxDuplicateIds, "syntax.duplicate-ids", "", "ID duplication", QtCriticalMsg, false,
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,
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,
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)
163#define X(category, name, setting, description, level, ignored, isDefault)
164 const QQmlSA::LoggerWarningId category{ name };
169#define X(category, name, setting, description, level, ignored, isDefault) ++i;
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]) {
184#define X(category, name, setting, description, level, ignored, isDefault) std::string_view(name),
188#define X(category, name, setting, description, level, ignored, isDefault) std::string_view(setting),
192#define X(category, name, setting, description, level, ignored, isDefault) std::string_view(description),
197QQmlJSLogger::QQmlJSLogger()
199 static const QList<QQmlJS::LoggerCategory> cats = defaultCategories();
201 for (
const QQmlJS::LoggerCategory &category : cats)
202 registerCategory(category);
205 m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
206 m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground);
207 m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
208 m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground);
211const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::defaultCategories()
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 },
223bool QQmlJSFixSuggestion::operator==(
const QQmlJSFixSuggestion &other)
const
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;
230bool QQmlJSFixSuggestion::operator!=(
const QQmlJSFixSuggestion &other)
const
232 return !(*
this == other);
235QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories()
const
237 return m_categories.values();
240void QQmlJSLogger::registerCategory(
const QQmlJS::LoggerCategory &category)
242 if (m_categories.contains(category.name())) {
243 qWarning() <<
"Trying to re-register existing logger category" << category.name();
247 m_categoryLevels[category.name()] = category.level();
248 m_categoryIgnored[category.name()] = category.isIgnored();
249 m_categories.insert(category.name(), category);
254 static QHash<QtMsgType,
int> level = { { QtDebugMsg, 0 },
257 { QtCriticalMsg, 3 },
259 return level[a] < level[b];
262void QQmlJSLogger::log(
263 Message diagMsg,
bool showContext,
bool showFileName,
const QString overrideFileName)
265 Q_ASSERT(m_categoryLevels.contains(diagMsg.id.toString()));
267 if (isCategoryIgnored(diagMsg.id) || isDisabled())
272 if (diagMsg.loc.isValid()
273 && m_ignoredWarnings[diagMsg.lineForDisabling()].contains(diagMsg.id.toString())) {
279 if ((!overrideFileName.isEmpty() || !m_filePath.isEmpty()) && showFileName) {
280 prefix = (!overrideFileName.isEmpty() ? overrideFileName : m_filePath)
281 + QStringLiteral(
":");
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(
": ");
291 diagMsg.type = std::clamp(diagMsg.type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
295 m_output.writePrefixedMessage(
296 u"%1%2 [%3]"_s.arg(prefix, diagMsg.message, diagMsg.id.toString()), diagMsg.type);
298 if (diagMsg.loc.length > 0 && !m_code.isEmpty() && showContext)
299 printContext(overrideFileName, diagMsg.loc);
301 if (diagMsg.fixSuggestion.has_value())
302 printFix(diagMsg.fixSuggestion.value());
304 if (m_inTransaction) {
305 m_pendingMessages.push_back(std::move(diagMsg));
307 countMessage(diagMsg);
308 m_currentFunctionMessages.push_back(std::move(diagMsg));
311 if (!m_inTransaction)
312 m_output.flushBuffer();
315void QQmlJSLogger::countMessage(
const Message &message)
317 switch (message.type) {
329void QQmlJSLogger::processMessages(
const QList<QQmlJS::DiagnosticMessage> &messages,
330 QQmlJS::LoggerWarningId id,
331 const QQmlJS::SourceLocation &sourceLocation)
333 if (messages.isEmpty() || isCategoryIgnored(id) || isDisabled())
336 m_output.write(QStringLiteral(
"---\n"));
340 for (
const QQmlJS::DiagnosticMessage &message : messages)
341 log(message.message, id, sourceLocation,
false,
false);
343 m_output.write(QStringLiteral(
"---\n\n"));
346void QQmlJSLogger::finalizeFuction()
348 Q_ASSERT(!m_inTransaction);
349 m_archivedMessages.append(std::exchange(m_currentFunctionMessages, {}));
350 m_hasCompileError =
false;
354
355
356
357
358
359
360
361
362
363
364void QQmlJSLogger::startTransaction()
366 Q_ASSERT(!m_inTransaction);
367 m_inTransaction =
true;
371
372
373
374
375void QQmlJSLogger::commit()
377 Q_ASSERT(m_inTransaction);
378 for (
const Message &message : std::as_const(m_pendingMessages))
379 countMessage(message);
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;
388
389
390
391
392void QQmlJSLogger::rollback()
394 Q_ASSERT(m_inTransaction);
395 m_pendingMessages.clear();
396 m_hasPendingCompileError =
false;
397 m_output.discardBuffer();
398 m_inTransaction =
false;
401void QQmlJSLogger::printContext(
const QString &overrideFileName,
402 const QQmlJS::SourceLocation &location)
404 QString code = m_code;
406 if (!overrideFileName.isEmpty() && overrideFileName != m_filePath) {
407 QFile file(overrideFileName);
408 const bool success = file.open(QFile::ReadOnly);
410 code = QString::fromUtf8(file.readAll());
413 IssueLocationWithContext issueLocationWithContext { code, location };
414 if (
const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
415 m_output.write(beforeText);
417 bool locationMultiline = issueLocationWithContext.issueText().contains(QLatin1Char(
'\n'));
419 if (!issueLocationWithContext.issueText().isEmpty())
420 m_output.write(issueLocationWithContext.issueText().toString(), QtCriticalMsg);
421 m_output.write(issueLocationWithContext.afterText().toString() + QLatin1Char(
'\n'));
424 if (locationMultiline)
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()
431 + QString::fromLatin1(
"\t").repeated(tabCount)
432 + QString::fromLatin1(
"^").repeated(locationLength) + QLatin1Char(
'\n'));
435void QQmlJSLogger::printFix(
const QQmlJSFixSuggestion &fixItem)
437 const QString currentFileAbsPath = m_filePath;
438 QString code = m_code;
440 m_output.writePrefixedMessage(fixItem.fixDescription(), QtInfoMsg);
442 if (!fixItem.location().isValid())
445 const QString filename = fixItem.filename();
446 if (filename == currentFile) {
448 }
else if (filename.isEmpty() || filename == currentFileAbsPath) {
451 QFile file(filename);
452 const bool success = file.open(QFile::ReadOnly);
454 code = QString::fromUtf8(file.readAll());
455 currentFile = filename;
458 IssueLocationWithContext issueLocationWithContext { code, fixItem.location() };
460 if (
const QStringView beforeText = issueLocationWithContext.beforeText();
461 !beforeText.isEmpty()) {
462 m_output.write(beforeText);
466 const QString replacement = fixItem.replacement();
467 QStringView replacementString = replacement.isEmpty()
468 ? issueLocationWithContext.issueText()
472 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
474 if (!replacementString.isEmpty())
475 m_output.write(replacementString, QtDebugMsg);
476 m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
478 int tabCount = issueLocationWithContext.beforeText().count(u'\t');
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');
488 if (!fixItem.hint().isEmpty())
489 m_output.write(
" "_L1 + fixItem.hint());
492QQmlJSFixSuggestion::QQmlJSFixSuggestion(
const QString &fixDescription,
493 const QQmlJS::SourceLocation &location,
494 const QString &replacement)
495 : m_location{ location }, m_fixDescription{ fixDescription }, m_replacement{ replacement }
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