1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#ifndef QQMLJSLOGGER_P_H
5#define QQMLJSLOGGER_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <qtqmlcompilerexports.h>
19
20#include "qcoloroutput_p.h"
21#include "qqmljsloggingutils_p.h"
22
23#include <private/qqmljsdiagnosticmessage_p.h>
24
25#include <QtCore/qhash.h>
26#include <QtCore/qmap.h>
27#include <QtCore/qstring.h>
28#include <QtCore/qlist.h>
29#include <QtCore/qset.h>
30#include <QtCore/QLoggingCategory>
31
32#include <optional>
33
34QT_BEGIN_NAMESPACE
35
36/*!
37 \internal
38 Used to print the line containing the location of a certain error
39 */
40class Q_QMLCOMPILER_EXPORT IssueLocationWithContext
41{
42public:
43 /*!
44 \internal
45 \param code: The whole text of a translation unit
46 \param location: The location where an error occurred.
47 */
48 IssueLocationWithContext(QStringView code, const QQmlJS::SourceLocation &location) {
49 quint32 before = qMax(a: 0, b: code.lastIndexOf(c: QLatin1Char('\n'), from: location.offset));
50
51 if (before != 0 && before < location.offset)
52 before++;
53
54 m_beforeText = code.mid(pos: before, n: location.offset - before);
55 m_issueText = code.mid(pos: location.offset, n: location.length);
56 int after = code.indexOf(c: QLatin1Char('\n'), from: location.offset + location.length);
57 m_afterText = code.mid(pos: location.offset + location.length,
58 n: after - (location.offset+location.length));
59 }
60
61 // returns start of the line till first character of location
62 QStringView beforeText() const { return m_beforeText; }
63 // returns the text at location
64 QStringView issueText() const { return m_issueText; }
65 // returns any text after location until the end of the line is reached
66 QStringView afterText() const { return m_afterText; }
67
68private:
69 QStringView m_beforeText;
70 QStringView m_issueText;
71 QStringView m_afterText;
72};
73
74class Q_QMLCOMPILER_EXPORT QQmlJSFixSuggestion
75{
76public:
77 QQmlJSFixSuggestion() = default;
78 QQmlJSFixSuggestion(const QString &fixDescription, const QQmlJS::SourceLocation &location,
79 const QString &replacement = QString());
80
81 QString fixDescription() const { return m_fixDescription; }
82 QQmlJS::SourceLocation location() const { return m_location; }
83 QString replacement() const { return m_replacement; }
84
85 void setFilename(const QString &filename) { m_filename = filename; }
86 QString filename() const { return m_filename; }
87
88 void setHint(const QString &hint) { m_hint = hint; }
89 QString hint() const { return m_hint; }
90
91 void setAutoApplicable(bool autoApply = true) { m_autoApplicable = autoApply; }
92 bool isAutoApplicable() const { return m_autoApplicable; }
93
94 bool operator==(const QQmlJSFixSuggestion &) const;
95 bool operator!=(const QQmlJSFixSuggestion &) const;
96
97private:
98 QQmlJS::SourceLocation m_location;
99 QString m_fixDescription;
100 QString m_replacement;
101 QString m_filename;
102 QString m_hint;
103 bool m_autoApplicable = false;
104};
105
106struct Message : public QQmlJS::DiagnosticMessage
107{
108 enum class CompilationStatus { Normal, Skip, Error };
109
110 // This doesn't need to be an owning-reference since the string is expected to outlive any
111 // Message object by virtue of coming from a LoggerWarningId.
112 QAnyStringView id;
113 std::optional<QQmlJSFixSuggestion> fixSuggestion;
114 CompilationStatus compilationStatus = CompilationStatus::Normal;
115 std::optional<quint32> customLineForDisabling = std::nullopt;
116
117 quint32 lineForDisabling() const { return customLineForDisabling.value_or(u: loc.startLine); }
118};
119
120class Q_QMLCOMPILER_EXPORT QQmlJSLogger
121{
122 Q_DISABLE_COPY_MOVE(QQmlJSLogger)
123public:
124 QList<QQmlJS::LoggerCategory> categories() const;
125 static const QList<QQmlJS::LoggerCategory> &defaultCategories();
126
127 void registerCategory(const QQmlJS::LoggerCategory &category);
128
129 QQmlJSLogger();
130 ~QQmlJSLogger() = default;
131
132 bool hasWarnings() const { return m_numWarnings > 0; }
133 bool hasErrors() const { return m_numErrors > 0; }
134
135 qsizetype numWarnings() const { return m_numWarnings; }
136 qsizetype numErrors() const { return m_numErrors; }
137
138 template<typename F>
139 void iterateCurrentFunctionMessages(F &&f) const
140 {
141 for (const Message &msg : m_currentFunctionMessages)
142 f(msg);
143 }
144
145 template<typename F>
146 void iterateAllMessages(F &&f) const
147 {
148 for (const Message &msg : m_archivedMessages)
149 f(msg);
150
151 for (const Message &msg : m_currentFunctionMessages)
152 f(msg);
153 }
154
155 QtMsgType categoryLevel(QQmlJS::LoggerWarningId id) const
156 {
157 return m_categoryLevels[id.name().toString()];
158 }
159 void setCategoryLevel(QQmlJS::LoggerWarningId id, QtMsgType level)
160 {
161 m_categoryLevels[id.name().toString()] = level;
162 m_categoryChanged[id.name().toString()] = true;
163 }
164
165 bool isCategoryIgnored(QQmlJS::LoggerWarningId id) const
166 {
167 return m_categoryIgnored[id.name().toString()];
168 }
169 void setCategoryIgnored(QQmlJS::LoggerWarningId id, bool error)
170 {
171 m_categoryIgnored[id.name().toString()] = error;
172 m_categoryChanged[id.name().toString()] = true;
173 }
174
175 bool isCategoryFatal(QQmlJS::LoggerWarningId id) const
176 {
177 return m_categoryFatal[id.name().toString()];
178 }
179 void setCategoryFatal(QQmlJS::LoggerWarningId id, bool error)
180 {
181 m_categoryFatal[id.name().toString()] = error;
182 m_categoryChanged[id.name().toString()] = true;
183 }
184
185 bool wasCategoryChanged(QQmlJS::LoggerWarningId id) const
186 {
187 return m_categoryChanged[id.name().toString()];
188 }
189
190 QtMsgType compileErrorLevel() const { return m_compileErrorLevel; }
191 void setCompileErrorLevel(QtMsgType level) { m_compileErrorLevel = level; }
192
193 QString compileErrorPrefix() const { return m_compileErrorPrefix; }
194 void setCompileErrorPrefix(const QString &prefix) { m_compileErrorPrefix = prefix; }
195
196 QString compileSkipPrefix() const { return m_compileSkipPrefix; }
197 void setCompileSkipPrefix(const QString &prefix) { m_compileSkipPrefix = prefix; }
198
199 /*! \internal
200
201 Logs \a message with severity deduced from \a category. Prefer using
202 this function in most cases.
203
204 \sa setCategoryLevel
205 */
206 void log(const QString &message, QQmlJS::LoggerWarningId id,
207 const QQmlJS::SourceLocation &srcLocation, bool showContext = true,
208 bool showFileName = true, const std::optional<QQmlJSFixSuggestion> &suggestion = {},
209 const QString overrideFileName = QString(),
210 std::optional<quint32> customLineForDisabling = std::nullopt)
211 {
212 log(diagMsg: Message {
213 QQmlJS::DiagnosticMessage {
214 .message: message,
215 .type: m_categoryLevels[id.name().toString()],
216 .loc: srcLocation,
217 },
218 .id: id.name(),
219 .fixSuggestion: suggestion,
220 .compilationStatus: Message::CompilationStatus::Normal,
221 .customLineForDisabling: customLineForDisabling
222 }, showContext, showFileName, overrideFileName);
223 }
224
225 void logCompileError(const QString &message, const QQmlJS::SourceLocation &srcLocation)
226 {
227 if (m_inTransaction)
228 m_hasPendingCompileError = true;
229 else
230 m_hasCompileError = true;
231
232 log(diagMsg: Message {
233 QQmlJS::DiagnosticMessage {
234 .message: m_compileErrorPrefix + message,
235 .type: m_compileErrorLevel,
236 .loc: srcLocation
237 },
238 .id: qmlCompiler.name(),
239 .fixSuggestion: {}, // fixSuggestion
240 .compilationStatus: Message::CompilationStatus::Error
241 });
242
243 }
244
245 void logCompileSkip(const QString &message, const QQmlJS::SourceLocation &srcLocation)
246 {
247 m_hasCompileSkip = true;
248 log(diagMsg: Message {
249 QQmlJS::DiagnosticMessage {
250 .message: m_compileSkipPrefix + message,
251 .type: m_compileSkipLevel,
252 .loc: srcLocation
253 },
254 .id: qmlCompiler.name(),
255 .fixSuggestion: {}, // fixSuggestion
256 .compilationStatus: Message::CompilationStatus::Skip
257 });
258 }
259
260 void processMessages(const QList<QQmlJS::DiagnosticMessage> &messages,
261 const QQmlJS::LoggerWarningId id,
262 const QQmlJS::SourceLocation &sourceLocation = QQmlJS::SourceLocation{});
263
264 void ignoreWarnings(uint32_t line, const QSet<QString> &categories)
265 {
266 m_ignoredWarnings[line] = categories;
267 }
268
269 void setSilent(bool silent) { m_output.setSilent(silent); }
270 bool isSilent() const { return m_output.isSilent(); }
271
272 /*!
273 \internal
274 The logger is disabled when warnings are not relevant, for example when the import visitor runs
275 on a dependency of a linted file. In that case, the warnings should not be created, and
276 expensive QQmlJSUtils::didYouMean call can be saved.
277
278 setSilent() has a different behavior: a silent logger can still be used to process messages as
279 JSON, for example, while a disabled logger won't contain any message.
280 */
281 void setIsDisabled(bool isDisabled) { m_isDisabled = isDisabled; }
282 bool isDisabled() const { return m_isDisabled; }
283
284 void setCode(const QString &code) { m_code = code; }
285 QString code() const { return m_code; }
286
287 void setFilePath(const QString &filePath) { m_filePath = filePath; }
288 QString filePath() const { return m_filePath; }
289
290 bool currentFunctionHasCompileError() const
291 {
292 return m_hasCompileError || m_hasPendingCompileError;
293 }
294
295 bool currentFunctionWasSkipped() const
296 {
297 return m_hasCompileSkip;
298 }
299
300 bool currentFunctionHasErrorOrSkip() const
301 {
302 return currentFunctionHasCompileError() || currentFunctionWasSkipped();
303 }
304
305 QString currentFunctionCompileErrorMessage() const
306 {
307 for (const Message &message : m_currentFunctionMessages) {
308 if (message.compilationStatus == Message::CompilationStatus::Error)
309 return message.message;
310 }
311
312 return QString();
313 }
314
315 QString currentFunctionCompileSkipMessage() const
316 {
317 for (const Message &message : m_currentFunctionMessages) {
318 if (message.compilationStatus == Message::CompilationStatus::Skip)
319 return message.message;
320 }
321
322 return QString();
323 }
324
325 void startTransaction();
326 void commit();
327 void rollback();
328
329 void finalizeFuction();
330
331private:
332 QMap<QString, QQmlJS::LoggerCategory> m_categories;
333
334 void printContext(const QString &overrideFileName, const QQmlJS::SourceLocation &location);
335 void printFix(const QQmlJSFixSuggestion &fix);
336
337 void log(Message diagMsg, bool showContext = false, bool showFileName = true,
338 const QString overrideFileName = QString());
339
340 void countMessage(const Message &message);
341
342 QString m_filePath;
343 QString m_code;
344
345 QColorOutput m_output;
346
347 QHash<QString, QtMsgType> m_categoryLevels;
348 QHash<QString, bool> m_categoryIgnored;
349
350 // If true, triggers qFatal on documents with "pragma Strict"
351 // TODO: Works only for qmlCompiler category so far.
352 QHash<QString, bool> m_categoryFatal;
353
354 QHash<QString, bool> m_categoryChanged;
355
356 QList<Message> m_pendingMessages;
357 QList<Message> m_currentFunctionMessages;
358 QList<Message> m_archivedMessages;
359 QHash<uint32_t, QSet<QString>> m_ignoredWarnings;
360
361 QString m_compileErrorPrefix;
362 QString m_compileSkipPrefix;
363
364 qsizetype m_numWarnings = 0;
365 qsizetype m_numErrors = 0;
366 bool m_inTransaction = false;
367 bool m_hasCompileError = false;
368 bool m_hasPendingCompileError = false;
369 bool m_hasCompileSkip = false;
370 bool m_isDisabled = false;
371
372 QtMsgType m_compileErrorLevel = QtWarningMsg;
373 QtMsgType m_compileSkipLevel = QtInfoMsg;
374};
375
376QT_END_NAMESPACE
377
378#endif // QQMLJSLOGGER_P_H
379

source code of qtdeclarative/src/qmlcompiler/qqmljslogger_p.h