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
4#include <qglobal.h>
5
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 <qlist.h>
13QT_WARNING_POP
14
15#include "qqmljslogger_p.h"
16#include "qqmljsloggingutils.h"
17#include "qqmlsa_p.h"
18
19#include <QtCore/qfile.h>
20#include <QtCore/qfileinfo.h>
21
22QT_BEGIN_NAMESPACE
23
24using namespace Qt::StringLiterals;
25
26const QQmlSA::LoggerWarningId qmlRequired{ "required" };
27const QQmlSA::LoggerWarningId qmlAliasCycle{ "alias-cycle" };
28const QQmlSA::LoggerWarningId qmlUnresolvedAlias{ "unresolved-alias" };
29const QQmlSA::LoggerWarningId qmlImport{ "import" };
30const QQmlSA::LoggerWarningId qmlRecursionDepthErrors{ "recursion-depth-errors" };
31const QQmlSA::LoggerWarningId qmlWith{ "with" };
32const QQmlSA::LoggerWarningId qmlInheritanceCycle{ "inheritance-cycle" };
33const QQmlSA::LoggerWarningId qmlDeprecated{ "deprecated" };
34const QQmlSA::LoggerWarningId qmlSignalParameters{ "signal-handler-parameters" };
35const QQmlSA::LoggerWarningId qmlMissingType{ "missing-type" };
36const QQmlSA::LoggerWarningId qmlUnresolvedType{ "unresolved-type" };
37const QQmlSA::LoggerWarningId qmlRestrictedType{ "restricted-type" };
38const QQmlSA::LoggerWarningId qmlPrefixedImportType{ "prefixed-import-type" };
39const QQmlSA::LoggerWarningId qmlIncompatibleType{ "incompatible-type" };
40const QQmlSA::LoggerWarningId qmlMissingProperty{ "missing-property" };
41const QQmlSA::LoggerWarningId qmlNonListProperty{ "non-list-property" };
42const QQmlSA::LoggerWarningId qmlReadOnlyProperty{ "read-only-property" };
43const QQmlSA::LoggerWarningId qmlDuplicatePropertyBinding{ "duplicate-property-binding" };
44const QQmlSA::LoggerWarningId qmlDuplicatedName{ "duplicated-name" };
45const QQmlSA::LoggerWarningId qmlDeferredPropertyId{ "deferred-property-id" };
46const QQmlSA::LoggerWarningId qmlUnqualified{ "unqualified" };
47const QQmlSA::LoggerWarningId qmlUnusedImports{ "unused-imports" };
48const QQmlSA::LoggerWarningId qmlMultilineStrings{ "multiline-strings" };
49const QQmlSA::LoggerWarningId qmlSyntax{ "syntax" };
50const QQmlSA::LoggerWarningId qmlSyntaxIdQuotation{ "syntax.id-quotation" };
51const QQmlSA::LoggerWarningId qmlSyntaxDuplicateIds{ "syntax.duplicate-ids" };
52const QQmlSA::LoggerWarningId qmlCompiler{ "compiler" };
53const QQmlSA::LoggerWarningId qmlAttachedPropertyReuse{ "attached-property-reuse" };
54const QQmlSA::LoggerWarningId qmlPlugin{ "plugin" };
55const QQmlSA::LoggerWarningId qmlVarUsedBeforeDeclaration{ "var-used-before-declaration" };
56const QQmlSA::LoggerWarningId qmlInvalidLintDirective{ "invalid-lint-directive" };
57const QQmlSA::LoggerWarningId qmlUseProperFunction{ "use-proper-function" };
58const QQmlSA::LoggerWarningId qmlAccessSingleton{ "access-singleton-via-object" };
59const QQmlSA::LoggerWarningId qmlTopLevelComponent{ "top-level-component" };
60const QQmlSA::LoggerWarningId qmlUncreatableType{ "uncreatable-type" };
61const QQmlSA::LoggerWarningId qmlMissingEnumEntry{ "missing-enum-entry" };
62
63QQmlJSLogger::QQmlJSLogger()
64{
65 static const QList<QQmlJS::LoggerCategory> cats = defaultCategories();
66
67 for (const QQmlJS::LoggerCategory &category : cats)
68 registerCategory(category);
69
70 // setup color output
71 m_output.insertMapping(colorID: QtCriticalMsg, colorCode: QColorOutput::RedForeground);
72 m_output.insertMapping(colorID: QtWarningMsg, colorCode: QColorOutput::PurpleForeground); // Yellow?
73 m_output.insertMapping(colorID: QtInfoMsg, colorCode: QColorOutput::BlueForeground);
74 m_output.insertMapping(colorID: QtDebugMsg, colorCode: QColorOutput::GreenForeground); // None?
75}
76
77const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::defaultCategories()
78{
79 static const QList<QQmlJS::LoggerCategory> cats = {
80 QQmlJS::LoggerCategory{ qmlRequired.name().toString(), QStringLiteral("RequiredProperty"),
81 QStringLiteral("Warn about required properties"), QtWarningMsg },
82 QQmlJS::LoggerCategory{ qmlAliasCycle.name().toString(),
83 QStringLiteral("PropertyAliasCycles"),
84 QStringLiteral("Warn about alias cycles"), QtWarningMsg },
85 QQmlJS::LoggerCategory{ qmlUnresolvedAlias.name().toString(),
86 QStringLiteral("PropertyAliasCycles"),
87 QStringLiteral("Warn about unresolved aliases"), QtWarningMsg },
88 QQmlJS::LoggerCategory{
89 qmlImport.name().toString(), QStringLiteral("ImportFailure"),
90 QStringLiteral("Warn about failing imports and deprecated qmltypes"),
91 QtWarningMsg },
92 QQmlJS::LoggerCategory{
93 qmlRecursionDepthErrors.name().toString(), QStringLiteral("ImportFailure"),
94 QStringLiteral("Warn about failing imports and deprecated qmltypes"), QtWarningMsg,
95 false, true },
96 QQmlJS::LoggerCategory{ qmlWith.name().toString(), QStringLiteral("WithStatement"),
97 QStringLiteral("Warn about with statements as they can cause false "
98 "positives when checking for unqualified access"),
99 QtWarningMsg },
100 QQmlJS::LoggerCategory{ qmlInheritanceCycle.name().toString(),
101 QStringLiteral("InheritanceCycle"),
102 QStringLiteral("Warn about inheritance cycles"), QtWarningMsg },
103 QQmlJS::LoggerCategory{ qmlDeprecated.name().toString(), QStringLiteral("Deprecated"),
104 QStringLiteral("Warn about deprecated properties and types"),
105 QtWarningMsg },
106 QQmlJS::LoggerCategory{
107 qmlSignalParameters.name().toString(), QStringLiteral("BadSignalHandlerParameters"),
108 QStringLiteral("Warn about bad signal handler parameters"), QtWarningMsg },
109 QQmlJS::LoggerCategory{ qmlMissingType.name().toString(), QStringLiteral("MissingType"),
110 QStringLiteral("Warn about missing types"), QtWarningMsg },
111 QQmlJS::LoggerCategory{ qmlUnresolvedType.name().toString(),
112 QStringLiteral("UnresolvedType"),
113 QStringLiteral("Warn about unresolved types"), QtWarningMsg },
114 QQmlJS::LoggerCategory{ qmlRestrictedType.name().toString(),
115 QStringLiteral("RestrictedType"),
116 QStringLiteral("Warn about restricted types"), QtWarningMsg },
117 QQmlJS::LoggerCategory{ qmlPrefixedImportType.name().toString(),
118 QStringLiteral("PrefixedImportType"),
119 QStringLiteral("Warn about prefixed import types"), QtWarningMsg },
120 QQmlJS::LoggerCategory{ qmlIncompatibleType.name().toString(),
121 QStringLiteral("IncompatibleType"),
122 QStringLiteral("Warn about missing types"), QtWarningMsg },
123 QQmlJS::LoggerCategory{ qmlMissingProperty.name().toString(),
124 QStringLiteral("MissingProperty"),
125 QStringLiteral("Warn about missing properties"), QtWarningMsg },
126 QQmlJS::LoggerCategory{ qmlNonListProperty.name().toString(),
127 QStringLiteral("NonListProperty"),
128 QStringLiteral("Warn about non-list properties"), QtWarningMsg },
129 QQmlJS::LoggerCategory{
130 qmlReadOnlyProperty.name().toString(), QStringLiteral("ReadOnlyProperty"),
131 QStringLiteral("Warn about writing to read-only properties"), QtWarningMsg },
132 QQmlJS::LoggerCategory{ qmlDuplicatePropertyBinding.name().toString(),
133 QStringLiteral("DuplicatePropertyBinding"),
134 QStringLiteral("Warn about duplicate property bindings"),
135 QtWarningMsg },
136 QQmlJS::LoggerCategory{
137 qmlDuplicatedName.name().toString(), QStringLiteral("DuplicatedName"),
138 QStringLiteral("Warn about duplicated property/signal names"), QtWarningMsg },
139 QQmlJS::LoggerCategory{
140 qmlDeferredPropertyId.name().toString(), QStringLiteral("DeferredPropertyId"),
141 QStringLiteral(
142 "Warn about making deferred properties immediate by giving them an id."),
143 QtInfoMsg, true, true },
144 QQmlJS::LoggerCategory{
145 qmlUnqualified.name().toString(), QStringLiteral("UnqualifiedAccess"),
146 QStringLiteral("Warn about unqualified identifiers and how to fix them"),
147 QtWarningMsg },
148 QQmlJS::LoggerCategory{ qmlUnusedImports.name().toString(), QStringLiteral("UnusedImports"),
149 QStringLiteral("Warn about unused imports"), QtInfoMsg },
150 QQmlJS::LoggerCategory{ qmlMultilineStrings.name().toString(),
151 QStringLiteral("MultilineStrings"),
152 QStringLiteral("Warn about multiline strings"), QtInfoMsg },
153 QQmlJS::LoggerCategory{ qmlSyntax.name().toString(), QString(),
154 QStringLiteral("Syntax errors"), QtWarningMsg, false, true },
155 QQmlJS::LoggerCategory{ qmlSyntaxIdQuotation.name().toString(), QString(),
156 QStringLiteral("ID quotation"), QtWarningMsg, false, true },
157 QQmlJS::LoggerCategory{ qmlSyntaxDuplicateIds.name().toString(), QString(),
158 QStringLiteral("ID duplication"), QtCriticalMsg, false, true },
159 QQmlJS::LoggerCategory{ qmlCompiler.name().toString(), QStringLiteral("CompilerWarnings"),
160 QStringLiteral("Warn about compiler issues"), QtWarningMsg, true },
161 QQmlJS::LoggerCategory{
162 qmlAttachedPropertyReuse.name().toString(), QStringLiteral("AttachedPropertyReuse"),
163 QStringLiteral("Warn if attached types from parent components "
164 "aren't reused. This is handled by the QtQuick "
165 "lint plugin. Use Quick.AttachedPropertyReuse instead."),
166 QtCriticalMsg, true },
167 QQmlJS::LoggerCategory{ qmlPlugin.name().toString(), QStringLiteral("LintPluginWarnings"),
168 QStringLiteral("Warn if a qmllint plugin finds an issue"),
169 QtWarningMsg, true },
170 QQmlJS::LoggerCategory{ qmlVarUsedBeforeDeclaration.name().toString(),
171 QStringLiteral("VarUsedBeforeDeclaration"),
172 QStringLiteral("Warn if a variable is used before declaration"),
173 QtWarningMsg },
174 QQmlJS::LoggerCategory{
175 qmlInvalidLintDirective.name().toString(), QStringLiteral("InvalidLintDirective"),
176 QStringLiteral("Warn if an invalid qmllint comment is found"), QtWarningMsg },
177 QQmlJS::LoggerCategory{
178 qmlUseProperFunction.name().toString(), QStringLiteral("UseProperFunction"),
179 QStringLiteral("Warn if var is used for storing functions"), QtWarningMsg },
180 QQmlJS::LoggerCategory{
181 qmlAccessSingleton.name().toString(), QStringLiteral("AccessSingletonViaObject"),
182 QStringLiteral("Warn if a singleton is accessed via an object"), QtWarningMsg },
183 QQmlJS::LoggerCategory{
184 qmlTopLevelComponent.name().toString(), QStringLiteral("TopLevelComponent"),
185 QStringLiteral("Fail when a top level Component are encountered"), QtWarningMsg },
186 QQmlJS::LoggerCategory{
187 qmlUncreatableType.name().toString(), QStringLiteral("UncreatableType"),
188 QStringLiteral("Warn if uncreatable types are created"), QtWarningMsg }
189 };
190
191 return cats;
192}
193
194bool QQmlJSFixSuggestion::operator==(const QQmlJSFixSuggestion &other) const
195{
196 return m_location == other.m_location && m_fixDescription == other.m_fixDescription
197 && m_replacement == other.m_replacement && m_filename == other.m_filename
198 && m_hint == other.m_hint && m_autoApplicable == other.m_autoApplicable;
199}
200
201bool QQmlJSFixSuggestion::operator!=(const QQmlJSFixSuggestion &other) const
202{
203 return !(*this == other);
204}
205
206QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories() const
207{
208 return m_categories.values();
209}
210
211void QQmlJSLogger::registerCategory(const QQmlJS::LoggerCategory &category)
212{
213 if (m_categories.contains(key: category.name())) {
214 qWarning() << "Trying to re-register existing logger category" << category.name();
215 return;
216 }
217
218 m_categoryLevels[category.name()] = category.level();
219 m_categoryIgnored[category.name()] = category.isIgnored();
220 m_categories.insert(key: category.name(), value: category);
221}
222
223static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
224{
225 static QHash<QtMsgType, int> level = { { QtDebugMsg, 0 },
226 { QtInfoMsg, 1 },
227 { QtWarningMsg, 2 },
228 { QtCriticalMsg, 3 },
229 { QtFatalMsg, 4 } };
230 return level[a] < level[b];
231}
232
233void QQmlJSLogger::log(const QString &message, QQmlJS::LoggerWarningId id,
234 const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext,
235 bool showFileName, const std::optional<QQmlJSFixSuggestion> &suggestion,
236 const QString overrideFileName)
237{
238 Q_ASSERT(m_categoryLevels.contains(id.name().toString()));
239
240 if (isCategoryIgnored(id))
241 return;
242
243 // Note: assume \a type is the type we should prefer for logging
244
245 if (srcLocation.isValid()
246 && m_ignoredWarnings[srcLocation.startLine].contains(value: id.name().toString()))
247 return;
248
249 QString prefix;
250
251 if ((!overrideFileName.isEmpty() || !m_fileName.isEmpty()) && showFileName)
252 prefix =
253 (!overrideFileName.isEmpty() ? overrideFileName : m_fileName) + QStringLiteral(":");
254
255 if (srcLocation.isValid())
256 prefix += QStringLiteral("%1:%2:").arg(a: srcLocation.startLine).arg(a: srcLocation.startColumn);
257
258 if (!prefix.isEmpty())
259 prefix.append(c: QLatin1Char(' '));
260
261 // Note: we do the clamping to [Info, Critical] range since our logger only
262 // supports 3 categories
263 type = std::clamp(val: type, lo: QtInfoMsg, hi: QtCriticalMsg, comp: isMsgTypeLess);
264
265 // Note: since we clamped our \a type, the output message is not printed
266 // exactly like it was requested, bear with us
267 m_output.writePrefixedMessage(message: u"%1%2 [%3]"_s.arg(args&: prefix, args: message, args: id.name().toString()), type);
268
269 Message diagMsg;
270 diagMsg.message = message;
271 diagMsg.id = id.name();
272 diagMsg.loc = srcLocation;
273 diagMsg.type = type;
274 diagMsg.fixSuggestion = suggestion;
275
276 switch (type) {
277 case QtWarningMsg: m_warnings.push_back(t: diagMsg); break;
278 case QtCriticalMsg: m_errors.push_back(t: diagMsg); break;
279 case QtInfoMsg: m_infos.push_back(t: diagMsg); break;
280 default: break;
281 }
282
283 if (srcLocation.length > 0 && !m_code.isEmpty() && showContext)
284 printContext(overrideFileName, location: srcLocation);
285
286 if (suggestion.has_value())
287 printFix(fix: suggestion.value());
288}
289
290void QQmlJSLogger::processMessages(const QList<QQmlJS::DiagnosticMessage> &messages,
291 QQmlJS::LoggerWarningId id)
292{
293 if (messages.isEmpty() || isCategoryIgnored(id))
294 return;
295
296 m_output.write(QStringLiteral("---\n"));
297
298 // TODO: we should instead respect message's category here (potentially, it
299 // should hold a category instead of type)
300 for (const QQmlJS::DiagnosticMessage &message : messages)
301 log(message: message.message, id, srcLocation: QQmlJS::SourceLocation(), showContext: false, showFileName: false);
302
303 m_output.write(QStringLiteral("---\n\n"));
304}
305
306void QQmlJSLogger::printContext(const QString &overrideFileName,
307 const QQmlJS::SourceLocation &location)
308{
309 QString code = m_code;
310
311 if (!overrideFileName.isEmpty() && overrideFileName != QFileInfo(m_fileName).absolutePath()) {
312 QFile file(overrideFileName);
313 const bool success = file.open(flags: QFile::ReadOnly);
314 Q_ASSERT(success);
315 code = QString::fromUtf8(ba: file.readAll());
316 }
317
318 IssueLocationWithContext issueLocationWithContext { code, location };
319 if (const QStringView beforeText = issueLocationWithContext.beforeText(); !beforeText.isEmpty())
320 m_output.write(message: beforeText);
321
322 bool locationMultiline = issueLocationWithContext.issueText().contains(c: QLatin1Char('\n'));
323
324 if (!issueLocationWithContext.issueText().isEmpty())
325 m_output.write(message: issueLocationWithContext.issueText().toString(), color: QtCriticalMsg);
326 m_output.write(message: issueLocationWithContext.afterText().toString() + QLatin1Char('\n'));
327
328 // Do not draw location indicator for multiline locations
329 if (locationMultiline)
330 return;
331
332 int tabCount = issueLocationWithContext.beforeText().count(c: QLatin1Char('\t'));
333 int locationLength = location.length == 0 ? 1 : location.length;
334 m_output.write(message: QString::fromLatin1(ba: " ").repeated(times: issueLocationWithContext.beforeText().size()
335 - tabCount)
336 + QString::fromLatin1(ba: "\t").repeated(times: tabCount)
337 + QString::fromLatin1(ba: "^").repeated(times: locationLength) + QLatin1Char('\n'));
338}
339
340void QQmlJSLogger::printFix(const QQmlJSFixSuggestion &fixItem)
341{
342 const QString currentFileAbsPath = QFileInfo(m_fileName).absolutePath();
343 QString code = m_code;
344 QString currentFile;
345 m_output.writePrefixedMessage(message: fixItem.fixDescription(), type: QtInfoMsg);
346
347 if (!fixItem.location().isValid())
348 return;
349
350 const QString filename = fixItem.filename();
351 if (filename == currentFile) {
352 // Nothing to do in this case, we've already read the code
353 } else if (filename.isEmpty() || filename == currentFileAbsPath) {
354 code = m_code;
355 } else {
356 QFile file(filename);
357 const bool success = file.open(flags: QFile::ReadOnly);
358 Q_ASSERT(success);
359 code = QString::fromUtf8(ba: file.readAll());
360 currentFile = filename;
361 }
362
363 IssueLocationWithContext issueLocationWithContext { code, fixItem.location() };
364
365 if (const QStringView beforeText = issueLocationWithContext.beforeText();
366 !beforeText.isEmpty()) {
367 m_output.write(message: beforeText);
368 }
369
370 // The replacement string can be empty if we're only pointing something out to the user
371 const QString replacement = fixItem.replacement();
372 QStringView replacementString = replacement.isEmpty()
373 ? issueLocationWithContext.issueText()
374 : replacement;
375
376 // But if there's nothing to change it cannot be auto-applied
377 Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
378
379 m_output.write(message: replacementString, color: QtDebugMsg);
380 m_output.write(message: issueLocationWithContext.afterText().toString() + u'\n');
381
382 int tabCount = issueLocationWithContext.beforeText().count(c: u'\t');
383
384 // Do not draw location indicator for multiline replacement strings
385 if (replacementString.contains(c: u'\n'))
386 return;
387
388 m_output.write(message: u" "_s.repeated(
389 times: issueLocationWithContext.beforeText().size() - tabCount)
390 + u"\t"_s.repeated(times: tabCount)
391 + u"^"_s.repeated(times: replacement.size()) + u'\n');
392}
393
394QQmlJSFixSuggestion::QQmlJSFixSuggestion(const QString &fixDescription,
395 const QQmlJS::SourceLocation &location,
396 const QString &replacement)
397 : m_location{ location }, m_fixDescription{ fixDescription }, m_replacement{ replacement }
398{
399}
400
401QT_END_NAMESPACE
402

source code of qtdeclarative/src/qmlcompiler/qqmljslogger.cpp