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
30
31
32
33
34
37
38
39
40
41
42
43
44
48#define QMLLINT_BUILTIN_CATEGORIES
49 X(qmlAccessSingleton, "access-singleton-via-object", "AccessSingletonViaObject",
50 "Warn if a singleton is accessed via an object", Warning, NonEssential)
51 X(qmlAliasCycle, "alias-cycle", "AliasCycle", "Warn about alias cycles", Warning, NonEssential)
52 X(qmlAssignmentInCondition, "assignment-in-condition", "AssignmentInCondition",
53 "Warn about using assignment in conditions.", Warning, NonEssential)
54 X(qmlAttachedPropertyReuse, "attached-property-reuse", "AttachedPropertyReuse",
55 "Warn if attached types from parent components aren't reused. This is handled by the "
56 "QtQuick lint plugin. Use Quick.AttachedPropertyReuse instead.",
57 Disable, NonEssential)
58 X(qmlBlockScopeVarDeclaration, "block-scope-var-declaration", "BlockScopeVarDeclaration",
59 "Warn if a variable is declared with var inside a block scope", Warning, NonEssential)
60 X(qmlComma, "comma", "Comma", "Warn about using comma expressions.", Warning, NonEssential)
61 X(qmlCompiler, "compiler", "CompilerWarnings", "Warn about compiler issues", Disable,
63 X(qmlComponentChildrenCount, "component-children-count", "ComponentChildrenCount",
64 "Warn about Components that don't have exactly one child", Warning, NonEssential)
65 X(qmlConfusingExpressionStatement, "confusing-expression-statement",
66 "ConfusingExpressionStatement",
67 "Warn about expression statement that has no obvious effect.", Warning, NonEssential)
68 X(qmlConfusingMinuses, "confusing-minuses", "ConfusingMinuses",
69 "Warn about confusing minuses.", Warning, NonEssential)
70 X(qmlConfusingPluses, "confusing-pluses", "ConfusingPluses",
71 "Warn about confusing pluses.", Warning, NonEssential)
72 X(qmlContextProperties, "context-properties", "ContextProperties",
73 "Warn about using context properties.", Warning, NonEssential)
74 X(qmlDeferredPropertyId, "deferred-property-id", "DeferredPropertyId",
75 "Warn about making deferred properties immediate by giving them an id.", Disable,
77 X(qmlEnumsAreNotTypes, "enums-are-not-types", "EnumsAreNotTypes",
78 "Warn about the use of enumerations as types.", Warning, NonEssential)
79 X(qmlEqualityTypeCoercion, "equality-type-coercion", "EqualityTypeCoercion",
80 "Warn about coercions due to usages of '==' and '!='", Warning, NonEssential)
81 X(qmlDeprecated, "deprecated", "Deprecated", "Warn about deprecated properties and types",
82 Warning, NonEssential)
83 X(qmlDuplicateEnumEntries, "duplicate-enum-entries", "DuplicateEnumEntries",
84 "Warn about duplicate enum entries", Warning, NonEssential)
85 X(qmlDuplicateImport, "duplicate-import", "DuplicateImport", "Warn about duplicate imports",
86 Warning, NonEssential)
87 X(qmlDuplicateInlineComponent, "duplicate-inline-component", "DuplicateInlineComponent",
88 "Warn about duplicate inline components", Warning, NonEssential)
89 X(qmlDuplicatePropertyBinding, "duplicate-property-binding", "DuplicatePropertyBinding",
90 "Warn about duplicate property bindings", Warning, NonEssential)
91 X(qmlDuplicatedName, "duplicated-name", "DuplicatedName",
92 "Warn about duplicated property/signal names", Warning, NonEssential)
93 X(qmlEnumEntryMatchesEnum, "enum-entry-matches-enum", "EnumEntryMatchesEnum",
94 "Warn about enum entries named the same as the enum itself", Warning, NonEssential)
95 X(qmlEnumKeyCase, "enum-key-case", "EnumKeyCase", "Warn about lowercase enum keys", Warning,
97 X(qmlEval, "eval", "Eval", "Warn about uses of eval()", Warning, NonEssential)
98 X(qmlFunctionUsedBeforeDeclaration, "function-used-before-declaration",
99 "FunctionUsedBeforeDeclaration", "Warn if a function is used before declaration",
100 Disable, NonEssential)
101 X(qmlIdShadowsMember, "id-shadows-member", "IdShadowsMember",
102 "Warn about ids potentially shadowing members", Warning, NonEssential)
103 X(qmlImport, "import", "ImportFailure", "Warn about failing imports and deprecated qmltypes",
104 Warning, NonEssential)
105 X(qmlImportFileSelector, "import-file-selector", "ImportFileSelector",
106 "Warn about encountered file selectors during import", Disable, NonEssential)
107 X(qmlIncompatibleType, "incompatible-type", "IncompatibleType",
108 "Warn about incompatible types", Warning, NonEssential)
109 X(qmlInheritanceCycle, "inheritance-cycle", "InheritanceCycle",
110 "Warn about inheritance cycles", Warning, NonEssential)
111 X(qmlInlineComponentEnums, "inline-component-enums", "InlineComponentEnums",
112 "Warn about enum declarations inside inline components", Warning, NonEssential)
113 X(qmlInvalidLintDirective, "invalid-lint-directive", "InvalidLintDirective",
114 "Warn if an invalid qmllint comment is found", Warning, NonEssential)
115 X(qmlLiteralConstructor, "literal-constructor", "LiteralConstructor",
116 "Warn about using literal constructors, like Boolean or String for example.", Warning,
118 X(qmlMissingEnumEntry, "missing-enum-entry", "MissingEnumEntry",
119 "Warn about using missing enum values.", Warning, NonEssential)
120 X(qmlMissingProperty, "missing-property", "MissingProperty", "Warn about missing properties",
121 Warning, NonEssential)
122 X(qmlMissingType, "missing-type", "MissingType", "Warn about missing types", Warning,
124 X(qmlMultilineStrings, "multiline-strings", "MultilineStrings",
125 "Warn about multiline strings", Info, NonEssential)
126 X(qmlNonListProperty, "non-list-property", "NonListProperty",
127 "Warn about non-list properties", Warning, NonEssential)
128 X(qmlNonRootEnums, "non-root-enum", "NonRootEnum",
129 "Warn about enums defined outside the root component", Warning, NonEssential)
130 X(qmlPropertyOverride, "property-override", "PropertyOverride",
131 "Warn about wrongly overriding properties from a base class", Warning, NonEssential)
132 X(qmlUnterminatedCase, "unterminated-case", "UnterminatedCase", "Warn about non-empty case "
133 "blocks that are not terminated by control flow or by a fallthrough comment", Warning,
135 X(qmlPreferNonVarProperties, "prefer-non-var-properties", "PreferNonVarProperties",
136 "Warn about var properties that could use a more specific type", Warning, NonEssential)
137 X(qmlPrefixedImportType, "prefixed-import-type", "PrefixedImportType",
138 "Warn about prefixed import types", Warning, NonEssential)
139 X(qmlReadOnlyProperty, "read-only-property", "ReadOnlyProperty",
140 "Warn about writing to read-only properties", Warning, NonEssential)
141 X(qmlRecursionDepthErrors, "recursion-depth-errors", "", "", Warning, NonEssential)
142 X(qmlRedundantOptionalChaining, "redundant-optional-chaining", "RedundantOptionalChaining",
143 "Warn about optional chaining on non-voidable and non-nullable base", Warning, NonEssential)
144 X(qmlRenamedType, "renamed-type", "RenamedType",
145 "Warn when renamed types refer to themselves using their unrenamed name", Warning,
147 X(qmlRequired, "required", "RequiredProperty", "Warn about required properties", Warning,
149 X(qmlShadow, "shadow", "Shadow", "Warn about shadowing attributes from a base class", Disable,
151 X(qmlSignalParameters, "signal-handler-parameters", "BadSignalHandlerParameters",
152 "Warn about bad signal handler parameters", Warning, NonEssential)
153 X(qmlStalePropertyRead, "stale-property-read", "StalePropertyRead",
154 "Warn about bindings reading non-constant and non-notifiable properties", Warning,
156 X(qmlSyntax, "syntax", "Syntax", "Syntax errors", Warning, Essential)
157 X(qmlSyntaxDuplicateIds, "syntax.duplicate-ids", "", "ID duplication", Error, NonEssential)
158 X(qmlSyntaxIdQuotation, "syntax.id-quotation", "", "ID quotation", Warning, NonEssential)
159 X(qmlTypeInstantiatedRecursively, "type-instantiated-recursively",
160 "TypeInstantiatedRecursively", "Warn when types are instantiated recursively", Warning,
162 X(qmlTopLevelComponent, "top-level-component", "TopLevelComponent",
163 "Warn if a top level Component is encountered", Warning, NonEssential)
164 X(qmlUncreatableType, "uncreatable-type", "UncreatableType",
165 "Warn if uncreatable types are created", Warning, NonEssential)
166 X(qmlUnintentionalEmptyBlock, "unintentional-empty-block", "UnintentionalEmptyBlock",
167 "Warn about bindings that contain only an empty block", Warning, NonEssential)
168 X(qmlUnqualified, "unqualified", "UnqualifiedAccess",
169 "Warn about unqualified identifiers and how to fix them", Warning, NonEssential)
170 X(qmlUnreachableCode, "unreachable-code", "UnreachableCode", "Warn about unreachable code.",
171 Warning, NonEssential)
172 X(qmlUnresolvedAlias, "unresolved-alias", "UnresolvedAlias", "Warn about unresolved aliases",
173 Warning, NonEssential)
174 X(qmlUnresolvedType, "unresolved-type", "UnresolvedType", "Warn about unresolved types",
175 Warning, NonEssential)
176 X(qmlUnusedImports, "unused-imports", "UnusedImports", "Warn about unused imports", Info,
178 X(qmlUseProperFunction, "use-proper-function", "UseProperFunction",
179 "Warn if var is used for storing functions", Disable, NonEssential)
180 X(qmlVarUsedBeforeDeclaration, "var-used-before-declaration", "VarUsedBeforeDeclaration",
181 "Warn if a variable is used before declaration", Warning, NonEssential)
182 X(qmlVoid, "void", "Void", "Warn about void expressions.", Disable, NonEssential)
183 X(qmlWith, "with", "WithStatement",
184 "Warn about with statements as they can cause NonEssential "
185 "positives when checking for unqualified access", Warning, NonEssential)
189#define X(category, name, setting, description, severity, essential)
190 const QQmlSA::LoggerWarningId category{ name };
195#define X(category, name, setting, description, severity, essential) ++i;
199constexpr bool isUnique(
const std::array<std::string_view, numCategories>& fields) {
200 for (std::size_t i = 0; i < fields.size(); ++i) {
201 for (std::size_t j = i + 1; j < fields.size(); ++j) {
202 if (!fields[i].empty() && fields[i] == fields[j]) {
210#define X(category, name, setting, description, severity, essential) std::string_view(name),
214#define X(category, name, setting, description, severity, essential) std::string_view(setting),
218#define X(category, name, setting, description, severity, essential) std::string_view(description),
223QQmlJSLogger::QQmlJSLogger()
225 static const QList<QQmlJS::LoggerCategory> cats = builtinCategories();
227 for (
const QQmlJS::LoggerCategory &category : cats)
228 registerCategory(category);
231 m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
232 m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground);
233 m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
234 m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground);
237const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::builtinCategories()
239 static const QList<QQmlJS::LoggerCategory> cats = {
240#define X(category, name, setting, description, severity, essential)
241 QQmlJS::LoggerCategory{ name##_L1, setting##_L1, description##_L1, QQmlJS::WarningSeverity::severity, QQmlJS::LoggerCategory::essential },
249bool QQmlJSFixSuggestion::operator==(
const QQmlJSFixSuggestion &other)
const
251 return m_location == other.m_location && m_description == other.m_description
252 && m_documentEdits == other.m_documentEdits && m_filename == other.m_filename
253 && m_autoApplicable == other.m_autoApplicable;
256bool QQmlJSFixSuggestion::operator!=(
const QQmlJSFixSuggestion &other)
const
258 return !(*
this == other);
261QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories()
const
263 return m_categories.values();
266void QQmlJSLogger::registerCategory(
const QQmlJS::LoggerCategory &category)
268 if (m_categories.contains(category.name())) {
269 qWarning() <<
"Trying to re-register existing logger category" << category.name();
273 m_categorySeverities[category.name()] = category.severity();
274 m_categories.insert(category.name(), category);
279 static QHash<QtMsgType,
int> level = { { QtDebugMsg, 0 },
282 { QtCriticalMsg, 3 },
284 return level[a] < level[b];
287void QQmlJSLogger::log(Message &&diagMsg,
bool showContext,
bool showFileName)
289 Q_ASSERT(m_categorySeverities.contains(diagMsg.id.toString()));
291 if (categorySeverity(diagMsg.id) == QQmlJS::WarningSeverity::Disable || isDisabled())
296 if (diagMsg.loc.isValid()
297 && m_ignoredWarnings[diagMsg.lineForDisabling()].contains(diagMsg.id.toString())) {
302 if (!m_filePath.isEmpty() && showFileName)
303 prefix = m_filePath + QStringLiteral(
":");
305 if (diagMsg.loc.isValid())
306 prefix += QStringLiteral(
"%1:%2: ").arg(diagMsg.loc.startLine).arg(diagMsg.loc.startColumn);
307 else if (!prefix.isEmpty())
308 prefix += QStringLiteral(
": ");
312 diagMsg.type = std::clamp(diagMsg.type, QtInfoMsg, QtCriticalMsg, isMsgTypeLess);
316 m_output.writePrefixedMessage(
317 u"%1%2 [%3]"_s.arg(prefix, diagMsg.message, diagMsg.id.toString()), diagMsg.type);
319 if (diagMsg.loc.length > 0 && !m_code.isEmpty() && showContext)
320 printContext(diagMsg.loc);
322 if (diagMsg.fixSuggestion.has_value())
323 printFix(diagMsg.fixSuggestion.value());
325 if (m_inTransaction) {
326 m_pendingMessages.push_back(std::move(diagMsg));
328 countMessage(diagMsg);
329 m_currentFunctionMessages.push_back(std::move(diagMsg));
332 if (!m_inTransaction && !m_manualFlush)
333 m_output.flushBuffer();
336void QQmlJSLogger::countMessage(
const Message &message)
338 switch (message.type) {
350void QQmlJSLogger::processMessages(QSpan<
const QQmlJS::DiagnosticMessage> messages,
351 QQmlJS::LoggerWarningId id,
352 const QQmlJS::SourceLocation &sourceLocation)
354 if (messages.isEmpty() || categorySeverity(id) == QQmlJS::WarningSeverity::Disable || isDisabled())
357 m_output.write(QStringLiteral(
"---\n"));
361 for (
const QQmlJS::DiagnosticMessage &message : messages)
362 log(message.message, id, sourceLocation,
false,
false);
364 m_output.write(QStringLiteral(
"---\n\n"));
367void QQmlJSLogger::finalizeFunction()
369 Q_ASSERT(!m_inTransaction);
370 m_archivedMessages.append(std::exchange(m_currentFunctionMessages, {}));
371 m_hasCompileError =
false;
375
376
377
378
379
380
381
382
383
384
385void QQmlJSLogger::startTransaction()
387 Q_ASSERT(!m_inTransaction);
388 m_inTransaction =
true;
392
393
394
395
396void QQmlJSLogger::commit()
398 Q_ASSERT(m_inTransaction);
399 for (
const Message &message : std::as_const(m_pendingMessages))
400 countMessage(message);
402 m_currentFunctionMessages.append(std::exchange(m_pendingMessages, {}));
403 m_hasCompileError = m_hasCompileError || std::exchange(m_hasPendingCompileError,
false);
405 m_output.flushBuffer();
406 m_inTransaction =
false;
410
411
412
413
414void QQmlJSLogger::rollback()
416 Q_ASSERT(m_inTransaction);
417 m_pendingMessages.clear();
418 m_hasPendingCompileError =
false;
419 m_output.discardBuffer();
420 m_inTransaction =
false;
423void QQmlJSLogger::printContext(
const QQmlJS::SourceLocation &location)
425 QString code = m_code;
427 IssueLocationWithContext issueLocationWithContext { code, location };
428 if (
const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
429 m_output.write(beforeText);
431 bool locationMultiline = issueLocationWithContext.issueText().contains(QLatin1Char(
'\n'));
433 if (!issueLocationWithContext.issueText().isEmpty())
434 m_output.write(issueLocationWithContext.issueText().toString(), QtCriticalMsg);
435 m_output.write(issueLocationWithContext.afterText().toString() + QLatin1Char(
'\n'));
438 if (locationMultiline)
441 int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char(
'\t'));
442 int locationLength = location.length == 0 ? 1 : location.length;
443 m_output.write(QString::fromLatin1(
" ").repeated(issueLocationWithContext.beforeText().size()
445 + QString::fromLatin1(
"\t").repeated(tabCount)
446 + QString::fromLatin1(
"^").repeated(locationLength) + QLatin1Char(
'\n'));
449void QQmlJSLogger::printFix(
const QQmlJSFixSuggestion &fixItem)
451 const QString currentFileAbsPath = m_filePath;
452 QString code = m_code;
454 m_output.writePrefixedMessage(fixItem.description(), QtInfoMsg);
456 if (!fixItem.location().isValid())
459 const QString filename = fixItem.filename();
460 if (filename == currentFile) {
462 }
else if (filename.isEmpty() || filename == currentFileAbsPath) {
465 QFile file(filename);
466 const bool success = file.open(QFile::ReadOnly);
468 code = QString::fromUtf8(file.readAll());
469 currentFile = filename;
472 const auto &documentEdits = fixItem.documentEdits();
473 if (!documentEdits.empty()) {
474 m_output.write(documentEdits.size() == 1 ?
"Suggested change:\n"_L1
475 :
"Suggested changes:\n"_L1);
476 for (
const auto &documentEdit: documentEdits) {
477 if (!documentEdit.m_location.isValid())
480 IssueLocationWithContext issueLocationWithContext { code, documentEdit.m_location };
482 if (
const QStringView beforeText = issueLocationWithContext.beforeText();
483 !beforeText.isEmpty()) {
484 m_output.write(beforeText);
488 const QString replacement = documentEdit.m_replacement;
489 QStringView replacementString = replacement.isEmpty()
490 ? issueLocationWithContext.issueText()
494 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
496 if (!replacementString.isEmpty())
497 m_output.write(replacementString, QtDebugMsg);
498 m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
500 int tabCount = issueLocationWithContext.beforeText().count(u'\t');
503 if (!replacementString.contains(u'\n')) {
504 m_output.write(u" "_s.repeated(
505 issueLocationWithContext.beforeText().size() - tabCount)
506 + u"\t"_s.repeated(tabCount)
507 + u"^"_s.repeated(replacement.size()) + u'\n');
513QQmlJSFixSuggestion::QQmlJSFixSuggestion(
const QString &description,
514 const QQmlJS::SourceLocation &location,
515 const QQmlJSDocumentEdit &documentEdit)
516 : QQmlJSFixSuggestion(description, location, QList{ documentEdit })
520QQmlJSFixSuggestion::QQmlJSFixSuggestion(
const QString &description,
521 const QQmlJS::SourceLocation &location,
522 const QList<QQmlJSDocumentEdit> &documentEdits)
523 : m_description{ description }, m_location{ location }, m_documentEdits(documentEdits)
527void QQmlJSFixSuggestion::addDocumentEdit(
const QQmlJSDocumentEdit &documentEdit)
529 m_documentEdits.append(documentEdit);
532void QQmlJSFixSuggestion::setDocumentEdits(
const QList<QQmlJSDocumentEdit> &documentEdits)
534 m_documentEdits = documentEdits;
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)