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 "qqmldomattachedinfo_p.h"
6
7#include <QtCore/QCborMap>
8#include <QtCore/QMutex>
9#include <QtCore/QMutexLocker>
10
11QT_BEGIN_NAMESPACE
12
13namespace QQmlJS {
14namespace Dom {
15
16Q_LOGGING_CATEGORY(domLog, "qt.qmldom", QtWarningMsg);
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(Sink sink) const
41{
42 sink(u"[");
43 sink(groupName());
44 sink(u"]");
45}
46
47void ErrorGroup::dumpId(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(Sink sink) const
74{
75 for (int i = 0; i < groups.size(); ++i)
76 groups.at(i).dump(sink);
77}
78
79void ErrorGroups::dumpId(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(Dumper msg, ErrorLevel level, Path element, QString canonicalFilePath, SourceLocation location) const
142{
143 if (level == ErrorLevel::Fatal)
144 fatal(msg, element, canonicalFilePath, location);
145 return ErrorMessage(dumperToString(writer: msg), *this, level, element, canonicalFilePath, location);
146}
147
148ErrorMessage ErrorGroups::errorMessage(const DiagnosticMessage &msg, Path element, QString canonicalFilePath) const
149{
150 ErrorMessage res(*this, msg, element, canonicalFilePath);
151 if (res.location == SourceLocation()
152 && (res.location.startLine != 0 || res.location.startColumn != 0)) {
153 res.location.offset = -1;
154 res.location.length = 1;
155 }
156 return res;
157}
158
159void ErrorGroups::fatal(Dumper msg, Path element, QStringView canonicalFilePath, SourceLocation location) const
160{
161 enum { FatalMsgMaxLen = 1023 };
162 char buf[FatalMsgMaxLen+1];
163 int ibuf = 0;
164 auto sink = [&ibuf, &buf](QStringView s) {
165 int is = 0;
166 while (ibuf < FatalMsgMaxLen && is < s.size()) {
167 QChar c = s.at(n: is);
168 if (c == QChar::fromLatin1(c: '\n') || c == QChar::fromLatin1(c: '\r') || (c >= QChar::fromLatin1(c: ' ') && c <= QChar::fromLatin1(c: '~')))
169 buf[ibuf++] = c.toLatin1();
170 else
171 buf[ibuf++] = '~';
172 ++is;
173 }
174 };
175 if (!canonicalFilePath.isEmpty()) {
176 sink(canonicalFilePath);
177 sink(u":");
178 }
179 if (location.length) {
180 sinkInt(s: sink, i: location.startLine);
181 sink(u":");
182 sinkInt(s: sink, i: location.startColumn);
183 sink(u":");
184 }
185 dump(sink);
186 msg(sink);
187 if (element.length()>0) {
188 sink(u" for ");
189 element.dump(sink);
190 }
191 buf[ibuf] = 0;
192 qFatal(msg: "%s", buf);
193}
194
195ErrorMessage ErrorGroups::debug(QString message) const
196{
197 return ErrorMessage(message, *this, ErrorLevel::Debug);
198}
199
200ErrorMessage ErrorGroups::debug(Dumper message) const
201{
202 return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Debug);
203}
204
205ErrorMessage ErrorGroups::info(QString message) const
206{
207 return ErrorMessage(message, *this, ErrorLevel::Info);
208}
209
210ErrorMessage ErrorGroups::info(Dumper message) const
211{
212 return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Info);
213}
214
215ErrorMessage ErrorGroups::warning(QString message) const
216{
217 return ErrorMessage(message, *this, ErrorLevel::Warning);
218}
219
220ErrorMessage ErrorGroups::warning(Dumper message) const
221{
222 return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Warning);
223}
224
225ErrorMessage ErrorGroups::error(QString message) const
226{
227 return ErrorMessage(message, *this, ErrorLevel::Error);
228}
229
230ErrorMessage ErrorGroups::error(Dumper message) const
231{
232 return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Error);
233}
234
235int ErrorGroups::cmp(const ErrorGroups &o1, const ErrorGroups &o2)
236{
237 auto &g1 = o1.groups;
238 auto &g2 = o2.groups;
239 if (g1.size() < g2.size())
240 return -1;
241 if (g1.size() < g2.size())
242 return 1;
243 for (int i = 0; i < g1.size(); ++i) {
244 int c = std::strcmp(s1: g1.at(i).groupId().data(), s2: g2.at(i).groupId().data());
245 if (c != 0)
246 return c;
247 }
248 return 0;
249}
250
251ErrorMessage::ErrorMessage(QString msg, ErrorGroups errorGroups, Level level, Path element, QString canonicalFilePath, SourceLocation location, QLatin1String errorId):
252 errorId(errorId), message(msg), errorGroups(errorGroups), level(level), path(element), file(canonicalFilePath), location(location)
253{
254 if (level == Level::Fatal) // we should not end up here, it should have been handled at a higher level already
255 errorGroups.fatal(msg, element, canonicalFilePath, location);
256}
257
258ErrorMessage::ErrorMessage(ErrorGroups errorGroups, const DiagnosticMessage &msg, Path element,
259 QString canonicalFilePath, QLatin1String errorId):
260 errorId(errorId), message(msg.message), errorGroups(errorGroups),
261 level(errorLevelFromQtMsgType(msgType: msg.type)), path(element), file(canonicalFilePath), location(msg.loc)
262{
263 if (level == Level::Fatal) // we should not end up here, it should have been handled at a higher level already
264 errorGroups.fatal(msg: msg.message, element, canonicalFilePath, location);
265}
266
267
268static QBasicMutex *registryMutex()
269{
270 static QBasicMutex rMutex{};
271 return &rMutex;
272}
273
274
275static ErrorGroups myErrors()
276{
277 static ErrorGroups g = {.groups: {NewErrorGroup("ErrorMessage")}};
278 return g;
279}
280
281struct StorableMsg {
282 StorableMsg():
283 msg(QStringLiteral(u"dummy"), myErrors(), ErrorLevel::Error)
284 {}
285
286 StorableMsg(const ErrorMessage &e):
287 msg(e)
288 {}
289
290 ErrorMessage msg;
291};
292
293static QHash<QLatin1String, StorableMsg> &registry()
294{
295 static QHash<QLatin1String, StorableMsg> r;
296 return r;
297}
298
299QLatin1String ErrorMessage::msg(const char *errorId, ErrorMessage err)
300{
301 return msg(errorId: QLatin1String(errorId), err);
302}
303
304QLatin1String ErrorMessage::msg(QLatin1String errorId, ErrorMessage err)
305{
306 bool doubleRegister = false;
307 ErrorMessage old = myErrors().debug(message: u"dummy");
308 {
309 QMutexLocker l(registryMutex());
310 auto &r = registry();
311 if (r.contains(key: err.errorId)) {
312 old = r[err.errorId].msg;
313 doubleRegister = true;
314 }
315 r[errorId] = StorableMsg{err.withErrorId(errorId)};
316 }
317 if (doubleRegister)
318 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())));
319 return errorId;
320}
321
322void ErrorMessage::visitRegisteredMessages(function_ref<bool (ErrorMessage)> visitor)
323{
324 QHash<QLatin1String, StorableMsg> r;
325 {
326 QMutexLocker l(registryMutex());
327 r = registry();
328 }
329 auto it = r.cbegin();
330 auto end = r.cend();
331 while (it != end) {
332 visitor(it->msg);
333 ++it;
334 }
335}
336
337ErrorMessage ErrorMessage::load(QLatin1String errorId)
338{
339 ErrorMessage res = myErrors().error(message: [errorId](Sink s){
340 s(u"Unregistered error ");
341 s(QString(errorId)); });
342 {
343 QMutexLocker l(registryMutex());
344 res = registry().value(key: errorId,defaultValue: res).msg;
345 }
346 return res;
347}
348
349ErrorMessage ErrorMessage::load(const char *errorId)
350{
351 return load(errorId: QLatin1String(errorId));
352}
353
354ErrorMessage &ErrorMessage::withErrorId(QLatin1String errorId)
355{
356 this->errorId = errorId;
357 return *this;
358}
359
360ErrorMessage &ErrorMessage::withPath(const Path &path)
361{
362 this->path = path;
363 return *this;
364}
365
366ErrorMessage &ErrorMessage::withFile(QString f)
367{
368 file=f;
369 return *this;
370}
371
372ErrorMessage &ErrorMessage::withFile(QStringView f)
373{
374 file = f.toString();
375 return *this;
376}
377
378ErrorMessage &ErrorMessage::withLocation(SourceLocation loc)
379{
380 location = loc;
381 return *this;
382}
383
384ErrorMessage &ErrorMessage::withItem(DomItem el)
385{
386 if (path.length() == 0)
387 path = el.canonicalPath();
388 if (file.isEmpty())
389 file = el.canonicalFilePath();
390 if (location == SourceLocation()) {
391 if (const FileLocations *fLocPtr = FileLocations::fileLocationsOf(el)) {
392 location = fLocPtr->regions.value(key: QString(), defaultValue: fLocPtr->fullRegion);
393 }
394 }
395 return *this;
396}
397
398ErrorMessage ErrorMessage::handle(const ErrorHandler &errorHandler)
399{
400 if (errorHandler)
401 errorHandler(*this);
402 else
403 defaultErrorHandler(*this);
404 return *this;
405}
406
407void ErrorMessage::dump(Sink sink) const
408{
409 if (!file.isEmpty()) {
410 sink(file);
411 sink(u":");
412 }
413 if (location.length) {
414 sinkInt(s: sink, i: location.startLine);
415 sink(u":");
416 sinkInt(s: sink, i: location.startColumn);
417 sink(u": ");
418 }
419 errorGroups.dump(sink);
420 sink(u" ");
421 dumpErrorLevel(s: sink, level);
422 if (! errorId.isEmpty()) {
423 sink(u" ");
424 sink(QString(errorId));
425 }
426 sink(u": ");
427 sink(message);
428 if (path.length()>0) {
429 sink(u" for ");
430 if (!file.isEmpty() && path.length() > 3 && path.headKind() == Path::Kind::Root)
431 path.mid(offset: 3).dump(sink);
432 else
433 path.dump(sink);
434 }
435}
436
437QString ErrorMessage::toString() const
438{
439 return dumperToString(writer: [this](Sink sink){ this->dump(sink); });
440}
441
442QCborMap ErrorMessage::toCbor() const
443{
444 return QCborMap({
445 {QStringLiteral(u"errorId"),errorId},
446 {QStringLiteral(u"message"), message},
447 {QStringLiteral(u"errorGroups"), errorGroups.toCbor()},
448 {QStringLiteral(u"level"), int(level)},
449 {QStringLiteral(u"path"), path.toString()},
450 {QStringLiteral(u"file"), file},
451 {QStringLiteral(u"location"), QCborMap({
452 {QStringLiteral(u"offset"),location.offset},
453 {QStringLiteral(u"length"),location.length},
454 {QStringLiteral(u"startLine"),location.startLine},
455 {QStringLiteral(u"startColumn"),location.startColumn}})}
456 });
457}
458
459/*!
460 * \internal
461 * \brief writes an ErrorMessage to QDebug
462 * \param error the error to write
463 */
464void errorToQDebug(const ErrorMessage &error)
465{
466 dumperToQDebug(dumper: [&error](Sink s){ error.dump(sink: s); }, level: error.level);
467}
468
469/*!
470 * \internal
471 * \brief Error handler that ignores all errors (excluding fatal ones)
472 */
473void silentError(const ErrorMessage &)
474{
475}
476
477void errorHandlerHandler(const ErrorMessage &msg, ErrorHandler *h = nullptr)
478{
479 static ErrorHandler handler = &errorToQDebug;
480 if (h) {
481 handler = *h;
482 } else {
483 handler(msg);
484 }
485}
486
487/*!
488 * \internal
489 * \brief Calls the default error handler (by default errorToQDebug)
490 */
491void defaultErrorHandler(const ErrorMessage &error)
492{
493 errorHandlerHandler(msg: error);
494}
495
496/*!
497 * \internal
498 * \brief Sets the default error handler
499 */
500void setDefaultErrorHandler(ErrorHandler h)
501{
502 errorHandlerHandler(msg: ErrorMessage(QString(), ErrorGroups({})), h: &h);
503}
504
505ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType)
506{
507 switch (msgType) {
508 case QtFatalMsg:
509 return ErrorLevel::Fatal;
510 case QtCriticalMsg:
511 return ErrorLevel::Error;
512 case QtWarningMsg:
513 return ErrorLevel::Warning;
514 case QtInfoMsg:
515 return ErrorLevel::Info;
516 case QtDebugMsg:
517 return ErrorLevel::Debug;
518 default:
519 return ErrorLevel::Error;
520 }
521}
522
523} // end namespace Dom
524} // end namespace QQmlJS
525
526QT_END_NAMESPACE
527
528#include "moc_qqmldomerrormessage_p.cpp"
529

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