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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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