| 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 | |