1// Copyright (C) 2023 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 "qqmljsloggingutils_p.h"
5
6#include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h>
7#include <QtCore/qcommandlineparser.h>
8
9QT_BEGIN_NAMESPACE
10
11using namespace Qt::StringLiterals;
12
13/*!
14 \class QQmlSA::LoggerWarningId
15 \inmodule QtQmlCompiler
16
17 \brief A wrapper around a string literal to uniquely identify
18 warning categories in the \c{QQmlSA} framework.
19*/
20
21/*!
22 \fn QQmlSA::LoggerWarningId::LoggerWarningId(QAnyStringView name)
23 Constructs a LoggerWarningId object with logging catergory name \a name.
24*/
25
26/*!
27 \fn QAnyStringView QQmlSA::LoggerWarningId::name() const
28 Returns the name of the wrapped warning category.
29*/
30
31namespace QQmlJS {
32
33LoggerCategory::LoggerCategory() : d_ptr{ new LoggerCategoryPrivate } { }
34
35LoggerCategory::LoggerCategory(QString name, QString settingsName, QString description,
36 QtMsgType level, bool ignored, bool isDefault)
37 : d_ptr{ new LoggerCategoryPrivate }
38{
39 Q_D(LoggerCategory);
40 d->m_name = name;
41 d->m_settingsName = settingsName;
42 d->m_description = description;
43 d->m_level = level;
44 d->m_ignored = ignored;
45 d->m_isDefault = isDefault;
46}
47
48LoggerCategory::LoggerCategory(const LoggerCategory &other)
49 : d_ptr{ new LoggerCategoryPrivate{ *other.d_func() } }
50{
51}
52
53LoggerCategory::LoggerCategory(LoggerCategory &&) noexcept = default;
54
55LoggerCategory &LoggerCategory::operator=(const LoggerCategory &other)
56{
57 *d_func() = *other.d_func();
58 return *this;
59}
60
61LoggerCategory &LoggerCategory::operator=(LoggerCategory &&) noexcept = default;
62
63LoggerCategory::~LoggerCategory() = default;
64
65QString LoggerCategory::name() const
66{
67 Q_D(const LoggerCategory);
68 return d->m_name;
69}
70
71QString LoggerCategory::settingsName() const
72{
73 Q_D(const LoggerCategory);
74 return d->m_settingsName;
75}
76
77QString LoggerCategory::description() const
78{
79 Q_D(const LoggerCategory);
80 return d->m_description;
81}
82
83QtMsgType LoggerCategory::level() const
84{
85 Q_D(const LoggerCategory);
86 return d->m_level;
87}
88
89bool LoggerCategory::isIgnored() const
90{
91 Q_D(const LoggerCategory);
92 return d->m_ignored;
93}
94
95bool LoggerCategory::isDefault() const
96{
97 Q_D(const LoggerCategory);
98 return d->m_isDefault;
99}
100
101LoggerWarningId LoggerCategory::id() const
102{
103 Q_D(const LoggerCategory);
104 return d->id();
105}
106
107void LoggerCategory::setLevel(QtMsgType type)
108{
109 Q_D(LoggerCategory);
110 d->setLevel(type);
111}
112
113void LoggerCategoryPrivate::setLevel(QtMsgType type)
114{
115 if (m_level == type)
116 return;
117
118 m_level = type;
119 m_changed = true;
120}
121
122void LoggerCategory::setIgnored(bool isIgnored)
123{
124 Q_D(LoggerCategory);
125 d->setIgnored(isIgnored);
126}
127
128void LoggerCategoryPrivate::setIgnored(bool isIgnored)
129{
130 if (m_ignored == isIgnored)
131 return;
132
133 m_ignored = isIgnored;
134 m_changed = true;
135}
136
137bool LoggerCategoryPrivate::hasChanged() const
138{
139 return m_changed;
140}
141
142LoggerCategoryPrivate *LoggerCategoryPrivate::get(LoggerCategory *loggerCategory)
143{
144 Q_ASSERT(loggerCategory);
145 return loggerCategory->d_func();
146}
147
148namespace LoggingUtils {
149
150QString levelToString(const QQmlJS::LoggerCategory &category)
151{
152 if (category.isIgnored())
153 return QStringLiteral("disable");
154
155 switch (category.level()) {
156 case QtInfoMsg:
157 return QStringLiteral("info");
158 case QtWarningMsg:
159 return QStringLiteral("warning");
160 case QtCriticalMsg:
161 return QStringLiteral("error");
162 default:
163 Q_UNREACHABLE();
164 break;
165 }
166};
167
168static QStringList settingsNamesForCategory(const LoggerCategory &category)
169{
170 const QString name = category.settingsName();
171 const QStringList result{ QStringLiteral("Warnings/") += name,
172 QStringLiteral("Warnings/") += name.sliced(pos: name.indexOf(ch: u'.') + 1) };
173 return result;
174}
175
176static QString lookInSettings(const LoggerCategory &category, const QQmlToolingSettings &settings,
177 const QString &settingsName)
178{
179 if (settings.isSet(name: settingsName))
180 return settings.value(name: settingsName).toString();
181 static constexpr QLatin1String propertyAliasCyclesKey = "Warnings/PropertyAliasCycles"_L1;
182
183 // keep compatibility to deprecated settings
184 if (category.name() == qmlAliasCycle.name() || category.name() == qmlUnresolvedAlias.name()) {
185 if (settings.isSet(name: propertyAliasCyclesKey)) {
186 qWarning()
187 << "Detected deprecated setting name \"PropertyAliasCycles\". Use %1 or %2 instead."_L1
188 .arg(args: qmlAliasCycle.name(), args: qmlUnresolvedAlias.name());
189 return settings.value(name: propertyAliasCyclesKey).toString();
190 }
191 }
192 return {};
193}
194
195static QString levelValueForCategory(const LoggerCategory &category,
196 const QQmlToolingSettings &settings,
197 QCommandLineParser *parser)
198{
199 const QString key = category.id().name().toString();
200 if (parser && parser->isSet(name: key))
201 return parser->value(name: key);
202
203 const QStringList settingsName = settingsNamesForCategory(category);
204 for (const QString &settingsName : settingsName) {
205 const QString value = lookInSettings(category, settings, settingsName);
206 if (value.isEmpty())
207 continue;
208
209 // Do not try to set the levels if it's due to a default config option.
210 // This way we can tell which options have actually been overwritten by the user.
211 if (levelToString(category) == value)
212 return QString();
213
214 return value;
215 }
216 return QString();
217}
218
219bool applyLevelToCategory(const QStringView level, LoggerCategory &category)
220{
221 // you can't downgrade errors
222 if (category.level() == QtCriticalMsg && !category.isIgnored() && level != "error"_L1)
223 return false;
224
225 if (level == "disable"_L1) {
226 category.setLevel(QtCriticalMsg);
227 category.setIgnored(true);
228 return true;
229 }
230 if (level == "info"_L1) {
231 category.setLevel(QtInfoMsg);
232 category.setIgnored(false);
233 return true;
234 }
235 if (level == "warning"_L1) {
236 category.setLevel(QtWarningMsg);
237 category.setIgnored(false);
238 return true;
239 }
240 if (level == "error"_L1) {
241 category.setLevel(QtCriticalMsg);
242 category.setIgnored(false);
243 return true;
244 }
245
246 return false;
247};
248
249/*!
250\internal
251Sets the category levels from a settings file and an optional parser.
252Calls \c {parser->showHelp(-1)} for invalid logging levels.
253*/
254void updateLogLevels(QList<LoggerCategory> &categories,
255 const QQmlToolingSettings &settings,
256 QCommandLineParser *parser)
257{
258 bool success = true;
259 for (auto &category : categories) {
260 if (category.isDefault())
261 continue;
262
263 const QString value = levelValueForCategory(category, settings, parser);
264 if (value.isEmpty())
265 continue;
266
267 if (!applyLevelToCategory(level: value, category)) {
268 qWarning() << "Invalid logging level" << value << "provided for"
269 << category.id().name().toString()
270 << "(allowed are: disable, info, warning, error)\n."
271 "You can't change categories that have level \"error\" by default.";
272 success = false;
273 }
274 }
275 if (!success && parser)
276 parser->showHelp(exitCode: -1);
277}
278} // namespace LoggingUtils
279
280} // namespace QQmlJS
281
282QT_END_NAMESPACE
283

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