1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3#include "qqmldomitem_p.h"
4#include "qqmldomstringdumper_p.h"
5#include "qqmldomfilelocations_p.h"
6
7#include <QtCore/QCborMap>
8#include <QtCore/QMutex>
9#include <QtCore/QMutexLocker>
10
11QT_BEGIN_NAMESPACE
12
13Q_LOGGING_CATEGORY(domLog, "qt.qmldom", QtWarningMsg);
14
15namespace QQmlJS {
16namespace Dom {
17
18enum {
19 FatalMsgMaxLen=511
20};
21
22/*!
23\internal
24\macro NewErrorGroup
25
26\param groupId a double qouted string giving the groupId for this group
27
28\brief convenience macro creating a new ErrorGroup and registering its groupId as translatable string
29*/
30
31/*!
32\internal
33\class QQmlJS::Dom::ErrorGroup
34\brief Represents a tag grouping a set of related error messages, it can be used to disable them
35
36Every group has a unique string identifying it (the \l{groupId}), and it should be a string that can
37be translated to get the local name. The best way to acheive this is to create new groups using
38the NewErrorGroup macro.
39 */
40void ErrorGroup::dump(const Sink &sink) const
41{
42 sink(u"[");
43 sink(groupName());
44 sink(u"]");
45}
46
47void ErrorGroup::dumpId(const Sink &sink) const
48{
49 sink(u"[");
50 sink(QString(groupId()));
51 sink(u"]");
52}
53
54QLatin1String ErrorGroup::groupId() const
55{
56 return QLatin1String(m_groupId);
57}
58
59QString ErrorGroup::groupName() const
60{
61 return tr(sourceText: m_groupId);
62}
63
64/*!
65\internal
66\class QQmlJS::Dom::ErrorGroups
67\brief Represents a set of tags grouping a set of related error messages
68
69The simplest way to create new ErrorMessages is to have an ErrorGroups instance,
70and use it to create new ErrorMessages using its debug, warning, error,... methods
71 */
72
73void ErrorGroups::dump(const Sink &sink) const
74{
75 for (int i = 0; i < groups.size(); ++i)
76 groups.at(i).dump(sink);
77}
78
79void ErrorGroups::dumpId(const Sink &sink) const
80{
81 for (int i = 0; i < groups.size(); ++i)
82 groups.at(i).dumpId(sink);
83}
84
85QCborArray ErrorGroups::toCbor() const
86{
87 QCborArray res;
88 for (int i = 0; i < groups.size(); ++i)
89 res.append(value: QCborValue(groups.at(i).groupId()));
90 return res;
91}
92
93/*!
94\internal
95\class QQmlJS::Dom::ErrorMessage
96\brief Represents an error message connected to the dom
97
98The error messages *should* be translated, but they do not need to be pre registered.
99To give a meaningful handling of error messages ErrorMessages have "tags" (ErrorGroup) that are
100grouped toghether in ErrorGroups.
101
102To create an ErrorMessage from scratch the best way is to use one of the methods provided by
103an ErrorGroups object.
104For example create an ErrorGroups called myErrors and use it to create all your errors.
105\code
106static ErrorGroups myErrors(){
107 static ErrorGroups res({NewErrorGroup("StaticAnalysis"), NewErrorGroup("FancyDetector")});
108 return res;
109}
110\endcode
111
112You can preregister the errors giving them a unique name (reverse dns notation is encouraged) with
113the msg function.
114This unique name (errorId) is a const char* (QLatin1String) to integrate better with the tr function.
115Ideally you create variables to store the errorId either by creating variables with plain strings
116that you use to initialize the error messages
117\code
118// in .h file
119constexpr const char *myError0 = "my.company.error0";
120// in some initialization function
121ErrorMessage::msg(myError0, myErrors().warning(tr("Error number 0")));
122\endcode
123or using the result of the msg function
124\code
125// in cpp file
126static auto myError1 = ErrorMessage::msg("my.company.error1", myErrors().warning(tr("Error number 1")));
127static auto myError2 = ErrorMessage::msg("my.company.error2", myErrors().error(tr("Error number 2 on %1")));
128\endcode
129and then use them like this
130\code
131ErrorMessage::load(myError2, QLatin1String("extra info")).handle(errorHandler);
132\endcode
133or using directly the string (more error prone)
134\code
135errorHandler(ErrorMessage::load(QLatin1String("my.company.error1")));
136\endcode
137
138The \l{withItem} method can be used to set the path file and location if not aready set.
139 */
140
141ErrorMessage ErrorGroups::errorMessage(
142 const Dumper &msg, ErrorLevel level, const Path &element, const QString &canonicalFilePath,
143 SourceLocation location) const
144{
145 if (level == ErrorLevel::Fatal)
146 fatal(msg, element, canonicalFilePath, location);
147 return ErrorMessage(dumperToString(writer: msg), *this, level, element, canonicalFilePath, location);
148}
149
150ErrorMessage ErrorGroups::errorMessage(const DiagnosticMessage &msg, const Path &element, const QString &canonicalFilePath) const
151{
152 ErrorMessage res(*this, msg, element, canonicalFilePath);
153 if (res.location == SourceLocation()
154 && (res.location.startLine != 0 || res.location.startColumn != 0)) {
155 res.location.offset = -1;
156 res.location.length = 1;
157 }
158 return res;
159}
160
161void ErrorGroups::fatal(
162 const Dumper &msg, const Path &element, QStringView canonicalFilePath,
163 SourceLocation location) const
164{
165 enum { FatalMsgMaxLen = 1023 };
166 char buf[FatalMsgMaxLen+1];
167 int ibuf = 0;
168 auto sink = [&ibuf, &buf](QStringView s) {
169 int is = 0;
170 while (ibuf < FatalMsgMaxLen && is < s.size()) {
171 QChar c = s.at(n: is);
172 if (c == QChar::fromLatin1(c: '\n') || c == QChar::fromLatin1(c: '\r') || (c >= QChar::fromLatin1(c: ' ') && c <= QChar::fromLatin1(c: '~')))
173 buf[ibuf++] = c.toLatin1();
174 else
175 buf[ibuf++] = '~';
176 ++is;
177 }
178 };
179 if (!canonicalFilePath.isEmpty()) {
180 sink(canonicalFilePath);
181 sink(u":");
182 }
183 if (location.length) {
184 sinkInt(s: sink, i: location.startLine);
185 sink(u":");
186 sinkInt(s: sink, i: location.startColumn);
187 sink(u":");
188 }
189 dump(sink);
190 msg(sink);
191 if (element.length()>0) {
192 sink(u" for ");
193 element.dump(sink);
194 }
195 buf[ibuf] = 0;
196 qFatal(msg: "%s", buf);
197}
198
199ErrorMessage ErrorGroups::debug(const QString &message) const
200{
201 return ErrorMessage(message, *this, ErrorLevel::Debug);
202}
203
204ErrorMessage ErrorGroups::debug(const Dumper &message) const
205{
206 return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Debug);
207}
208
209ErrorMessage ErrorGroups::info(const QString &message) const
210{
211 return ErrorMessage(message, *this, ErrorLevel::Info);
212}
213
214ErrorMessage ErrorGroups::info(const Dumper &message) const
215{
216 return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Info);
217}
218
219ErrorMessage ErrorGroups::warning(const QString &message) const
220{
221 return ErrorMessage(message, *this, ErrorLevel::Warning);
222}
223
224ErrorMessage ErrorGroups::warning(const Dumper &message) const
225{
226 return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Warning);
227}
228
229ErrorMessage ErrorGroups::error(const QString &message) const
230{
231 return ErrorMessage(message, *this, ErrorLevel::Error);
232}
233
234ErrorMessage ErrorGroups::error(const Dumper &message) const
235{
236 return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Error);
237}
238
239int ErrorGroups::cmp(const ErrorGroups &o1, const ErrorGroups &o2)
240{
241 auto &g1 = o1.groups;
242 auto &g2 = o2.groups;
243 if (g1.size() < g2.size())
244 return -1;
245 if (g1.size() < g2.size())
246 return 1;
247 for (int i = 0; i < g1.size(); ++i) {
248 int c = std::strcmp(s1: g1.at(i).groupId().data(), s2: g2.at(i).groupId().data());
249 if (c != 0)
250 return c;
251 }
252 return 0;
253}
254
255ErrorMessage::ErrorMessage(
256 const QString &msg, const ErrorGroups &errorGroups, Level level, const Path &element,
257 const QString &canonicalFilePath, SourceLocation location, QLatin1String errorId)
258 : errorId(errorId)
259 , message(msg)
260 , errorGroups(errorGroups)
261 , level(level)
262 , path(element)
263 , file(canonicalFilePath)
264 , location(location)
265{
266 if (level == Level::Fatal) // we should not end up here, it should have been handled at a higher level already
267 errorGroups.fatal(msg, element, canonicalFilePath, location);
268}
269
270ErrorMessage::ErrorMessage(
271 const ErrorGroups &errorGroups, const DiagnosticMessage &msg, const Path &element,
272 const QString &canonicalFilePath, QLatin1String errorId)
273 : errorId(errorId)
274 , message(msg.message)
275 , errorGroups(errorGroups)
276 , level(errorLevelFromQtMsgType(msgType: msg.type))
277 , path(element)
278 , file(canonicalFilePath)
279 , location(msg.loc)
280{
281 if (level == Level::Fatal) // we should not end up here, it should have been handled at a higher level already
282 errorGroups.fatal(msg: msg.message, element, canonicalFilePath, location);
283}
284
285
286static QBasicMutex *registryMutex()
287{
288 static QBasicMutex rMutex{};
289 return &rMutex;
290}
291
292
293static ErrorGroups myErrors()
294{
295 static ErrorGroups g = {.groups: {NewErrorGroup("ErrorMessage")}};
296 return g;
297}
298
299struct StorableMsg {
300 StorableMsg():
301 msg(QStringLiteral(u"dummy"), myErrors(), ErrorLevel::Error)
302 {}
303
304 StorableMsg(const ErrorMessage &e):
305 msg(e)
306 {}
307
308 StorableMsg(ErrorMessage &&e):
309 msg(std::move(e))
310 {}
311
312 ErrorMessage msg;
313};
314
315static QHash<QLatin1String, StorableMsg> &registry()
316{
317 static QHash<QLatin1String, StorableMsg> r;
318 return r;
319}
320
321QLatin1String ErrorMessage::msg(const char *errorId, ErrorMessage &&err)
322{
323 return msg(errorId: QLatin1String(errorId), err: std::move(err));
324}
325
326QLatin1String ErrorMessage::msg(QLatin1String errorId, ErrorMessage &&err)
327{
328 using namespace Qt::StringLiterals;
329 bool doubleRegister = false;
330 ErrorMessage old = myErrors().debug(message: u"dummy"_sv);
331 {
332 QMutexLocker l(registryMutex());
333 auto &r = registry();
334 if (r.contains(key: err.errorId)) {
335 old = r[err.errorId].msg;
336 doubleRegister = true;
337 }
338 r[errorId] = StorableMsg{std::move(err.withErrorId(errorId))};
339 }
340 if (doubleRegister)
341 defaultErrorHandler(myErrors().warning(message: tr(sourceText: "Double registration of error %1: (%2) vs (%3)").arg(args&: errorId, args: err.withErrorId(errorId).toString(), args: old.toString())));
342 return errorId;
343}
344
345void ErrorMessage::visitRegisteredMessages(function_ref<bool(const ErrorMessage &)> visitor)
346{
347 QHash<QLatin1String, StorableMsg> r;
348 {
349 QMutexLocker l(registryMutex());
350 r = registry();
351 }
352 auto it = r.cbegin();
353 auto end = r.cend();
354 while (it != end) {
355 visitor(it->msg);
356 ++it;
357 }
358}
359
360ErrorMessage ErrorMessage::load(QLatin1String errorId)
361{
362 ErrorMessage res = myErrors().error(message: [errorId](const Sink &s){
363 s(u"Unregistered error ");
364 s(QString(errorId)); });
365 {
366 QMutexLocker l(registryMutex());
367 res = registry().value(key: errorId,defaultValue: res).msg;
368 }
369 return res;
370}
371
372ErrorMessage ErrorMessage::load(const char *errorId)
373{
374 return load(errorId: QLatin1String(errorId));
375}
376
377ErrorMessage &ErrorMessage::withErrorId(QLatin1String errorId)
378{
379 this->errorId = errorId;
380 return *this;
381}
382
383ErrorMessage &ErrorMessage::withPath(const Path &path)
384{
385 this->path = path;
386 return *this;
387}
388
389ErrorMessage &ErrorMessage::withFile(const QString &f)
390{
391 file=f;
392 return *this;
393}
394
395ErrorMessage &ErrorMessage::withFile(QStringView f)
396{
397 file = f.toString();
398 return *this;
399}
400
401ErrorMessage &ErrorMessage::withLocation(SourceLocation loc)
402{
403 location = loc;
404 return *this;
405}
406
407ErrorMessage &ErrorMessage::withItem(const DomItem &el)
408{
409 if (path.length() == 0)
410 path = el.canonicalPath();
411 if (file.isEmpty())
412 file = el.canonicalFilePath();
413 if (location == SourceLocation()) {
414 if (const FileLocations::Tree tree = FileLocations::treeOf(el)) {
415 location = FileLocations::region(fLoc: tree, region: MainRegion);
416 }
417 }
418 return *this;
419}
420
421ErrorMessage ErrorMessage::handle(const ErrorHandler &errorHandler)
422{
423 if (errorHandler)
424 errorHandler(*this);
425 else
426 defaultErrorHandler(*this);
427 return *this;
428}
429
430void ErrorMessage::dump(const Sink &sink) const
431{
432 if (!file.isEmpty()) {
433 sink(file);
434 sink(u":");
435 }
436 if (location.length) {
437 sinkInt(s: sink, i: location.startLine);
438 sink(u":");
439 sinkInt(s: sink, i: location.startColumn);
440 sink(u": ");
441 }
442 errorGroups.dump(sink);
443 sink(u" ");
444 dumpErrorLevel(s: sink, level);
445 if (! errorId.isEmpty()) {
446 sink(u" ");
447 sink(QString(errorId));
448 }
449 sink(u": ");
450 sink(message);
451 if (path.length()>0) {
452 sink(u" for ");
453 if (!file.isEmpty() && path.length() > 3 && path.headKind() == Path::Kind::Root)
454 path.mid(offset: 3).dump(sink);
455 else
456 path.dump(sink);
457 }
458}
459
460QString ErrorMessage::toString() const
461{
462 return dumperToString(writer: [this](const Sink &sink){ this->dump(sink); });
463}
464
465QCborMap ErrorMessage::toCbor() const
466{
467 return QCborMap({
468 {QStringLiteral(u"errorId"),errorId},
469 {QStringLiteral(u"message"), message},
470 {QStringLiteral(u"errorGroups"), errorGroups.toCbor()},
471 {QStringLiteral(u"level"), int(level)},
472 {QStringLiteral(u"path"), path.toString()},
473 {QStringLiteral(u"file"), file},
474 {QStringLiteral(u"location"), QCborMap({
475 {QStringLiteral(u"offset"),location.offset},
476 {QStringLiteral(u"length"),location.length},
477 {QStringLiteral(u"startLine"),location.startLine},
478 {QStringLiteral(u"startColumn"),location.startColumn}})}
479 });
480}
481
482/*!
483 * \internal
484 * \brief writes an ErrorMessage to QDebug
485 * \param error the error to write
486 */
487void errorToQDebug(const ErrorMessage &error)
488{
489 dumperToQDebug(dumper: [&error](const Sink &s){ error.dump(sink: s); }, level: error.level);
490}
491
492/*!
493 * \internal
494 * \brief Error handler that ignores all errors (excluding fatal ones)
495 */
496void silentError(const ErrorMessage &)
497{
498}
499
500void errorHandlerHandler(const ErrorMessage &msg, const ErrorHandler *h = nullptr)
501{
502 static ErrorHandler handler = &errorToQDebug;
503 if (h) {
504 handler = *h;
505 } else {
506 handler(msg);
507 }
508}
509
510/*!
511 * \internal
512 * \brief Calls the default error handler (by default errorToQDebug)
513 */
514void defaultErrorHandler(const ErrorMessage &error)
515{
516 errorHandlerHandler(msg: error);
517}
518
519/*!
520 * \internal
521 * \brief Sets the default error handler
522 */
523void setDefaultErrorHandler(const ErrorHandler &h)
524{
525 errorHandlerHandler(msg: ErrorMessage(QString(), ErrorGroups({})), h: &h);
526}
527
528ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType)
529{
530 switch (msgType) {
531 case QtFatalMsg:
532 return ErrorLevel::Fatal;
533 case QtCriticalMsg:
534 return ErrorLevel::Error;
535 case QtWarningMsg:
536 return ErrorLevel::Warning;
537 case QtInfoMsg:
538 return ErrorLevel::Info;
539 case QtDebugMsg:
540 return ErrorLevel::Debug;
541 default:
542 return ErrorLevel::Error;
543 }
544}
545
546} // end namespace Dom
547} // end namespace QQmlJS
548
549QT_END_NAMESPACE
550
551#include "moc_qqmldomerrormessage_p.cpp"
552

source code of qtdeclarative/src/qmldom/qqmldomerrormessage.cpp