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
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 bool doubleRegister = false;
329 ErrorMessage old = myErrors().debug(message: u"dummy");
330 {
331 QMutexLocker l(registryMutex());
332 auto &r = registry();
333 if (r.contains(key: err.errorId)) {
334 old = r[err.errorId].msg;
335 doubleRegister = true;
336 }
337 r[errorId] = StorableMsg{std::move(err.withErrorId(errorId))};
338 }
339 if (doubleRegister)
340 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())));
341 return errorId;
342}
343
344void ErrorMessage::visitRegisteredMessages(function_ref<bool(const ErrorMessage &)> visitor)
345{
346 QHash<QLatin1String, StorableMsg> r;
347 {
348 QMutexLocker l(registryMutex());
349 r = registry();
350 }
351 auto it = r.cbegin();
352 auto end = r.cend();
353 while (it != end) {
354 visitor(it->msg);
355 ++it;
356 }
357}
358
359ErrorMessage ErrorMessage::load(QLatin1String errorId)
360{
361 ErrorMessage res = myErrors().error(message: [errorId](const Sink &s){
362 s(u"Unregistered error ");
363 s(QString(errorId)); });
364 {
365 QMutexLocker l(registryMutex());
366 res = registry().value(key: errorId,defaultValue: res).msg;
367 }
368 return res;
369}
370
371ErrorMessage ErrorMessage::load(const char *errorId)
372{
373 return load(errorId: QLatin1String(errorId));
374}
375
376ErrorMessage &ErrorMessage::withErrorId(QLatin1String errorId)
377{
378 this->errorId = errorId;
379 return *this;
380}
381
382ErrorMessage &ErrorMessage::withPath(const Path &path)
383{
384 this->path = path;
385 return *this;
386}
387
388ErrorMessage &ErrorMessage::withFile(const QString &f)
389{
390 file=f;
391 return *this;
392}
393
394ErrorMessage &ErrorMessage::withFile(QStringView f)
395{
396 file = f.toString();
397 return *this;
398}
399
400ErrorMessage &ErrorMessage::withLocation(SourceLocation loc)
401{
402 location = loc;
403 return *this;
404}
405
406ErrorMessage &ErrorMessage::withItem(const DomItem &el)
407{
408 if (path.length() == 0)
409 path = el.canonicalPath();
410 if (file.isEmpty())
411 file = el.canonicalFilePath();
412 if (location == SourceLocation()) {
413 if (const FileLocations::Tree tree = FileLocations::treeOf(el)) {
414 location = FileLocations::region(fLoc: tree, region: MainRegion);
415 }
416 }
417 return *this;
418}
419
420ErrorMessage ErrorMessage::handle(const ErrorHandler &errorHandler)
421{
422 if (errorHandler)
423 errorHandler(*this);
424 else
425 defaultErrorHandler(*this);
426 return *this;
427}
428
429void ErrorMessage::dump(const Sink &sink) const
430{
431 if (!file.isEmpty()) {
432 sink(file);
433 sink(u":");
434 }
435 if (location.length) {
436 sinkInt(s: sink, i: location.startLine);
437 sink(u":");
438 sinkInt(s: sink, i: location.startColumn);
439 sink(u": ");
440 }
441 errorGroups.dump(sink);
442 sink(u" ");
443 dumpErrorLevel(s: sink, level);
444 if (! errorId.isEmpty()) {
445 sink(u" ");
446 sink(QString(errorId));
447 }
448 sink(u": ");
449 sink(message);
450 if (path.length()>0) {
451 sink(u" for ");
452 if (!file.isEmpty() && path.length() > 3 && path.headKind() == Path::Kind::Root)
453 path.mid(offset: 3).dump(sink);
454 else
455 path.dump(sink);
456 }
457}
458
459QString ErrorMessage::toString() const
460{
461 return dumperToString(writer: [this](const Sink &sink){ this->dump(sink); });
462}
463
464QCborMap ErrorMessage::toCbor() const
465{
466 return QCborMap({
467 {QStringLiteral(u"errorId"),errorId},
468 {QStringLiteral(u"message"), message},
469 {QStringLiteral(u"errorGroups"), errorGroups.toCbor()},
470 {QStringLiteral(u"level"), int(level)},
471 {QStringLiteral(u"path"), path.toString()},
472 {QStringLiteral(u"file"), file},
473 {QStringLiteral(u"location"), QCborMap({
474 {QStringLiteral(u"offset"),location.offset},
475 {QStringLiteral(u"length"),location.length},
476 {QStringLiteral(u"startLine"),location.startLine},
477 {QStringLiteral(u"startColumn"),location.startColumn}})}
478 });
479}
480
481/*!
482 * \internal
483 * \brief writes an ErrorMessage to QDebug
484 * \param error the error to write
485 */
486void errorToQDebug(const ErrorMessage &error)
487{
488 dumperToQDebug(dumper: [&error](const Sink &s){ error.dump(sink: s); }, level: error.level);
489}
490
491/*!
492 * \internal
493 * \brief Error handler that ignores all errors (excluding fatal ones)
494 */
495void silentError(const ErrorMessage &)
496{
497}
498
499void errorHandlerHandler(const ErrorMessage &msg, const ErrorHandler *h = nullptr)
500{
501 static ErrorHandler handler = &errorToQDebug;
502 if (h) {
503 handler = *h;
504 } else {
505 handler(msg);
506 }
507}
508
509/*!
510 * \internal
511 * \brief Calls the default error handler (by default errorToQDebug)
512 */
513void defaultErrorHandler(const ErrorMessage &error)
514{
515 errorHandlerHandler(msg: error);
516}
517
518/*!
519 * \internal
520 * \brief Sets the default error handler
521 */
522void setDefaultErrorHandler(const ErrorHandler &h)
523{
524 errorHandlerHandler(msg: ErrorMessage(QString(), ErrorGroups({})), h: &h);
525}
526
527ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType)
528{
529 switch (msgType) {
530 case QtFatalMsg:
531 return ErrorLevel::Fatal;
532 case QtCriticalMsg:
533 return ErrorLevel::Error;
534 case QtWarningMsg:
535 return ErrorLevel::Warning;
536 case QtInfoMsg:
537 return ErrorLevel::Info;
538 case QtDebugMsg:
539 return ErrorLevel::Debug;
540 default:
541 return ErrorLevel::Error;
542 }
543}
544
545} // end namespace Dom
546} // end namespace QQmlJS
547
548QT_END_NAMESPACE
549
550#include "moc_qqmldomerrormessage_p.cpp"
551

Provided by KDAB

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

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