Qt 6.x
The Qt SDK
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
379void QQmlDirParser::reportError(quint16 line, quint16 column, const QString &description)
380{
382 error.loc.startLine = line;
383 error.loc.startColumn = column;
384 error.message = description;
385 _errors.append(error);
386}
387
389{
390 _errors.clear();
391 reportError(e.loc.startLine, e.loc.startColumn, e.message);
392}
393
395{
397 const int numErrors = _errors.size();
398 errors.reserve(numErrors);
399 for (int i = 0; i < numErrors; ++i) {
400 QQmlJS::DiagnosticMessage e = _errors.at(i);
401 e.message.replace(QLatin1String("$$URI$$"), uri);
402 errors << e;
403 }
404 return errors;
405}
406
408{
409 const QString output = QStringLiteral("{%1 %2.%3}")
410 .arg(component.typeName).arg(component.version.majorVersion())
411 .arg(component.version.minorVersion());
412 return debug << qPrintable(output);
413}
414
416{
417 const QString output = QStringLiteral("{%1 %2.%3}")
418 .arg(script.nameSpace).arg(script.version.majorVersion())
419 .arg(script.version.minorVersion());
420 return debug << qPrintable(output);
421}
422
\inmodule QtCore
Definition qchar.h:48
constexpr bool isDigit() const noexcept
Returns true if the character is a decimal digit (Number_DecimalDigit); otherwise returns false.
Definition qchar.h:473
constexpr char16_t unicode() const noexcept
Returns the numeric Unicode value of the QChar.
Definition qchar.h:458
\inmodule QtCore
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
void reserve(qsizetype size)
Definition qlist.h:746
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
bool hasError() const
void setError(const QQmlJS::DiagnosticMessage &)
QList< QQmlJS::DiagnosticMessage > errors(const QString &uri) const
bool parse(const QString &source)
url is used for generating errors.
\inmodule QtCore
Definition qstringview.h:76
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1107
qsizetype size() const
Returns the number of characters in this string.
Definition qstring.h:182
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
Definition qstring.cpp:5204
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1079
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
static QString static QString qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4420
\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]
double e
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
GLint left
GLbitfield flags
GLuint start
GLsizei GLsizei GLchar * source
GLenum GLenum GLsizei void GLsizei void * column
GLuint entry
static qreal component(const QPointF &point, unsigned int i)
static void scanSpace(const QChar *&ch)
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 QT_BEGIN_NAMESPACE int parseInt(QStringView str, bool *ok)
SSL_CTX int(*) void arg)
#define qPrintable(string)
Definition qstring.h:1391
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
unsigned short quint16
Definition qtypes.h:43
QT_BEGIN_NAMESPACE typedef uchar * output
\inmodule QtCore \reentrant
Definition qchar.h:17