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 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | Q_LOGGING_CATEGORY(domLog, "qt.qmldom", QtWarningMsg); |
14 | |
15 | namespace QQmlJS { |
16 | namespace Dom { |
17 | |
18 | enum { |
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 | |
36 | Every group has a unique string identifying it (the \l{groupId}), and it should be a string that can |
37 | be translated to get the local name. The best way to acheive this is to create new groups using |
38 | the NewErrorGroup macro. |
39 | */ |
40 | void ErrorGroup::dump(const Sink &sink) const |
41 | { |
42 | sink(u"["); |
43 | sink(groupName()); |
44 | sink(u"]"); |
45 | } |
46 | |
47 | void ErrorGroup::dumpId(const Sink &sink) const |
48 | { |
49 | sink(u"["); |
50 | sink(QString(groupId())); |
51 | sink(u"]"); |
52 | } |
53 | |
54 | QLatin1String ErrorGroup::groupId() const |
55 | { |
56 | return QLatin1String(m_groupId); |
57 | } |
58 | |
59 | QString 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 | |
69 | The simplest way to create new ErrorMessages is to have an ErrorGroups instance, |
70 | and use it to create new ErrorMessages using its debug, warning, error,... methods |
71 | */ |
72 | |
73 | void ErrorGroups::dump(const Sink &sink) const |
74 | { |
75 | for (int i = 0; i < groups.size(); ++i) |
76 | groups.at(i).dump(sink); |
77 | } |
78 | |
79 | void ErrorGroups::dumpId(const Sink &sink) const |
80 | { |
81 | for (int i = 0; i < groups.size(); ++i) |
82 | groups.at(i).dumpId(sink); |
83 | } |
84 | |
85 | QCborArray 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 | |
98 | The error messages *should* be translated, but they do not need to be pre registered. |
99 | To give a meaningful handling of error messages ErrorMessages have "tags" (ErrorGroup) that are |
100 | grouped toghether in ErrorGroups. |
101 | |
102 | To create an ErrorMessage from scratch the best way is to use one of the methods provided by |
103 | an ErrorGroups object. |
104 | For example create an ErrorGroups called myErrors and use it to create all your errors. |
105 | \code |
106 | static ErrorGroups myErrors(){ |
107 | static ErrorGroups res({NewErrorGroup("StaticAnalysis"), NewErrorGroup("FancyDetector")}); |
108 | return res; |
109 | } |
110 | \endcode |
111 | |
112 | You can preregister the errors giving them a unique name (reverse dns notation is encouraged) with |
113 | the msg function. |
114 | This unique name (errorId) is a const char* (QLatin1String) to integrate better with the tr function. |
115 | Ideally you create variables to store the errorId either by creating variables with plain strings |
116 | that you use to initialize the error messages |
117 | \code |
118 | // in .h file |
119 | constexpr const char *myError0 = "my.company.error0"; |
120 | // in some initialization function |
121 | ErrorMessage::msg(myError0, myErrors().warning(tr("Error number 0"))); |
122 | \endcode |
123 | or using the result of the msg function |
124 | \code |
125 | // in cpp file |
126 | static auto myError1 = ErrorMessage::msg("my.company.error1", myErrors().warning(tr("Error number 1"))); |
127 | static auto myError2 = ErrorMessage::msg("my.company.error2", myErrors().error(tr("Error number 2 on %1"))); |
128 | \endcode |
129 | and then use them like this |
130 | \code |
131 | ErrorMessage::load(myError2, QLatin1String("extra info")).handle(errorHandler); |
132 | \endcode |
133 | or using directly the string (more error prone) |
134 | \code |
135 | errorHandler(ErrorMessage::load(QLatin1String("my.company.error1"))); |
136 | \endcode |
137 | |
138 | The \l{withItem} method can be used to set the path file and location if not aready set. |
139 | */ |
140 | |
141 | ErrorMessage 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 | |
150 | ErrorMessage 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 | |
161 | void 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 | |
199 | ErrorMessage ErrorGroups::debug(const QString &message) const |
200 | { |
201 | return ErrorMessage(message, *this, ErrorLevel::Debug); |
202 | } |
203 | |
204 | ErrorMessage ErrorGroups::debug(const Dumper &message) const |
205 | { |
206 | return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Debug); |
207 | } |
208 | |
209 | ErrorMessage ErrorGroups::info(const QString &message) const |
210 | { |
211 | return ErrorMessage(message, *this, ErrorLevel::Info); |
212 | } |
213 | |
214 | ErrorMessage ErrorGroups::info(const Dumper &message) const |
215 | { |
216 | return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Info); |
217 | } |
218 | |
219 | ErrorMessage ErrorGroups::warning(const QString &message) const |
220 | { |
221 | return ErrorMessage(message, *this, ErrorLevel::Warning); |
222 | } |
223 | |
224 | ErrorMessage ErrorGroups::warning(const Dumper &message) const |
225 | { |
226 | return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Warning); |
227 | } |
228 | |
229 | ErrorMessage ErrorGroups::error(const QString &message) const |
230 | { |
231 | return ErrorMessage(message, *this, ErrorLevel::Error); |
232 | } |
233 | |
234 | ErrorMessage ErrorGroups::error(const Dumper &message) const |
235 | { |
236 | return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Error); |
237 | } |
238 | |
239 | int 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 | |
255 | ErrorMessage::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 | |
270 | ErrorMessage::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 | |
286 | static QBasicMutex *registryMutex() |
287 | { |
288 | static QBasicMutex rMutex{}; |
289 | return &rMutex; |
290 | } |
291 | |
292 | |
293 | static ErrorGroups myErrors() |
294 | { |
295 | static ErrorGroups g = {.groups: {NewErrorGroup("ErrorMessage")}}; |
296 | return g; |
297 | } |
298 | |
299 | struct 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 | |
315 | static QHash<QLatin1String, StorableMsg> ®istry() |
316 | { |
317 | static QHash<QLatin1String, StorableMsg> r; |
318 | return r; |
319 | } |
320 | |
321 | QLatin1String ErrorMessage::msg(const char *errorId, ErrorMessage &&err) |
322 | { |
323 | return msg(errorId: QLatin1String(errorId), err: std::move(err)); |
324 | } |
325 | |
326 | QLatin1String 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 | |
344 | void 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 | |
359 | ErrorMessage 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 | |
371 | ErrorMessage ErrorMessage::load(const char *errorId) |
372 | { |
373 | return load(errorId: QLatin1String(errorId)); |
374 | } |
375 | |
376 | ErrorMessage &ErrorMessage::withErrorId(QLatin1String errorId) |
377 | { |
378 | this->errorId = errorId; |
379 | return *this; |
380 | } |
381 | |
382 | ErrorMessage &ErrorMessage::withPath(const Path &path) |
383 | { |
384 | this->path = path; |
385 | return *this; |
386 | } |
387 | |
388 | ErrorMessage &ErrorMessage::withFile(const QString &f) |
389 | { |
390 | file=f; |
391 | return *this; |
392 | } |
393 | |
394 | ErrorMessage &ErrorMessage::withFile(QStringView f) |
395 | { |
396 | file = f.toString(); |
397 | return *this; |
398 | } |
399 | |
400 | ErrorMessage &ErrorMessage::withLocation(SourceLocation loc) |
401 | { |
402 | location = loc; |
403 | return *this; |
404 | } |
405 | |
406 | ErrorMessage &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 | |
420 | ErrorMessage ErrorMessage::handle(const ErrorHandler &errorHandler) |
421 | { |
422 | if (errorHandler) |
423 | errorHandler(*this); |
424 | else |
425 | defaultErrorHandler(*this); |
426 | return *this; |
427 | } |
428 | |
429 | void 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 | |
459 | QString ErrorMessage::toString() const |
460 | { |
461 | return dumperToString(writer: [this](const Sink &sink){ this->dump(sink); }); |
462 | } |
463 | |
464 | QCborMap 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 | */ |
486 | void 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 | */ |
495 | void silentError(const ErrorMessage &) |
496 | { |
497 | } |
498 | |
499 | void 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 | */ |
513 | void defaultErrorHandler(const ErrorMessage &error) |
514 | { |
515 | errorHandlerHandler(msg: error); |
516 | } |
517 | |
518 | /*! |
519 | * \internal |
520 | * \brief Sets the default error handler |
521 | */ |
522 | void setDefaultErrorHandler(const ErrorHandler &h) |
523 | { |
524 | errorHandlerHandler(msg: ErrorMessage(QString(), ErrorGroups({})), h: &h); |
525 | } |
526 | |
527 | ErrorLevel 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 | |
548 | QT_END_NAMESPACE |
549 | |
550 | #include "moc_qqmldomerrormessage_p.cpp" |
551 |
Definitions
- domLog
- dump
- dumpId
- groupId
- groupName
- dump
- dumpId
- toCbor
- errorMessage
- errorMessage
- fatal
- debug
- debug
- info
- info
- warning
- warning
- error
- error
- cmp
- ErrorMessage
- ErrorMessage
- registryMutex
- myErrors
- StorableMsg
- StorableMsg
- StorableMsg
- StorableMsg
- registry
- msg
- msg
- visitRegisteredMessages
- load
- load
- withErrorId
- withPath
- withFile
- withFile
- withLocation
- withItem
- handle
- dump
- toString
- toCbor
- errorToQDebug
- silentError
- errorHandlerHandler
- defaultErrorHandler
- setDefaultErrorHandler
Learn to use CMake with our Intro Training
Find out more