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 | |
4 | #include "qqmldomtypesreader_p.h" |
5 | #include "qqmldomelements_p.h" |
6 | #include "qqmldomcompare_p.h" |
7 | #include "qqmldomfieldfilter_p.h" |
8 | |
9 | #include <QtQml/private/qqmljsparser_p.h> |
10 | #include <QtQml/private/qqmljslexer_p.h> |
11 | #include <QtQml/private/qqmljsengine_p.h> |
12 | #include <private/qqmljstypedescriptionreader_p.h> |
13 | |
14 | #include <QtCore/qdir.h> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | namespace QQmlJS { |
19 | namespace Dom { |
20 | |
21 | using namespace QQmlJS::AST; |
22 | |
23 | static ErrorGroups readerParseErrors() |
24 | { |
25 | static ErrorGroups errs = { .groups: { NewErrorGroup("Dom" ), NewErrorGroup("QmltypesFile" ), |
26 | NewErrorGroup("Parsing" ) } }; |
27 | return errs; |
28 | } |
29 | |
30 | void QmltypesReader::insertProperty( |
31 | const QQmlJSScope::ConstPtr &jsScope, const QQmlJSMetaProperty &property, |
32 | QMap<int, QmlObject> &objs) |
33 | { |
34 | PropertyDefinition prop; |
35 | prop.name = property.propertyName(); |
36 | prop.typeName = property.typeName(); |
37 | prop.isPointer = property.isPointer(); |
38 | prop.isReadonly = !property.isWritable(); |
39 | prop.isRequired = jsScope->isPropertyLocallyRequired(name: prop.name); |
40 | prop.isList = property.isList(); |
41 | int revision = property.revision(); |
42 | prop.isFinal = property.isFinal(); |
43 | prop.bindable = property.bindable(); |
44 | prop.read = property.read(); |
45 | prop.write = property.write(); |
46 | prop.notify = property.notify(); |
47 | |
48 | if (prop.name.isEmpty() || prop.typeName.isEmpty()) { |
49 | addError(message: readerParseErrors() |
50 | .warning(message: tr(sourceText: "Property object is missing a name or type script binding." )) |
51 | .handle()); |
52 | return; |
53 | } |
54 | objs[revision].addPropertyDef(propertyDef: prop, option: AddOption::KeepExisting); |
55 | } |
56 | |
57 | void QmltypesReader::insertSignalOrMethod(const QQmlJSMetaMethod &metaMethod, |
58 | QMap<int, QmlObject> &objs) |
59 | { |
60 | MethodInfo methodInfo; |
61 | // ### confusion between Method and Slot. Method should be removed. |
62 | switch (metaMethod.methodType()) { |
63 | case QQmlJSMetaMethodType::Method: |
64 | case QQmlJSMetaMethodType::Slot: |
65 | methodInfo.methodType = MethodInfo::MethodType::Method; |
66 | break; |
67 | case QQmlJSMetaMethodType::Signal: |
68 | methodInfo.methodType = MethodInfo::MethodType::Signal; |
69 | break; |
70 | default: |
71 | Q_UNREACHABLE(); |
72 | } |
73 | auto parameters = metaMethod.parameters(); |
74 | qsizetype nParam = parameters.size(); |
75 | for (int i = 0; i < nParam; ++i) { |
76 | MethodParameter param; |
77 | param.name = parameters[i].name(); |
78 | param.typeName = parameters[i].typeName(); |
79 | methodInfo.parameters.append(t: param); |
80 | } |
81 | methodInfo.name = metaMethod.methodName(); |
82 | methodInfo.typeName = metaMethod.returnTypeName(); |
83 | int revision = metaMethod.revision(); |
84 | methodInfo.isConstructor = metaMethod.isConstructor(); |
85 | if (methodInfo.name.isEmpty()) { |
86 | addError(message: readerParseErrors().error(message: tr(sourceText: "Method or signal is missing a name." )).handle()); |
87 | return; |
88 | } |
89 | |
90 | objs[revision].addMethod(functionDef: methodInfo, option: AddOption::KeepExisting); |
91 | } |
92 | |
93 | EnumDecl QmltypesReader::enumFromMetaEnum(const QQmlJSMetaEnum &metaEnum) |
94 | { |
95 | EnumDecl res; |
96 | res.setName(metaEnum.name()); |
97 | res.setAlias(metaEnum.alias()); |
98 | res.setIsFlag(metaEnum.isFlag()); |
99 | QList<EnumItem> values; |
100 | int lastValue = -1; |
101 | for (const auto &k : metaEnum.keys()) { |
102 | if (metaEnum.hasValues()) |
103 | lastValue = metaEnum.value(key: k); |
104 | else |
105 | ++lastValue; |
106 | values.append(t: EnumItem(k, lastValue)); |
107 | } |
108 | res.setValues(values); |
109 | return res; |
110 | } |
111 | |
112 | void QmltypesReader::insertComponent(const QQmlJSScope::ConstPtr &jsScope, |
113 | const QList<QQmlJSScope::Export> &exportsList) |
114 | { |
115 | QmltypesComponent comp; |
116 | comp.setSemanticScope(jsScope); |
117 | QMap<int, QmlObject> objects; |
118 | { |
119 | bool hasExports = false; |
120 | for (const QQmlJSScope::Export &jsE : exportsList) { |
121 | int metaRev = jsE.version().toEncodedVersion<int>(); |
122 | hasExports = true; |
123 | QmlObject object; |
124 | object.setSemanticScope(jsScope); |
125 | objects.insert(key: metaRev, value: object); |
126 | } |
127 | if (!hasExports) { |
128 | QmlObject object; |
129 | object.setSemanticScope(jsScope); |
130 | objects.insert(key: 0, value: object); |
131 | } |
132 | } |
133 | bool incrementedPath = false; |
134 | QString prototype; |
135 | QString defaultPropertyName; |
136 | { |
137 | QHash<QString, QQmlJSMetaProperty> els = jsScope->ownProperties(); |
138 | auto it = els.cbegin(); |
139 | auto end = els.cend(); |
140 | while (it != end) { |
141 | insertProperty(jsScope, property: it.value(), objs&: objects); |
142 | ++it; |
143 | } |
144 | } |
145 | { |
146 | QMultiHash<QString, QQmlJSMetaMethod> els = jsScope->ownMethods(); |
147 | auto it = els.cbegin(); |
148 | auto end = els.cend(); |
149 | while (it != end) { |
150 | insertSignalOrMethod(metaMethod: it.value(), objs&: objects); |
151 | ++it; |
152 | } |
153 | } |
154 | { |
155 | QHash<QString, QQmlJSMetaEnum> els = jsScope->ownEnumerations(); |
156 | auto it = els.cbegin(); |
157 | auto end = els.cend(); |
158 | while (it != end) { |
159 | comp.addEnumeration(enumeration: enumFromMetaEnum(metaEnum: it.value())); |
160 | ++it; |
161 | } |
162 | } |
163 | comp.setFileName(jsScope->filePath()); |
164 | comp.setName(jsScope->internalName()); |
165 | m_currentPath = m_currentPath.key(name: comp.name()) |
166 | .index(i: qmltypesFilePtr()->components().values(key: comp.name()).size()); |
167 | incrementedPath = true; |
168 | prototype = jsScope->baseTypeName(); |
169 | defaultPropertyName = jsScope->ownDefaultPropertyName(); |
170 | comp.setInterfaceNames(jsScope->interfaceNames()); |
171 | QString typeName = jsScope->ownAttachedTypeName(); |
172 | comp.setAttachedTypeName(typeName); |
173 | if (!typeName.isEmpty()) |
174 | comp.setAttachedTypePath(Paths::lookupCppTypePath(name: typeName)); |
175 | comp.setIsSingleton(jsScope->isSingleton()); |
176 | comp.setIsCreatable(jsScope->isCreatable()); |
177 | comp.setIsComposite(jsScope->isComposite()); |
178 | comp.setHasCustomParser(jsScope->hasCustomParser()); |
179 | comp.setValueTypeName(jsScope->valueTypeName()); |
180 | comp.setAccessSemantics(jsScope->accessSemantics()); |
181 | comp.setExtensionTypeName(jsScope->extensionTypeName()); |
182 | comp.setExtensionIsJavaScript(jsScope->extensionIsJavaScript()); |
183 | comp.setExtensionIsNamespace(jsScope->extensionIsNamespace()); |
184 | Path exportSourcePath = qmltypesFile().canonicalPath(); |
185 | QMap<int, Path> revToPath; |
186 | auto it = objects.end(); |
187 | auto begin = objects.begin(); |
188 | int objectIndex = 0; |
189 | QList<int> metaRevs; |
190 | Path compPath = qmltypesFile() |
191 | .canonicalPath() |
192 | .field(name: Fields::components) |
193 | .key(name: comp.name()) |
194 | .index(i: qmltypesFilePtr()->components().values(key: comp.name()).size()); |
195 | |
196 | // emit & map objs |
197 | while (it != begin) { |
198 | --it; |
199 | if (it.key() < 0) { |
200 | addError(message: readerParseErrors().error( |
201 | message: tr(sourceText: "negative meta revision %1 not supported" ).arg(a: it.key()))); |
202 | } |
203 | revToPath.insert(key: it.key(), value: compPath.field(name: Fields::objects).index(i: objectIndex)); |
204 | Path nextObjectPath = compPath.field(name: Fields::objects).index(i: ++objectIndex); |
205 | if (it == begin) { |
206 | if (!prototype.isEmpty()) |
207 | it->addPrototypePath(prototypePath: Paths::lookupCppTypePath(name: prototype)); |
208 | it->setName(prototype); |
209 | } else { |
210 | it->addPrototypePath(prototypePath: nextObjectPath); |
211 | it->setName(comp.name() + QLatin1String("-" ) + QString::number(it.key())); |
212 | } |
213 | comp.addObject(object: *it); |
214 | metaRevs.append(t: it.key()); |
215 | } |
216 | comp.setMetaRevisions(metaRevs); |
217 | |
218 | // exports: |
219 | QList<Export> exports; |
220 | for (const QQmlJSScope::Export &jsE : exportsList) { |
221 | auto v = jsE.version(); |
222 | int metaRev = v.toEncodedVersion<int>(); |
223 | Export e; |
224 | e.uri = jsE.package(); |
225 | e.typeName = jsE.type(); |
226 | e.isSingleton = jsScope->isSingleton(); |
227 | e.version = Version((v.hasMajorVersion() ? v.majorVersion() : Version::Latest), |
228 | (v.hasMinorVersion() ? v.minorVersion() : Version::Latest)); |
229 | e.typePath = revToPath.value(key: metaRev); |
230 | if (!e.typePath) { |
231 | qCWarning(domLog) << "could not find version" << metaRev << "in" << revToPath.keys(); |
232 | } |
233 | e.exportSourcePath = exportSourcePath; |
234 | comp.addExport(exportedEntry: e); |
235 | } |
236 | |
237 | if (comp.name().isEmpty()) { |
238 | addError(message: readerParseErrors() |
239 | .error(message: tr(sourceText: "Component definition is missing a name binding." )) |
240 | .handle()); |
241 | return; |
242 | } |
243 | qmltypesFilePtr()->addComponent(comp, option: AddOption::KeepExisting); |
244 | if (incrementedPath) |
245 | m_currentPath = m_currentPath.dropTail().dropTail(); |
246 | } |
247 | |
248 | bool QmltypesReader::parse() |
249 | { |
250 | QQmlJSTypeDescriptionReader reader(qmltypesFilePtr()->canonicalFilePath(), |
251 | qmltypesFilePtr()->code()); |
252 | QStringList dependencies; |
253 | QList<QQmlJSExportedScope> objects; |
254 | const bool isValid = reader(&objects, &dependencies); |
255 | for (const auto &obj : std::as_const(t&: objects)) |
256 | insertComponent(jsScope: obj.scope, exportsList: obj.exports); |
257 | qmltypesFilePtr()->setIsValid(isValid); |
258 | return isValid; |
259 | } |
260 | |
261 | void QmltypesReader::addError(ErrorMessage &&message) |
262 | { |
263 | if (message.file.isEmpty()) |
264 | message.file = qmltypesFile().canonicalFilePath(); |
265 | if (!message.path) |
266 | message.path = m_currentPath; |
267 | qmltypesFilePtr()->addErrorLocal(msg: message.handle()); |
268 | } |
269 | |
270 | } // end namespace Dom |
271 | } // end namespace QQmlJS |
272 | QT_END_NAMESPACE |
273 | |