1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qqmldirparser_p.h"
41
42#include <QtCore/QtDebug>
43
44QT_BEGIN_NAMESPACE
45
46static int parseInt(const QStringRef &str, bool *ok)
47{
48 int pos = 0;
49 int number = 0;
50 while (pos < str.length() && str.at(i: pos).isDigit()) {
51 if (pos != 0)
52 number *= 10;
53 number += str.at(i: pos).unicode() - '0';
54 ++pos;
55 }
56 if (pos != str.length())
57 *ok = false;
58 else
59 *ok = true;
60 return number;
61}
62
63static bool parseVersion(const QString &str, int *major, int *minor)
64{
65 const int dotIndex = str.indexOf(c: QLatin1Char('.'));
66 if (dotIndex != -1 && str.indexOf(c: QLatin1Char('.'), from: dotIndex + 1) == -1) {
67 bool ok = false;
68 *major = parseInt(str: QStringRef(&str, 0, dotIndex), ok: &ok);
69 if (ok)
70 *minor = parseInt(str: QStringRef(&str, dotIndex + 1, str.length() - dotIndex - 1), ok: &ok);
71 return ok;
72 }
73 return false;
74}
75
76void QQmlDirParser::clear()
77{
78 _errors.clear();
79 _typeNamespace.clear();
80 _components.clear();
81 _dependencies.clear();
82 _imports.clear();
83 _scripts.clear();
84 _plugins.clear();
85 _designerSupported = false;
86 _typeInfos.clear();
87 _className.clear();
88}
89
90inline static void scanSpace(const QChar *&ch) {
91 while (ch->isSpace() && !ch->isNull() && *ch != QLatin1Char('\n'))
92 ++ch;
93}
94
95inline static void scanToEnd(const QChar *&ch) {
96 while (*ch != QLatin1Char('\n') && !ch->isNull())
97 ++ch;
98}
99
100inline static void scanWord(const QChar *&ch) {
101 while (!ch->isSpace() && !ch->isNull())
102 ++ch;
103}
104
105/*!
106\a url is used for generating errors.
107*/
108bool QQmlDirParser::parse(const QString &source)
109{
110 quint16 lineNumber = 0;
111 bool firstLine = true;
112
113 const QChar *ch = source.constData();
114 while (!ch->isNull()) {
115 ++lineNumber;
116
117 bool invalidLine = false;
118 const QChar *lineStart = ch;
119
120 scanSpace(ch);
121 if (*ch == QLatin1Char('\n')) {
122 ++ch;
123 continue;
124 }
125 if (ch->isNull())
126 break;
127
128 QString sections[4];
129 int sectionCount = 0;
130
131 do {
132 if (*ch == QLatin1Char('#')) {
133 scanToEnd(ch);
134 break;
135 }
136 const QChar *start = ch;
137 scanWord(ch);
138 if (sectionCount < 4) {
139 sections[sectionCount++] = source.mid(position: start-source.constData(), n: ch-start);
140 } else {
141 reportError(line: lineNumber, column: start-lineStart, message: QLatin1String("unexpected token"));
142 scanToEnd(ch);
143 invalidLine = true;
144 break;
145 }
146 scanSpace(ch);
147 } while (*ch != QLatin1Char('\n') && !ch->isNull());
148
149 if (!ch->isNull())
150 ++ch;
151
152 if (invalidLine) {
153 reportError(line: lineNumber, column: 0,
154 QStringLiteral("invalid qmldir directive contains too many tokens"));
155 continue;
156 } else if (sectionCount == 0) {
157 continue; // no sections, no party.
158
159 } else if (sections[0] == QLatin1String("module")) {
160 if (sectionCount != 2) {
161 reportError(line: lineNumber, column: 0,
162 QStringLiteral("module identifier directive requires one argument, but %1 were provided").arg(a: sectionCount - 1));
163 continue;
164 }
165 if (!_typeNamespace.isEmpty()) {
166 reportError(line: lineNumber, column: 0,
167 QStringLiteral("only one module identifier directive may be defined in a qmldir file"));
168 continue;
169 }
170 if (!firstLine) {
171 reportError(line: lineNumber, column: 0,
172 QStringLiteral("module identifier directive must be the first directive in a qmldir file"));
173 continue;
174 }
175
176 _typeNamespace = sections[1];
177
178 } else if (sections[0] == QLatin1String("plugin")) {
179 if (sectionCount < 2 || sectionCount > 3) {
180 reportError(line: lineNumber, column: 0,
181 QStringLiteral("plugin directive requires one or two arguments, but %1 were provided").arg(a: sectionCount - 1));
182
183 continue;
184 }
185
186 const Plugin entry(sections[1], sections[2]);
187
188 _plugins.append(t: entry);
189
190 } else if (sections[0] == QLatin1String("classname")) {
191 if (sectionCount < 2) {
192 reportError(line: lineNumber, column: 0,
193 QStringLiteral("classname directive requires an argument, but %1 were provided").arg(a: sectionCount - 1));
194
195 continue;
196 }
197
198 _className = sections[1];
199
200 } else if (sections[0] == QLatin1String("internal")) {
201 if (sectionCount != 3) {
202 reportError(line: lineNumber, column: 0,
203 QStringLiteral("internal types require 2 arguments, but %1 were provided").arg(a: sectionCount - 1));
204 continue;
205 }
206 Component entry(sections[1], sections[2], -1, -1);
207 entry.internal = true;
208 _components.insert(akey: entry.typeName, avalue: entry);
209 } else if (sections[0] == QLatin1String("singleton")) {
210 if (sectionCount < 3 || sectionCount > 4) {
211 reportError(line: lineNumber, column: 0,
212 QStringLiteral("singleton types require 2 or 3 arguments, but %1 were provided").arg(a: sectionCount - 1));
213 continue;
214 } else if (sectionCount == 3) {
215 // handle qmldir directory listing case where singleton is defined in the following pattern:
216 // singleton TestSingletonType TestSingletonType.qml
217 Component entry(sections[1], sections[2], -1, -1);
218 entry.singleton = true;
219 _components.insert(akey: entry.typeName, avalue: entry);
220 } else {
221 // handle qmldir module listing case where singleton is defined in the following pattern:
222 // singleton TestSingletonType 2.0 TestSingletonType20.qml
223 int major, minor;
224 if (parseVersion(str: sections[2], major: &major, minor: &minor)) {
225 const QString &fileName = sections[3];
226 Component entry(sections[1], fileName, major, minor);
227 entry.singleton = true;
228 _components.insert(akey: entry.typeName, avalue: entry);
229 } else {
230 reportError(line: lineNumber, column: 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(a: sections[2]));
231 }
232 }
233 } else if (sections[0] == QLatin1String("typeinfo")) {
234 if (sectionCount != 2) {
235 reportError(line: lineNumber, column: 0,
236 QStringLiteral("typeinfo requires 1 argument, but %1 were provided").arg(a: sectionCount - 1));
237 continue;
238 }
239#ifdef QT_CREATOR
240 TypeInfo typeInfo(sections[1]);
241 _typeInfos.append(typeInfo);
242#endif
243
244 } else if (sections[0] == QLatin1String("designersupported")) {
245 if (sectionCount != 1)
246 reportError(line: lineNumber, column: 0, QStringLiteral("designersupported does not expect any argument"));
247 else
248 _designerSupported = true;
249 } else if (sections[0] == QLatin1String("depends")) {
250 if (sectionCount != 3) {
251 reportError(line: lineNumber, column: 0,
252 QStringLiteral("depends requires 2 arguments, but %1 were provided").arg(a: sectionCount - 1));
253 continue;
254 }
255
256 int major, minor;
257 if (parseVersion(str: sections[2], major: &major, minor: &minor)) {
258 Component entry(sections[1], QString(), major, minor);
259 entry.internal = true;
260 _dependencies.insert(akey: entry.typeName, avalue: entry);
261 } else {
262 reportError(line: lineNumber, column: 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(a: sections[2]));
263 }
264 } else if (sections[0] == QLatin1String("import")) {
265 if (sectionCount != 2) {
266 reportError(line: lineNumber, column: 0,
267 QStringLiteral("import requires 2 arguments, but %1 were provided").arg(a: sectionCount - 1));
268 continue;
269 }
270 _imports << sections[1];
271 } else if (sectionCount == 2) {
272 // No version specified (should only be used for relative qmldir files)
273 const Component entry(sections[0], sections[1], -1, -1);
274 _components.insert(akey: entry.typeName, avalue: entry);
275 } else if (sectionCount == 3) {
276 int major, minor;
277 if (parseVersion(str: sections[1], major: &major, minor: &minor)) {
278 const QString &fileName = sections[2];
279
280 if (fileName.endsWith(s: QLatin1String(".js")) || fileName.endsWith(s: QLatin1String(".mjs"))) {
281 // A 'js' extension indicates a namespaced script import
282 const Script entry(sections[0], fileName, major, minor);
283 _scripts.append(t: entry);
284 } else {
285 const Component entry(sections[0], fileName, major, minor);
286 _components.insert(akey: entry.typeName, avalue: entry);
287 }
288 } else {
289 reportError(line: lineNumber, column: 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(a: sections[1]));
290 }
291 } else {
292 reportError(line: lineNumber, column: 0,
293 QStringLiteral("a component declaration requires two or three arguments, but %1 were provided").arg(a: sectionCount));
294 }
295
296 firstLine = false;
297 }
298
299 return hasError();
300}
301
302void QQmlDirParser::reportError(quint16 line, quint16 column, const QString &description)
303{
304 QQmlJS::DiagnosticMessage error;
305 error.loc.startLine = line;
306 error.loc.startColumn = column;
307 error.message = description;
308 _errors.append(t: error);
309}
310
311bool QQmlDirParser::hasError() const
312{
313 if (! _errors.isEmpty())
314 return true;
315
316 return false;
317}
318
319void QQmlDirParser::setError(const QQmlJS::DiagnosticMessage &e)
320{
321 _errors.clear();
322 reportError(line: e.loc.startLine, column: e.loc.startColumn, description: e.message);
323}
324
325QList<QQmlJS::DiagnosticMessage> QQmlDirParser::errors(const QString &uri) const
326{
327 QList<QQmlJS::DiagnosticMessage> errors;
328 const int numErrors = _errors.size();
329 errors.reserve(alloc: numErrors);
330 for (int i = 0; i < numErrors; ++i) {
331 QQmlJS::DiagnosticMessage e = _errors.at(i);
332 e.message.replace(before: QLatin1String("$$URI$$"), after: uri);
333 errors << e;
334 }
335 return errors;
336}
337
338QString QQmlDirParser::typeNamespace() const
339{
340 return _typeNamespace;
341}
342
343void QQmlDirParser::setTypeNamespace(const QString &s)
344{
345 _typeNamespace = s;
346}
347
348QList<QQmlDirParser::Plugin> QQmlDirParser::plugins() const
349{
350 return _plugins;
351}
352
353QMultiHash<QString, QQmlDirParser::Component> QQmlDirParser::components() const
354{
355 return _components;
356}
357
358QHash<QString, QQmlDirParser::Component> QQmlDirParser::dependencies() const
359{
360 return _dependencies;
361}
362
363QStringList QQmlDirParser::imports() const
364{
365 return _imports;
366}
367
368QList<QQmlDirParser::Script> QQmlDirParser::scripts() const
369{
370 return _scripts;
371}
372
373QList<QQmlDirParser::TypeInfo> QQmlDirParser::typeInfos() const
374{
375 return _typeInfos;
376}
377
378bool QQmlDirParser::designerSupported() const
379{
380 return _designerSupported;
381}
382
383QString QQmlDirParser::className() const
384{
385 return _className;
386}
387
388QDebug &operator<< (QDebug &debug, const QQmlDirParser::Component &component)
389{
390 const QString output = QStringLiteral("{%1 %2.%3}").
391 arg(a: component.typeName).arg(a: component.majorVersion).arg(a: component.minorVersion);
392 return debug << qPrintable(output);
393}
394
395QDebug &operator<< (QDebug &debug, const QQmlDirParser::Script &script)
396{
397 const QString output = QStringLiteral("{%1 %2.%3}").
398 arg(a: script.nameSpace).arg(a: script.majorVersion).arg(a: script.minorVersion);
399 return debug << qPrintable(output);
400}
401
402QT_END_NAMESPACE
403

source code of qtdeclarative/src/qml/qmldirparser/qqmldirparser.cpp