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