Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qqmldirparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
4#include "qqmldirparser_p.h"
5
6#include <QtCore/QtDebug>
7
9
10static int parseInt(QStringView str, bool *ok)
11{
12 int pos = 0;
13 int number = 0;
14 while (pos < str.size() && str.at(pos).isDigit()) {
15 if (pos != 0)
16 number *= 10;
17 number += str.at(pos).unicode() - '0';
18 ++pos;
19 }
20 if (pos != str.size())
21 *ok = false;
22 else
23 *ok = true;
24 return number;
25}
26
28{
29 const int dotIndex = str.indexOf(QLatin1Char('.'));
30 if (dotIndex != -1 && str.indexOf(QLatin1Char('.'), dotIndex + 1) == -1) {
31 bool ok = false;
32 const int major = parseInt(QStringView(str).left(dotIndex), &ok);
33 if (!ok) return QTypeRevision();
34 const int minor = parseInt(QStringView(str).mid(dotIndex + 1, str.size() - dotIndex - 1), &ok);
35 return ok ? QTypeRevision::fromVersion(major, minor) : QTypeRevision();
36 }
37 return QTypeRevision();
38}
39
41{
42 _errors.clear();
43 _typeNamespace.clear();
44 _components.clear();
45 _dependencies.clear();
46 _imports.clear();
47 _scripts.clear();
48 _plugins.clear();
49 _designerSupported = false;
50 _typeInfos.clear();
51 _classNames.clear();
52 _linkTarget.clear();
53}
54
55inline static void scanSpace(const QChar *&ch) {
56 while (ch->isSpace() && !ch->isNull() && *ch != QLatin1Char('\n'))
57 ++ch;
58}
59
60inline static void scanToEnd(const QChar *&ch) {
61 while (*ch != QLatin1Char('\n') && !ch->isNull())
62 ++ch;
63}
64
65inline static void scanWord(const QChar *&ch) {
66 while (!ch->isSpace() && !ch->isNull())
67 ++ch;
68}
69
74{
75 quint16 lineNumber = 0;
76 bool firstLine = true;
77
78 auto readImport = [&](const QString *sections, int sectionCount, Import::Flags flags) {
79 Import import;
80 if (sectionCount == 2) {
81 import = Import(sections[1], QTypeRevision(), flags);
82 } else if (sectionCount == 3) {
83 if (sections[2] == QLatin1String("auto")) {
84 import = Import(sections[1], QTypeRevision(), flags | Import::Auto);
85 } else {
86 const auto version = parseVersion(sections[2]);
87 if (version.isValid()) {
88 import = Import(sections[1], version, flags);
89 } else {
90 reportError(lineNumber, 0,
91 QStringLiteral("invalid version %1, expected <major>.<minor>")
92 .arg(sections[2]));
93 return false;
94 }
95 }
96 } else {
97 reportError(lineNumber, 0,
98 QStringLiteral("%1 requires 1 or 2 arguments, but %2 were provided")
99 .arg(sections[0]).arg(sectionCount - 1));
100 return false;
101 }
102 if (sections[0] == QStringLiteral("import"))
103 _imports.append(import);
104 else
105 _dependencies.append(import);
106 return true;
107 };
108
109 auto readPlugin = [&](const QString *sections, int sectionCount, bool isOptional) {
110 if (sectionCount < 2 || sectionCount > 3) {
111 reportError(lineNumber, 0, QStringLiteral("plugin directive requires one or two "
112 "arguments, but %1 were provided")
113 .arg(sectionCount - 1));
114 return false;
115 }
116
117 const Plugin entry(sections[1], sections[2], isOptional);
118 _plugins.append(entry);
119 return true;
120 };
121
122 const QChar *ch = source.constData();
123 while (!ch->isNull()) {
124 ++lineNumber;
125
126 bool invalidLine = false;
127 const QChar *lineStart = ch;
128
129 scanSpace(ch);
130 if (*ch == QLatin1Char('\n')) {
131 ++ch;
132 continue;
133 }
134 if (ch->isNull())
135 break;
136
137 QString sections[4];
138 int sectionCount = 0;
139
140 do {
141 if (*ch == QLatin1Char('#')) {
142 scanToEnd(ch);
143 break;
144 }
145 const QChar *start = ch;
146 scanWord(ch);
147 if (sectionCount < 4) {
148 sections[sectionCount++] = source.mid(start-source.constData(), ch-start);
149 } else {
150 reportError(lineNumber, start-lineStart, QLatin1String("unexpected token"));
151 scanToEnd(ch);
152 invalidLine = true;
153 break;
154 }
155 scanSpace(ch);
156 } while (*ch != QLatin1Char('\n') && !ch->isNull());
157
158 if (!ch->isNull())
159 ++ch;
160
161 if (invalidLine) {
162 reportError(lineNumber, 0,
163 QStringLiteral("invalid qmldir directive contains too many tokens"));
164 continue;
165 } else if (sectionCount == 0) {
166 continue; // no sections, no party.
167
168 } else if (sections[0] == QLatin1String("module")) {
169 if (sectionCount != 2) {
170 reportError(lineNumber, 0,
171 QStringLiteral("module identifier directive requires one argument, but %1 were provided").arg(sectionCount - 1));
172 continue;
173 }
174 if (!_typeNamespace.isEmpty()) {
175 reportError(lineNumber, 0,
176 QStringLiteral("only one module identifier directive may be defined in a qmldir file"));
177 continue;
178 }
179 if (!firstLine) {
180 reportError(lineNumber, 0,
181 QStringLiteral("module identifier directive must be the first directive in a qmldir file"));
182 continue;
183 }
184
185 _typeNamespace = sections[1];
186
187 } else if (sections[0] == QLatin1String("plugin")) {
188 if (!readPlugin(sections, sectionCount, false))
189 continue;
190 } else if (sections[0] == QLatin1String("optional")) {
191 if (sectionCount < 2) {
192 reportError(lineNumber, 0, QStringLiteral("optional directive requires further "
193 "arguments, but none were provided."));
194 continue;
195 }
196
197 if (sections[1] == QStringLiteral("plugin")) {
198 if (!readPlugin(sections + 1, sectionCount - 1, true))
199 continue;
200 } else if (sections[1] == QLatin1String("import")) {
201 if (!readImport(sections + 1, sectionCount - 1, Import::Optional))
202 continue;
203 } else {
204 reportError(lineNumber, 0, QStringLiteral("only import and plugin can be optional, "
205 "not %1.").arg(sections[1]));
206 continue;
207 }
208 } else if (sections[0] == QLatin1String("default")) {
209 if (sectionCount < 2) {
210 reportError(lineNumber, 0,
211 QStringLiteral("default directive requires further "
212 "arguments, but none were provided."));
213 continue;
214 }
215 if (sections[1] == QLatin1String("import")) {
216 if (!readImport(sections + 1, sectionCount - 1,
217 Import::Flags({ Import::Optional, Import::OptionalDefault })))
218 continue;
219 } else {
220 reportError(lineNumber, 0,
221 QStringLiteral("only optional imports can have a default, "
222 "not %1.")
223 .arg(sections[1]));
224 }
225 } else if (sections[0] == QLatin1String("classname")) {
226 if (sectionCount < 2) {
227 reportError(lineNumber, 0,
228 QStringLiteral("classname directive requires an argument, but %1 were provided").arg(sectionCount - 1));
229
230 continue;
231 }
232
233 _classNames.append(sections[1]);
234
235 } else if (sections[0] == QLatin1String("internal")) {
236 if (sectionCount == 3) {
237 Component entry(sections[1], sections[2], QTypeRevision());
238 entry.internal = true;
239 _components.insert(entry.typeName, entry);
240 } else if (sectionCount == 4) {
241 const QTypeRevision version = parseVersion(sections[2]);
242 if (version.isValid()) {
243 Component entry(sections[1], sections[3], version);
244 entry.internal = true;
245 _components.insert(entry.typeName, entry);
246 } else {
247 reportError(lineNumber, 0,
248 QStringLiteral("invalid version %1, expected <major>.<minor>")
249 .arg(sections[2]));
250 continue;
251 }
252 } else {
253 reportError(lineNumber, 0,
254 QStringLiteral("internal types require 2 or 3 arguments, "
255 "but %1 were provided").arg(sectionCount - 1));
256 continue;
257 }
258 } else if (sections[0] == QLatin1String("singleton")) {
259 if (sectionCount < 3 || sectionCount > 4) {
260 reportError(lineNumber, 0,
261 QStringLiteral("singleton types require 2 or 3 arguments, but %1 were provided").arg(sectionCount - 1));
262 continue;
263 } else if (sectionCount == 3) {
264 // handle qmldir directory listing case where singleton is defined in the following pattern:
265 // singleton TestSingletonType TestSingletonType.qml
266 Component entry(sections[1], sections[2], QTypeRevision());
267 entry.singleton = true;
268 _components.insert(entry.typeName, entry);
269 } else {
270 // handle qmldir module listing case where singleton is defined in the following pattern:
271 // singleton TestSingletonType 2.0 TestSingletonType20.qml
272 const QTypeRevision version = parseVersion(sections[2]);
273 if (version.isValid()) {
274 const QString &fileName = sections[3];
275 Component entry(sections[1], fileName, version);
276 entry.singleton = true;
277 _components.insert(entry.typeName, entry);
278 } else {
279 reportError(lineNumber, 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(sections[2]));
280 }
281 }
282 } else if (sections[0] == QLatin1String("typeinfo")) {
283 if (sectionCount != 2) {
284 reportError(lineNumber, 0,
285 QStringLiteral("typeinfo requires 1 argument, but %1 were provided").arg(sectionCount - 1));
286 continue;
287 }
288 _typeInfos.append(sections[1]);
289 } else if (sections[0] == QLatin1String("designersupported")) {
290 if (sectionCount != 1)
291 reportError(lineNumber, 0, QStringLiteral("designersupported does not expect any argument"));
292 else
293 _designerSupported = true;
294 } else if (sections[0] == QLatin1String("static")) {
295 if (sectionCount != 1)
296 reportError(lineNumber, 0, QStringLiteral("static does not expect any argument"));
297 else
298 _isStaticModule = true;
299 } else if (sections[0] == QLatin1String("system")) {
300 if (sectionCount != 1)
301 reportError(lineNumber, 0, QStringLiteral("system does not expect any argument"));
302 else
303 _isSystemModule = true;
304 } else if (sections[0] == QLatin1String("import")
305 || sections[0] == QLatin1String("depends")) {
306 if (!readImport(sections, sectionCount, Import::Default))
307 continue;
308 } else if (sections[0] == QLatin1String("prefer")) {
309 if (sectionCount < 2) {
310 reportError(lineNumber, 0,
311 QStringLiteral("prefer directive requires one argument, "
312 "but %1 were provided").arg(sectionCount - 1));
313 continue;
314 }
315
316 if (!_preferredPath.isEmpty()) {
317 reportError(lineNumber, 0, QStringLiteral(
318 "only one prefer directive may be defined in a qmldir file"));
319 continue;
320 }
321
322 if (!sections[1].endsWith(u'/')) {
323 // Yes. People should realize it's a directory.
324 reportError(lineNumber, 0, QStringLiteral(
325 "the preferred directory has to end with a '/'"));
326 continue;
327 }
328
329 _preferredPath = sections[1];
330 } else if (sections[0] == QLatin1String("linktarget")) {
331 if (sectionCount < 2) {
332 reportError(lineNumber, 0,
333 QStringLiteral("linktarget directive requires an argument, "
334 "but %1 were provided")
335 .arg(sectionCount - 1));
336 continue;
337 }
338
339 if (!_linkTarget.isEmpty()) {
340 reportError(
341 lineNumber, 0,
343 "only one linktarget directive may be defined in a qmldir file"));
344 continue;
345 }
346
347 _linkTarget = sections[1];
348 } else if (sectionCount == 2) {
349 // No version specified (should only be used for relative qmldir files)
350 const Component entry(sections[0], sections[1], QTypeRevision());
351 _components.insert(entry.typeName, entry);
352 } else if (sectionCount == 3) {
353 const QTypeRevision version = parseVersion(sections[1]);
354 if (version.isValid()) {
355 const QString &fileName = sections[2];
356
357 if (fileName.endsWith(QLatin1String(".js")) || fileName.endsWith(QLatin1String(".mjs"))) {
358 // A 'js' extension indicates a namespaced script import
359 const Script entry(sections[0], fileName, version);
360 _scripts.append(entry);
361 } else {
362 const Component entry(sections[0], fileName, version);
363 _components.insert(entry.typeName, entry);
364 }
365 } else {
366 reportError(lineNumber, 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(sections[1]));
367 }
368 } else {
369 reportError(lineNumber, 0,
370 QStringLiteral("a component declaration requires two or three arguments, but %1 were provided").arg(sectionCount));
371 }
372
373 firstLine = false;
374 }
375
376 return hasError();
377}
378
379/* removes all file selector occurrences in path
380 firstPlus is the position of the initial '+' in the path
381 which we always have as we check for '+' to decide whether
382 we need to do some work at all
383*/
384static QString pathWithoutFileSelectors(QString path, // we want a copy of path
385 qsizetype firstPlus)
386{
387 do {
388 Q_ASSERT(path.at(firstPlus) == u'+');
389 const auto eos = path.size();
390 qsizetype terminatingSlashPos = firstPlus + 1;
391 while (terminatingSlashPos != eos && path.at(terminatingSlashPos) != u'/')
392 ++terminatingSlashPos;
393 path.remove(firstPlus, terminatingSlashPos - firstPlus + 1);
394 firstPlus = path.indexOf(u'+', firstPlus);
395 } while (firstPlus != -1);
396 return path;
397}
398
399static bool canDisambiguate(
400 const QString &fileName1, const QString &fileName2, QString *correctedFileName)
401{
402 // If the entries are exactly the same we can delete one without losing anything.
403 if (fileName1 == fileName2)
404 return true;
405
406 // If we detect conflicting paths, we check if they agree when we remove anything
407 // looking like a file selector.
408
409 // ugly heuristic to deal with file selectors
410 const qsizetype file2PotentialFileSelectorPos = fileName2.indexOf(u'+');
411 const bool file2MightHaveFileSelector = file2PotentialFileSelectorPos != -1;
412
413 if (const qsizetype fileSelectorPos1 = fileName1.indexOf(u'+'); fileSelectorPos1 != -1) {
414 // existing entry was file selector entry, fix it up
415 // it could also be the case that _both_ are using file selectors
416 const QString baseName = file2MightHaveFileSelector
417 ? pathWithoutFileSelectors(fileName2, file2PotentialFileSelectorPos)
418 : fileName2;
419
420 if (pathWithoutFileSelectors(fileName1, fileSelectorPos1) != baseName)
421 return false;
422
423 *correctedFileName = baseName;
424 return true;
425 }
426
427 // new entry contains file selector (and we know that fileName1 did not)
428 if (file2MightHaveFileSelector
429 && pathWithoutFileSelectors(fileName2, file2PotentialFileSelectorPos) == fileName1) {
430 *correctedFileName = fileName1;
431 return true;
432 }
433
434 return false;
435}
436
438{
439 using ConstIterator = QQmlDirComponents::const_iterator;
440
441 // end iterator may get invalidated by the erasing below.
442 // Therefore, refetch it on each iteration.
443 for (ConstIterator cit = components->constBegin(); cit != components->constEnd();) {
444
445 // We can erase and re-assign cit if we immediately forget cit2.
446 // But we cannot erase cit2 without potentially invalidating cit.
447
448 bool doErase = false;
449 const ConstIterator cend = components->constEnd();
450 for (ConstIterator cit2 = ++ConstIterator(cit); cit2 != cend; ++cit2) {
451 if (cit2.key() != cit.key())
452 break;
453
454 Q_ASSERT(cit2->typeName == cit->typeName);
455
456 if (cit2->version != cit->version
457 || cit2->internal != cit->internal
458 || cit2->singleton != cit->singleton) {
459 continue;
460 }
461
462 // The two components may differ only by fileName now.
463
464 if (canDisambiguate(cit->fileName, cit2->fileName, &(cit2->fileName))) {
465 doErase = true;
466 break;
467 }
468 }
469
470 if (doErase)
471 cit = components->erase(cit);
472 else
473 ++cit;
474 }
475}
476
478{
479 using Iterator = QQmlDirScripts::iterator;
480
481 Iterator send = scripts->end();
482
483 for (Iterator sit = scripts->begin(); sit != send; ++sit) {
484 send = std::remove_if(++Iterator(sit), send, [sit](const QQmlDirParser::Script &script2) {
485 if (sit->nameSpace != script2.nameSpace || sit->version != script2.version)
486 return false;
487
488 // The two scripts may differ only by fileName now.
489 return canDisambiguate(sit->fileName, script2.fileName, &(sit->fileName));
490 });
491 }
492
493 scripts->erase(send, scripts->end());
494}
495
501
502void QQmlDirParser::reportError(quint16 line, quint16 column, const QString &description)
503{
505 error.loc.startLine = line;
506 error.loc.startColumn = column;
507 error.message = description;
508 _errors.append(error);
509}
510
512{
513 _errors.clear();
514 reportError(e.loc.startLine, e.loc.startColumn, e.message);
515}
516
517QList<QQmlJS::DiagnosticMessage> QQmlDirParser::errors(const QString &uri) const
518{
519 QList<QQmlJS::DiagnosticMessage> errors;
520 const int numErrors = _errors.size();
521 errors.reserve(numErrors);
522 for (int i = 0; i < numErrors; ++i) {
523 QQmlJS::DiagnosticMessage e = _errors.at(i);
524 e.message.replace(QLatin1String("$$URI$$"), uri);
525 errors << e;
526 }
527 return errors;
528}
529
531{
532 const QString output = QStringLiteral("{%1 %2.%3}")
533 .arg(component.typeName).arg(component.version.majorVersion())
534 .arg(component.version.minorVersion());
535 return debug << qPrintable(output);
536}
537
539{
540 const QString output = QStringLiteral("{%1 %2.%3}")
541 .arg(script.nameSpace).arg(script.version.majorVersion())
542 .arg(script.version.minorVersion());
543 return debug << qPrintable(output);
544}
545
\inmodule QtCore
\inmodule QtCore
qsizetype size() const noexcept
Definition qlist.h:397
iterator erase(const_iterator begin, const_iterator end)
Definition qlist.h:889
iterator end()
Definition qlist.h:626
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
iterator begin()
Definition qlist.h:625
void reserve(qsizetype size)
Definition qlist.h:753
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
bool hasError() const
void setError(const QQmlJS::DiagnosticMessage &)
QList< QQmlJS::DiagnosticMessage > errors(const QString &uri) const
void disambiguateFileSelectors()
bool parse(const QString &source)
url is used for generating errors.
\inmodule QtCore
Definition qstringview.h:78
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
qsizetype indexOf(QLatin1StringView s, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4517
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3824
QString mid(qsizetype position, qsizetype n=-1) const &
Definition qstring.cpp:5300
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1226
\inmodule QtCore
static constexpr QTypeRevision fromVersion(Major majorVersion, Minor minorVersion)
Produces a QTypeRevision from the given majorVersion and minorVersion, both of which need to be a val...
constexpr quint8 minorVersion() const
Returns the minor version encoded in the revision.
constexpr bool isValid() const
Returns true if the major version or the minor version is known, otherwise false.
constexpr quint8 majorVersion() const
Returns the major version encoded in the revision.
QString str
[2]
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
GLint GLenum GLint components
GLint left
GLbitfield flags
GLuint start
GLsizei GLsizei GLchar * source
GLenum GLenum GLsizei void GLsizei void * column
GLuint entry
GLsizei const GLchar *const * path
static qreal component(const QPointF &point, unsigned int i)
static void scanSpace(const QChar *&ch)
static void disambiguateFileSelectedScripts(QQmlDirScripts *scripts)
static QString pathWithoutFileSelectors(QString path, qsizetype firstPlus)
static void disambiguateFileSelectedComponents(QQmlDirComponents *components)
static void scanToEnd(const QChar *&ch)
QDebug & operator<<(QDebug &debug, const QQmlDirParser::Component &component)
static void scanWord(const QChar *&ch)
static QTypeRevision parseVersion(const QString &str)
static bool canDisambiguate(const QString &fileName1, const QString &fileName2, QString *correctedFileName)
static QT_BEGIN_NAMESPACE int parseInt(QStringView str, bool *ok)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int void * arg
#define qPrintable(string)
Definition qstring.h:1531
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
unsigned short quint16
Definition qtypes.h:48
ptrdiff_t qsizetype
Definition qtypes.h:165
QT_BEGIN_NAMESPACE typedef uchar * output
\inmodule QtCore \reentrant
Definition qchar.h:18