Revision control

++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:significant
++
++ #include "qqmljstypedescriptionreader_p.h"
++
++ #include <QtQml/private/qqmljsparser_p.h>
++ #include <QtQml/private/qqmljslexer_p.h>
++ #include <QtQml/private/qqmljsengine_p.h>
++
++ #include <QtCore/qdir.h>
++ #include <QtCore/qstring.h>
++ #include <QtCore/qtyperevision.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace QQmlJS;
++ using namespace QQmlJS::AST;
++ using namespace Qt::StringLiterals;
++
++ QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
++ {
++ QString result;
++
++ for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
++ if (iter != qualifiedId)
++ result += delimiter;
++
++ result += iter->name;
++ }
++
++ return result;
++ }
++
++ bool QQmlJSTypeDescriptionReader::operator()(
++ QList<QQmlJSExportedScope> *objects, QStringList *dependencies)
++ {
++ Engine engine;
++
++ Lexer lexer(&engine);
++ Parser parser(&engine);
++
++ lexer.setCode(m_source, /*lineno = */ 1, /*qmlMode = */true);
++
++ if (!parser.parse()) {
++ m_errorMessage = QString::fromLatin1("%1:%2: %3").arg(
++ QString::number(parser.errorLineNumber()),
++ QString::number(parser.errorColumnNumber()),
++ parser.errorMessage());
++ return false;
++ }
++
++ m_objects = objects;
++ m_dependencies = dependencies;
++ readDocument(parser.ast());
++
++ return m_errorMessage.isEmpty();
++ }
++
++ void QQmlJSTypeDescriptionReader::readDocument(UiProgram *ast)
++ {
++ if (!ast) {
++ addError(SourceLocation(), tr("Could not parse document."));
++ return;
++ }
++
++ if (!ast->headers || ast->headers->next || !cast<UiImport *>(ast->headers->headerItem)) {
++ addError(SourceLocation(), tr("Expected a single import."));
++ return;
++ }
++
++ auto *import = cast<UiImport *>(ast->headers->headerItem);
++ if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) {
++ addError(import->importToken, tr("Expected import of QtQuick.tooling."));
++ return;
++ }
++
++ if (!import->version) {
++ addError(import->firstSourceLocation(), tr("Import statement without version."));
++ return;
++ }
++
++ if (import->version->version.majorVersion() != 1) {
++ addError(import->version->firstSourceLocation(),
++ tr("Major version different from 1 not supported."));
++ return;
++ }
++
++ if (!ast->members || !ast->members->member || ast->members->next) {
++ addError(SourceLocation(), tr("Expected document to contain a single object definition."));
++ return;
++ }
++
++ auto *module = cast<UiObjectDefinition *>(ast->members->member);
++ if (!module) {
++ addError(SourceLocation(), tr("Expected document to contain a single object definition."));
++ return;
++ }
++
++ if (toString(module->qualifiedTypeNameId) != QLatin1String("Module")) {
++ addError(SourceLocation(), tr("Expected document to contain a Module {} member."));
++ return;
++ }
++
++ readModule(module);
++ }
++
++ void QQmlJSTypeDescriptionReader::readModule(UiObjectDefinition *ast)
++ {
++ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
++ UiObjectMember *member = it->member;
++ auto *component = cast<UiObjectDefinition *>(member);
++
++ auto *script = cast<UiScriptBinding *>(member);
++ if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) {
++ readDependencies(script);
++ continue;
++ }
++
++ QString typeName;
++ if (component)
++ typeName = toString(component->qualifiedTypeNameId);
++
++ if (!component || typeName != QLatin1String("Component")) {
++ continue;
++ }
++
++ if (typeName == QLatin1String("Component"))
++ readComponent(component);
++ }
++ }
++
++ void QQmlJSTypeDescriptionReader::addError(const SourceLocation &loc, const QString &message)
++ {
++ m_errorMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
++ QDir::toNativeSeparators(m_fileName),
++ QString::number(loc.startLine),
++ QString::number(loc.startColumn),
++ message);
++ }
++
++ void QQmlJSTypeDescriptionReader::addWarning(const SourceLocation &loc, const QString &message)
++ {
++ m_warningMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
++ QDir::toNativeSeparators(m_fileName),
++ QString::number(loc.startLine),
++ QString::number(loc.startColumn),
++ message);
++ }
++
++ void QQmlJSTypeDescriptionReader::readDependencies(UiScriptBinding *ast)
++ {
++ auto *stmt = cast<ExpressionStatement*>(ast->statement);
++ if (!stmt) {
++ addError(ast->statement->firstSourceLocation(), tr("Expected dependency definitions"));
++ return;
++ }
++ auto *exp = cast<ArrayPattern *>(stmt->expression);
++ if (!exp) {
++ addError(stmt->expression->firstSourceLocation(), tr("Expected dependency definitions"));
++ return;
++ }
++ for (PatternElementList *l = exp->elements; l; l = l->next) {
++ auto *str = cast<StringLiteral *>(l->element->initializer);
++ *m_dependencies << str->value.toString();
++ }
++ }
++
++ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
++ {
++ m_currentCtorIndex = 0;
++ m_currentMethodIndex = 0;
++ QQmlJSScope::Ptr scope = QQmlJSScope::create();
++ QList<QQmlJSScope::Export> exports;
++
++ UiScriptBinding *metaObjectRevisions = nullptr;
++ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
++ UiObjectMember *member = it->member;
++ auto *component = cast<UiObjectDefinition *>(member);
++ auto *script = cast<UiScriptBinding *>(member);
++ if (component) {
++ QString name = toString(component->qualifiedTypeNameId);
++ if (name == QLatin1String("Property"))
++ readProperty(component, scope);
++ else if (name == QLatin1String("Method") || name == QLatin1String("Signal"))
++ readSignalOrMethod(component, name == QLatin1String("Method"), scope);
++ else if (name == QLatin1String("Enum"))
++ readEnum(component, scope);
++ else
++ addWarning(component->firstSourceLocation(),
++ tr("Expected only Property, Method, Signal and Enum object definitions, "
++ "not \"%1\".").arg(name));
++ } else if (script) {
++ QString name = toString(script->qualifiedId);
++ if (name == QLatin1String("file")) {
++ scope->setFilePath(readStringBinding(script));
++ } else if (name == QLatin1String("lineNumber")) {
++ scope->setLineNumber(readNumericBinding(script));
++ } else if (name == QLatin1String("resolvedFile")) {
++ scope->setResolvedFilePath(readStringBinding(script));
++ } else if (name == QLatin1String("lineNumberInResolvedFile")) {
++ scope->setLineNumberInResolvedFile(readNumericBinding(script));
++ } else if (name == QLatin1String("name")) {
++ scope->setInternalName(readStringBinding(script));
++ } else if (name == QLatin1String("prototype")) {
++ scope->setBaseTypeName(readStringBinding(script));
++ } else if (name == QLatin1String("defaultProperty")) {
++ scope->setOwnDefaultPropertyName(readStringBinding(script));
++ } else if (name == QLatin1String("parentProperty")) {
++ scope->setOwnParentPropertyName(readStringBinding(script));
++ } else if (name == QLatin1String("exports")) {
++ exports = readExports(script);
++ } else if (name == QLatin1String("aliases")) {
++ readAliases(script, scope);
++ } else if (name == QLatin1String("interfaces")) {
++ readInterfaces(script, scope);
++ } else if (name == QLatin1String("exportMetaObjectRevisions")) {
++ metaObjectRevisions = script;
++ } else if (name == QLatin1String("attachedType")) {
++ scope->setOwnAttachedTypeName(readStringBinding(script));
++ } else if (name == QLatin1String("valueType")) {
++ scope->setElementTypeName(readStringBinding(script));
++ } else if (name == QLatin1String("isSingleton")) {
++ scope->setIsSingleton(readBoolBinding(script));
++ } else if (name == QLatin1String("isCreatable")) {
++ scope->setCreatableFlag(readBoolBinding(script));
++ } else if (name == QLatin1String("isStructured")) {
++ scope->setStructuredFlag(readBoolBinding(script));
++ } else if (name == QLatin1String("isComposite")) {
++ scope->setIsComposite(readBoolBinding(script));
++ } else if (name == QLatin1String("hasCustomParser")) {
++ scope->setHasCustomParser(readBoolBinding(script));
++ } else if (name == QLatin1String("enforcesScopedEnums")) {
++ scope->setEnforcesScopedEnumsFlag(readBoolBinding(script));
++ } else if (name == QLatin1String("accessSemantics")) {
++ const QString semantics = readStringBinding(script);
++ if (semantics == QLatin1String("reference")) {
++ scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Reference);
++ } else if (semantics == QLatin1String("value")) {
++ scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
++ } else if (semantics == QLatin1String("none")) {
++ scope->setAccessSemantics(QQmlJSScope::AccessSemantics::None);
++ } else if (semantics == QLatin1String("sequence")) {
++ scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Sequence);
++ } else {
++ addWarning(script->firstSourceLocation(),
++ tr("Unknown access semantics \"%1\".").arg(semantics));
++ }
++ } else if (name == QLatin1String("extension")) {
++ scope->setExtensionTypeName(readStringBinding(script));
++ } else if (name == QLatin1String("extensionIsJavaScript")) {
++ scope->setExtensionIsJavaScript(readBoolBinding(script));
++ } else if (name == QLatin1String("extensionIsNamespace")) {
++ scope->setExtensionIsNamespace(readBoolBinding(script));
++ } else if (name == QLatin1String("isSelfExtension")) {
++ scope->setIsSelfExtension(readBoolBinding(script));
++ } else if (name == QLatin1String("deferredNames")) {
++ readDeferredNames(script, scope);
++ } else if (name == QLatin1String("immediateNames")) {
++ readImmediateNames(script, scope);
++ } else if (name == QLatin1String("isJavaScriptBuiltin")) {
++ scope->setIsJavaScriptBuiltin(readBoolBinding(script));
++ } else if (name == QLatin1String("isTypeOpaque")) {
++ scope->setIsTypeOpaque(readBoolBinding(script));
++ } else {
++ addWarning(script->firstSourceLocation(),
++ tr("Expected only lineNumber, name, prototype, defaultProperty, "
++ "attachedType, "
++ "valueType, exports, interfaces, isSingleton, isCreatable, "
++ "isStructured, isComposite, hasCustomParser, enforcesScopedEnums, "
++ "aliases, exportMetaObjectRevisions, deferredNames, and "
++ "immediateNames in script bindings, not \"%1\".")
++ .arg(name));
++ }
++ } else {
++ addWarning(member->firstSourceLocation(),
++ tr("Expected only script bindings and object definitions."));
++ }
++ }
++
++ if (scope->internalName().isEmpty()) {
++ addError(ast->firstSourceLocation(), tr("Component definition is missing a name binding."));
++ return;
++ }
++
++ if (metaObjectRevisions)
++ checkMetaObjectRevisions(metaObjectRevisions, &exports);
++ m_objects->append({scope, exports});
++ }
++
++ void QQmlJSTypeDescriptionReader::readSignalOrMethod(
++ UiObjectDefinition *ast, bool isMethod, const QQmlJSScope::Ptr &scope)
++ {
++ QQmlJSMetaMethod metaMethod;
++ // ### confusion between Method and Slot. Method should be removed.
++ if (isMethod)
++ metaMethod.setMethodType(QQmlJSMetaMethodType::Slot);
++ else
++ metaMethod.setMethodType(QQmlJSMetaMethodType::Signal);
++
++ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
++ UiObjectMember *member = it->member;
++ auto *component = cast<UiObjectDefinition *>(member);
++ auto *script = cast<UiScriptBinding *>(member);
++ if (component) {
++ QString name = toString(component->qualifiedTypeNameId);
++ if (name == QLatin1String("Parameter")) {
++ readParameter(component, &metaMethod);
++ } else {
++ addWarning(component->firstSourceLocation(),
++ tr("Expected only Parameter in object definitions."));
++ }
++ } else if (script) {
++ QString name = toString(script->qualifiedId);
++ if (name == QLatin1String("name")) {
++ metaMethod.setMethodName(readStringBinding(script));
++ } else if (name == QLatin1String("lineNumber")) {
++ metaMethod.setSourceLocation(
++ SourceLocation::fromQSizeType(0, 0, readIntBinding(script), 1));
++ } else if (name == QLatin1String("type")) {
++ metaMethod.setReturnTypeName(readStringBinding(script));
++ } else if (name == QLatin1String("revision")) {
++ metaMethod.setRevision(readIntBinding(script));
++ } else if (name == QLatin1String("isCloned")) {
++ metaMethod.setIsCloned(readBoolBinding(script));
++ } else if (name == QLatin1String("isConstructor")) {
++ // The constructors in the moc json output are ordered the same
++ // way as the ones in the metaobject. qmltyperegistrar moves them into
++ // the same list as the other members, but maintains their order.
++ if (readBoolBinding(script)) {
++ metaMethod.setIsConstructor(true);
++ metaMethod.setConstructorIndex(
++ QQmlJSMetaMethod::RelativeFunctionIndex(m_currentCtorIndex++));
++ }
++ } else if (name == QLatin1String("isJavaScriptFunction")) {
++ metaMethod.setIsJavaScriptFunction(readBoolBinding(script));
++ } else if (name == QLatin1String("isList")) {
++ auto metaReturnType = metaMethod.returnValue();
++ metaReturnType.setIsList(readBoolBinding(script));
++ metaMethod.setReturnValue(metaReturnType);
++ } else if (name == QLatin1String("isPointer")) {
++ // TODO: We don't need this information. We can probably drop all isPointer members
++ // once we make sure that the type information is always complete. The
++ // description of the type being referenced has access semantics after all.
++ auto metaReturnType = metaMethod.returnValue();
++ metaReturnType.setIsPointer(readBoolBinding(script));
++ metaMethod.setReturnValue(metaReturnType);
++ } else if (name == QLatin1String("isTypeConstant")
++ || name == QLatin1String("isConstant")) {
++ // note: isConstant is only read for backwards compatibility
++ auto metaReturnType = metaMethod.returnValue();
++ metaReturnType.setTypeQualifier(readBoolBinding(script)
++ ? QQmlJSMetaParameter::Const
++ : QQmlJSMetaParameter::NonConst);
++ metaMethod.setReturnValue(metaReturnType);
++ } else if (name == QLatin1String("isMethodConstant")) {
++ metaMethod.setIsConst(readBoolBinding(script));
++ } else {
++ addWarning(script->firstSourceLocation(),
++ tr("Expected only name, lineNumber, type, revision, isPointer, "
++ "isTypeConstant, "
++ "isList, isCloned, isConstructor, isMethodConstant, and "
++ "isJavaScriptFunction in script bindings."));
++ }
++ } else {
++ addWarning(member->firstSourceLocation(),
++ tr("Expected only script bindings and object definitions."));
++ }
++ }
++
++ if (metaMethod.methodName().isEmpty()) {
++ addError(ast->firstSourceLocation(),
++ tr("Method or signal is missing a name script binding."));
++ return;
++ }
++
++ // Signals, slots and method share one index space. Constructors are separate.
++ // We also assume that the order and therefore the indexing of all methods is retained from
++ // moc's JSON output.
++ if (!metaMethod.isConstructor())
++ metaMethod.setMethodIndex(QQmlJSMetaMethod::RelativeFunctionIndex(m_currentMethodIndex++));
++
++ if (metaMethod.returnTypeName().isEmpty())
++ metaMethod.setReturnTypeName(QLatin1String("void"));
++
++ scope->addOwnMethod(metaMethod);
++ }
++
++ void QQmlJSTypeDescriptionReader::readProperty(UiObjectDefinition *ast, const QQmlJSScope::Ptr &scope)
++ {
++ QQmlJSMetaProperty property;
++ property.setIsWritable(true); // default is writable
++ bool isRequired = false;
++
++ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
++ UiObjectMember *member = it->member;
++ auto *script = cast<UiScriptBinding *>(member);
++ if (!script) {
++ addWarning(member->firstSourceLocation(), tr("Expected script binding."));
++ continue;
++ }
++
++ QString id = toString(script->qualifiedId);
++ if (id == QLatin1String("name")) {
++ property.setPropertyName(readStringBinding(script));
++ } else if (id == QLatin1String("lineNumber")) {
++ property.setSourceLocation(
++ SourceLocation::fromQSizeType(0, 0, readIntBinding(script), 1));
++ } else if (id == QLatin1String("type")) {
++ property.setTypeName(readStringBinding(script));
++ } else if (id == QLatin1String("isPointer")) {
++ property.setIsPointer(readBoolBinding(script));
++ } else if (id == QLatin1String("isReadonly")) {
++ property.setIsWritable(!readBoolBinding(script));
++ } else if (id == QLatin1String("isRequired")) {
++ isRequired = readBoolBinding(script);
++ } else if (id == QLatin1String("isList")) {
++ property.setIsList(readBoolBinding(script));
++ } else if (id == QLatin1String("isFinal")) {
++ property.setIsFinal(readBoolBinding(script));
++ } else if (id == QLatin1String("isVirtual")) {
++ property.setIsVirtual(readBoolBinding(script));
++ } else if (id == QLatin1String("isOverride")) {
++ property.setIsOverride(readBoolBinding(script));
++ } else if (id == QLatin1String("isTypeConstant")) {
++ property.setIsTypeConstant(readBoolBinding(script));
++ } else if (id == QLatin1String("isPropertyConstant")) {
++ property.setIsPropertyConstant(readBoolBinding(script));
++ } else if (id == QLatin1String("isConstant")) {
++ // support old "isConstant" for backwards compatibility
++ property.setIsPropertyConstant(readBoolBinding(script));
++ } else if (id == QLatin1String("revision")) {
++ property.setRevision(readIntBinding(script));
++ } else if (id == QLatin1String("bindable")) {
++ property.setBindable(readStringBinding(script));
++ } else if (id == QLatin1String("read")) {
++ property.setRead(readStringBinding(script));
++ } else if (id == QLatin1String("write")) {
++ property.setWrite(readStringBinding(script));
++ } else if (id == QLatin1String("reset")) {
++ property.setReset(readStringBinding(script));
++ } else if (id == QLatin1String("notify")) {
++ property.setNotify(readStringBinding(script));
++ } else if (id == QLatin1String("index")) {
++ property.setIndex(readIntBinding(script));
++ } else if (id == QLatin1String("privateClass")) {
++ property.setPrivateClass(readStringBinding(script));
++ } else {
++ addWarning(script->firstSourceLocation(),
++ tr("Expected only type, name, lineNumber, revision, isPointer, "
++ "isTypeConstant, isReadonly, isRequired, "
++ "isFinal, isList, bindable, read, write, isPropertyConstant, reset, "
++ "notify, index, privateClass and script bindings."));
++ }
++ }
++
++ if (property.propertyName().isEmpty()) {
++ addError(ast->firstSourceLocation(),
++ tr("Property object is missing a name script binding."));
++ return;
++ }
++
++ scope->addOwnProperty(property);
++ if (isRequired)
++ scope->setPropertyLocallyRequired(property.propertyName(), true);
++ }
++
++ void QQmlJSTypeDescriptionReader::readEnum(UiObjectDefinition *ast, const QQmlJSScope::Ptr &scope)
++ {
++ QQmlJSMetaEnum metaEnum;
++
++ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
++ UiObjectMember *member = it->member;
++ auto *script = cast<UiScriptBinding *>(member);
++ if (!script) {
++ addWarning(member->firstSourceLocation(), tr("Expected script binding."));
++ continue;
++ }
++
++ QString name = toString(script->qualifiedId);
++ if (name == QLatin1String("name")) {
++ metaEnum.setName(readStringBinding(script));
++ } else if (name == QLatin1String("alias")) {
++ metaEnum.setAlias(readStringBinding(script));
++ } else if (name == QLatin1String("isFlag")) {
++ metaEnum.setIsFlag(readBoolBinding(script));
++ } else if (name == QLatin1String("values")) {
++ readEnumValues(script, &metaEnum);
++ } else if (name == QLatin1String("isScoped")) {
++ metaEnum.setIsScoped(readBoolBinding(script));
++ } else if (name == QLatin1String("type")) {
++ metaEnum.setTypeName(readStringBinding(script));
++ } else if (name == QLatin1String("lineNumber")) {
++ metaEnum.setLineNumber(readIntBinding(script));
++ } else {
++ addWarning(script->firstSourceLocation(),
++ tr("Expected only name, alias, isFlag, values, isScoped, type, or "
++ "lineNumber."));
++ }
++ }
++
++ scope->addOwnEnumeration(metaEnum);
++ }
++
++ void QQmlJSTypeDescriptionReader::readParameter(UiObjectDefinition *ast, QQmlJSMetaMethod *metaMethod)
++ {
++ QString name;
++ QString type;
++ bool isConstant = false;
++ bool isPointer = false;
++ bool isList = false;
++
++ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
++ UiObjectMember *member = it->member;
++ auto *script = cast<UiScriptBinding *>(member);
++ if (!script) {
++ addWarning(member->firstSourceLocation(), tr("Expected script binding."));
++ continue;
++ }
++
++ const QString id = toString(script->qualifiedId);
++ if (id == QLatin1String("name")) {
++ name = readStringBinding(script);
++ } else if (id == QLatin1String("type")) {
++ type = readStringBinding(script);
++ } else if (id == QLatin1String("isPointer")) {
++ isPointer = readBoolBinding(script);
++ } else if (id == QLatin1String("isTypeConstant") || id == QLatin1String("isConstant")) {
++ // note: isConstant is only read for backwards compatibility
++ isConstant = readBoolBinding(script);
++ } else if (id == QLatin1String("isReadonly")) {
++ // ### unhandled
++ } else if (id == QLatin1String("isList")) {
++ isList = readBoolBinding(script);
++ } else {
++ addWarning(script->firstSourceLocation(),
++ tr("Expected only name, type, isPointer, isTypeConstant, isReadonly, "
++ "or IsList script bindings."));
++ }
++ }
++
++ QQmlJSMetaParameter p(name, type);
++ p.setTypeQualifier(isConstant ? QQmlJSMetaParameter::Const : QQmlJSMetaParameter::NonConst);
++ p.setIsPointer(isPointer);
++ p.setIsList(isList);
++ metaMethod->addParameter(std::move(p));
++ }
++
++ QString QQmlJSTypeDescriptionReader::readStringBinding(UiScriptBinding *ast)
++ {
++ Q_ASSERT(ast);
++
++ if (!ast->statement) {
++ addError(ast->colonToken, tr("Expected string after colon."));
++ return QString();
++ }
++
++ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
++ if (!expStmt) {
++ addError(ast->statement->firstSourceLocation(), tr("Expected string after colon."));
++ return QString();
++ }
++
++ auto *stringLit = cast<StringLiteral *>(expStmt->expression);
++ if (!stringLit) {
++ addError(expStmt->firstSourceLocation(), tr("Expected string after colon."));
++ return QString();
++ }
++
++ return stringLit->value.toString();
++ }
++
++ bool QQmlJSTypeDescriptionReader::readBoolBinding(UiScriptBinding *ast)
++ {
++ Q_ASSERT(ast);
++
++ if (!ast->statement) {
++ addError(ast->colonToken, tr("Expected boolean after colon."));
++ return false;
++ }
++
++ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
++ if (!expStmt) {
++ addError(ast->statement->firstSourceLocation(), tr("Expected boolean after colon."));
++ return false;
++ }
++
++ auto *trueLit = cast<TrueLiteral *>(expStmt->expression);
++ auto *falseLit = cast<FalseLiteral *>(expStmt->expression);
++ if (!trueLit && !falseLit) {
++ addError(expStmt->firstSourceLocation(), tr("Expected true or false after colon."));
++ return false;
++ }
++
++ return trueLit;
++ }
++
++ double QQmlJSTypeDescriptionReader::readNumericBinding(UiScriptBinding *ast)
++ {
++ Q_ASSERT(ast);
++
++ if (!ast->statement) {
++ addError(ast->colonToken, tr("Expected numeric literal after colon."));
++ return 0;
++ }
++
++ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
++ if (!expStmt) {
++ addError(ast->statement->firstSourceLocation(),
++ tr("Expected numeric literal after colon."));
++ return 0;
++ }
++
++ auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
++ if (!numericLit) {
++ addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon."));
++ return 0;
++ }
++
++ return numericLit->value;
++ }
++
++ static QTypeRevision parseVersion(const QString &versionString)
++ {
++ const int dotIdx = versionString.indexOf(QLatin1Char('.'));
++ if (dotIdx == -1)
++ return QTypeRevision();
++ bool ok = false;
++ const int maybeMajor = QStringView{versionString}.left(dotIdx).toInt(&ok);
++ if (!ok)
++ return QTypeRevision();
++ const int maybeMinor = QStringView{versionString}.mid(dotIdx + 1).toInt(&ok);
++ if (!ok)
++ return QTypeRevision();
++ return QTypeRevision::fromVersion(maybeMajor, maybeMinor);
++ }
++
++ QTypeRevision QQmlJSTypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast)
++ {
++ QTypeRevision invalidVersion;
++
++ if (!ast || !ast->statement) {
++ addError((ast ? ast->colonToken : SourceLocation()),
++ tr("Expected numeric literal after colon."));
++ return invalidVersion;
++ }
++
++ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
++ if (!expStmt) {
++ addError(ast->statement->firstSourceLocation(),
++ tr("Expected numeric literal after colon."));
++ return invalidVersion;
++ }
++
++ auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
++ if (!numericLit) {
++ addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon."));
++ return invalidVersion;
++ }
++
++ return parseVersion(m_source.mid(numericLit->literalToken.begin(),
++ numericLit->literalToken.length));
++ }
++
++ int QQmlJSTypeDescriptionReader::readIntBinding(UiScriptBinding *ast)
++ {
++ double v = readNumericBinding(ast);
++ int i = static_cast<int>(v);
++
++ if (i != v) {
++ addError(ast->firstSourceLocation(), tr("Expected integer after colon."));
++ return 0;
++ }
++
++ return i;
++ }
++
++ ArrayPattern* QQmlJSTypeDescriptionReader::getArray(UiScriptBinding *ast)
++ {
++ Q_ASSERT(ast);
++
++ if (!ast->statement) {
++ addError(ast->colonToken, tr("Expected array of strings after colon."));
++ return nullptr;
++ }
++
++ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
++ if (!expStmt) {
++ addError(ast->statement->firstSourceLocation(),
++ tr("Expected array of strings after colon."));
++ return nullptr;
++ }
++
++ auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
++ if (!arrayLit) {
++ addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon."));
++ return nullptr;
++ }
++
++ return arrayLit;
++ }
++
++ QList<QQmlJSScope::Export> QQmlJSTypeDescriptionReader::readExports(UiScriptBinding *ast)
++ {
++ QList<QQmlJSScope::Export> exports;
++ auto *arrayLit = getArray(ast);
++
++ if (!arrayLit)
++ return exports;
++
++ for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
++ auto *stringLit = cast<StringLiteral *>(it->element->initializer);
++
++ if (!stringLit) {
++ addError(arrayLit->firstSourceLocation(),
++ tr("Expected array literal with only string literal members."));
++ return exports;
++ }
++
++ QString exp = stringLit->value.toString();
++ int slashIdx = exp.indexOf(QLatin1Char('/'));
++ int spaceIdx = exp.indexOf(QLatin1Char(' '));
++ const QTypeRevision version = parseVersion(exp.mid(spaceIdx + 1));
++
++ if (spaceIdx == -1 || !version.isValid()) {
++ addError(stringLit->firstSourceLocation(),
++ tr("Expected string literal to contain 'Package/Name major.minor' "
++ "or 'Name major.minor'."));
++ continue;
++ }
++ QString package;
++ if (slashIdx != -1)
++ package = exp.left(slashIdx);
++ QString name = exp.mid(slashIdx + 1, spaceIdx - (slashIdx+1));
++
++ // ### relocatable exports where package is empty?
++ exports.append(QQmlJSScope::Export(package, name, version, version));
++ }
++
++ return exports;
++ }
++
++ void QQmlJSTypeDescriptionReader::readAliases(
++ QQmlJS::AST::UiScriptBinding *ast, const QQmlJSScope::Ptr &scope)
++ {
++ scope->setAliases(readStringList(ast));
++ }
++
++ void QQmlJSTypeDescriptionReader::readInterfaces(UiScriptBinding *ast, const QQmlJSScope::Ptr &scope)
++ {
++ auto *arrayLit = getArray(ast);
++
++ if (!arrayLit)
++ return;
++
++ QStringList list;
++
++ for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
++ auto *stringLit = cast<StringLiteral *>(it->element->initializer);
++ if (!stringLit) {
++ addError(arrayLit->firstSourceLocation(),
++ tr("Expected array literal with only string literal members."));
++ return;
++ }
++
++ list << stringLit->value.toString();
++ }
++
++ scope->setInterfaceNames(list);
++ }
++
++ void QQmlJSTypeDescriptionReader::checkMetaObjectRevisions(
++ UiScriptBinding *ast, QList<QQmlJSScope::Export> *exports)
++ {
++ Q_ASSERT(ast);
++
++ if (!ast->statement) {
++ addError(ast->colonToken, tr("Expected array of numbers after colon."));
++ return;
++ }
++
++ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
++ if (!expStmt) {
++ addError(ast->statement->firstSourceLocation(),
++ tr("Expected array of numbers after colon."));
++ return;
++ }
++
++ auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
++ if (!arrayLit) {
++ addError(expStmt->firstSourceLocation(), tr("Expected array of numbers after colon."));
++ return;
++ }
++
++ int exportIndex = 0;
++ const int exportCount = exports->size();
++ for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) {
++ auto *numberLit = cast<NumericLiteral *>(it->element->initializer);
++ if (!numberLit) {
++ addError(arrayLit->firstSourceLocation(),
++ tr("Expected array literal with only number literal members."));
++ return;
++ }
++
++ if (exportIndex >= exportCount) {
++ addError(numberLit->firstSourceLocation(),
++ tr("Meta object revision without matching export."));
++ return;
++ }
++
++ const double v = numberLit->value;
++ const int metaObjectRevision = static_cast<int>(v);
++ if (metaObjectRevision != v) {
++ addError(numberLit->firstSourceLocation(), tr("Expected integer."));
++ return;
++ }
++
++ const QTypeRevision metaObjectVersion
++ = QTypeRevision::fromEncodedVersion(metaObjectRevision);
++ const QQmlJSScope::Export &entry = exports->at(exportIndex);
++ const QTypeRevision exportVersion = entry.version();
++ if (metaObjectVersion != exportVersion) {
++ addWarning(numberLit->firstSourceLocation(),
++ tr("Meta object revision and export version differ.\n"
++ "Revision %1 corresponds to version %2.%3; it should be %4.%5.")
++ .arg(metaObjectRevision)
++ .arg(metaObjectVersion.majorVersion()).arg(metaObjectVersion.minorVersion())
++ .arg(exportVersion.majorVersion()).arg(exportVersion.minorVersion()));
++ (*exports)[exportIndex] = QQmlJSScope::Export(entry.package(), entry.type(),
++ exportVersion, metaObjectVersion);
++ }
++ }
++ }
++
++ QStringList QQmlJSTypeDescriptionReader::readStringList(UiScriptBinding *ast)
++ {
++ auto *arrayLit = getArray(ast);
++ if (!arrayLit)
++ return {};
++
++ QStringList list;
++
++ for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
++ auto *stringLit = cast<StringLiteral *>(it->element->initializer);
++ if (!stringLit) {
++ addError(arrayLit->firstSourceLocation(),
++ tr("Expected array literal with only string literal members."));
++ return {};
++ }
++
++ list << stringLit->value.toString();
++ }
++
++ return list;
++ }
++
++ void QQmlJSTypeDescriptionReader::readDeferredNames(UiScriptBinding *ast,
++ const QQmlJSScope::Ptr &scope)
++ {
++ scope->setOwnDeferredNames(readStringList(ast));
++ }
++
++ void QQmlJSTypeDescriptionReader::readImmediateNames(UiScriptBinding *ast,
++ const QQmlJSScope::Ptr &scope)
++ {
++ scope->setOwnImmediateNames(readStringList(ast));
++ }
++
++ void QQmlJSTypeDescriptionReader::readEnumValues(UiScriptBinding *ast, QQmlJSMetaEnum *metaEnum)
++ {
++ if (!ast)
++ return;
++ if (!ast->statement) {
++ addError(ast->colonToken, tr("Expected object literal after colon."));
++ return;
++ }
++
++ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
++ if (!expStmt) {
++ addError(ast->statement->firstSourceLocation(), tr("Expected expression after colon."));
++ return;
++ }
++
++ if (auto *objectLit = cast<ObjectPattern *>(expStmt->expression)) {
++ int currentValue = -1;
++ for (PatternPropertyList *it = objectLit->properties; it; it = it->next) {
++ if (PatternProperty *assignement = it->property) {
++ if (auto *name = cast<StringLiteralPropertyName *>(assignement->name)) {
++ metaEnum->addKey(name->id.toString());
++
++ if (auto *value = AST::cast<NumericLiteral *>(assignement->initializer)) {
++ currentValue = int(value->value);
++ } else if (auto *minus = AST::cast<UnaryMinusExpression *>(
++ assignement->initializer)) {
++ if (auto *value = AST::cast<NumericLiteral *>(minus->expression))
++ currentValue = -int(value->value);
++ else
++ ++currentValue;
++ } else {
++ ++currentValue;
++ }
++
++ metaEnum->addValue(currentValue);
++ continue;
++ }
++ }
++ addError(it->firstSourceLocation(), tr("Expected strings as enum keys."));
++ }
++ } else if (auto *arrayLit = cast<ArrayPattern *>(expStmt->expression)) {
++ for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
++ if (PatternElement *element = it->element) {
++ if (auto *name = cast<StringLiteral *>(element->initializer)) {
++ metaEnum->addKey(name->value.toString());
++ continue;
++ }
++ }
++ addError(it->firstSourceLocation(), tr("Expected strings as enum keys."));
++ }
++ } else {
++ addError(ast->statement->firstSourceLocation(),
++ tr("Expected either array or object literal as enum definition."));
++ }
++ }
++
++ QT_END_NAMESPACE