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 | namespace QQmlJS { |
14 | namespace Dom { |
15 | |
16 | Q_LOGGING_CATEGORY(domLog, "qt.qmldom" , QtWarningMsg); |
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(Sink sink) const |
41 | { |
42 | sink(u"[" ); |
43 | sink(groupName()); |
44 | sink(u"]" ); |
45 | } |
46 | |
47 | void ErrorGroup::dumpId(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(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(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(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 | |
148 | ErrorMessage 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 | |
159 | void 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 | |
195 | ErrorMessage ErrorGroups::debug(QString message) const |
196 | { |
197 | return ErrorMessage(message, *this, ErrorLevel::Debug); |
198 | } |
199 | |
200 | ErrorMessage ErrorGroups::debug(Dumper message) const |
201 | { |
202 | return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Debug); |
203 | } |
204 | |
205 | ErrorMessage ErrorGroups::info(QString message) const |
206 | { |
207 | return ErrorMessage(message, *this, ErrorLevel::Info); |
208 | } |
209 | |
210 | ErrorMessage ErrorGroups::info(Dumper message) const |
211 | { |
212 | return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Info); |
213 | } |
214 | |
215 | ErrorMessage ErrorGroups::warning(QString message) const |
216 | { |
217 | return ErrorMessage(message, *this, ErrorLevel::Warning); |
218 | } |
219 | |
220 | ErrorMessage ErrorGroups::warning(Dumper message) const |
221 | { |
222 | return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Warning); |
223 | } |
224 | |
225 | ErrorMessage ErrorGroups::error(QString message) const |
226 | { |
227 | return ErrorMessage(message, *this, ErrorLevel::Error); |
228 | } |
229 | |
230 | ErrorMessage ErrorGroups::error(Dumper message) const |
231 | { |
232 | return ErrorMessage(dumperToString(writer: message), *this, ErrorLevel::Error); |
233 | } |
234 | |
235 | int 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 | |
251 | ErrorMessage::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 | |
258 | ErrorMessage::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 | |
268 | static QBasicMutex *registryMutex() |
269 | { |
270 | static QBasicMutex rMutex{}; |
271 | return &rMutex; |
272 | } |
273 | |
274 | |
275 | static ErrorGroups myErrors() |
276 | { |
277 | static ErrorGroups g = {.groups: {NewErrorGroup("ErrorMessage" )}}; |
278 | return g; |
279 | } |
280 | |
281 | struct 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 | |
293 | static QHash<QLatin1String, StorableMsg> ®istry() |
294 | { |
295 | static QHash<QLatin1String, StorableMsg> r; |
296 | return r; |
297 | } |
298 | |
299 | QLatin1String ErrorMessage::msg(const char *errorId, ErrorMessage err) |
300 | { |
301 | return msg(errorId: QLatin1String(errorId), err); |
302 | } |
303 | |
304 | QLatin1String 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 | |
322 | void 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 | |
337 | ErrorMessage 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 | |
349 | ErrorMessage ErrorMessage::load(const char *errorId) |
350 | { |
351 | return load(errorId: QLatin1String(errorId)); |
352 | } |
353 | |
354 | ErrorMessage &ErrorMessage::withErrorId(QLatin1String errorId) |
355 | { |
356 | this->errorId = errorId; |
357 | return *this; |
358 | } |
359 | |
360 | ErrorMessage &ErrorMessage::withPath(const Path &path) |
361 | { |
362 | this->path = path; |
363 | return *this; |
364 | } |
365 | |
366 | ErrorMessage &ErrorMessage::withFile(QString f) |
367 | { |
368 | file=f; |
369 | return *this; |
370 | } |
371 | |
372 | ErrorMessage &ErrorMessage::withFile(QStringView f) |
373 | { |
374 | file = f.toString(); |
375 | return *this; |
376 | } |
377 | |
378 | ErrorMessage &ErrorMessage::withLocation(SourceLocation loc) |
379 | { |
380 | location = loc; |
381 | return *this; |
382 | } |
383 | |
384 | ErrorMessage &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 | |
398 | ErrorMessage ErrorMessage::handle(const ErrorHandler &errorHandler) |
399 | { |
400 | if (errorHandler) |
401 | errorHandler(*this); |
402 | else |
403 | defaultErrorHandler(*this); |
404 | return *this; |
405 | } |
406 | |
407 | void 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 | |
437 | QString ErrorMessage::toString() const |
438 | { |
439 | return dumperToString(writer: [this](Sink sink){ this->dump(sink); }); |
440 | } |
441 | |
442 | QCborMap 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 | */ |
464 | void 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 | */ |
473 | void silentError(const ErrorMessage &) |
474 | { |
475 | } |
476 | |
477 | void 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 | */ |
491 | void defaultErrorHandler(const ErrorMessage &error) |
492 | { |
493 | errorHandlerHandler(msg: error); |
494 | } |
495 | |
496 | /*! |
497 | * \internal |
498 | * \brief Sets the default error handler |
499 | */ |
500 | void setDefaultErrorHandler(ErrorHandler h) |
501 | { |
502 | errorHandlerHandler(msg: ErrorMessage(QString(), ErrorGroups({})), h: &h); |
503 | } |
504 | |
505 | ErrorLevel 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 | |
526 | QT_END_NAMESPACE |
527 | |
528 | #include "moc_qqmldomerrormessage_p.cpp" |
529 | |