diff options
Diffstat (limited to 'src/qmlcompiler/qqmljstypedescriptionreader.cpp')
-rw-r--r-- | src/qmlcompiler/qqmljstypedescriptionreader.cpp | 878 |
1 files changed, 878 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljstypedescriptionreader.cpp b/src/qmlcompiler/qqmljstypedescriptionreader.cpp new file mode 100644 index 0000000000..b398137a23 --- /dev/null +++ b/src/qmlcompiler/qqmljstypedescriptionreader.cpp @@ -0,0 +1,878 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#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> + +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; + 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("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->setValueTypeName(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("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("deferredNames")) { + readDeferredNames(script, scope); + } else if (name == QLatin1String("immediateNames")) { + readImmediateNames(script, scope); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name, prototype, defaultProperty, attachedType, " + "valueType, exports, interfaces, isSingleton, isCreatable, " + "isStructured, isComposite, hasCustomParser, 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("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")) { + auto metaReturnType = metaMethod.returnValue(); + metaReturnType.setTypeQualifier(readBoolBinding(script) + ? QQmlJSMetaParameter::Const + : QQmlJSMetaParameter::NonConst); + metaMethod.setReturnValue(metaReturnType); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name, type, revision, isPointer, isTypeConstant, " + "isList, isCloned, isConstructor, 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; + } + + 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("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("isTypeConstant")) { + property.setIsTypeConstant(readBoolBinding(script)); + } else if (id == QLatin1String("isPropertyConstant")) { + 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, revision, isPointer, isTypeConstant, isReadonly, isRequired, " + "isFinal, isList, bindable, read, write, isPropertyConstant, reset, notify, index, and " + "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 { + addWarning(script->firstSourceLocation(), + tr("Expected only name, alias, isFlag, values, isScoped, or type.")); + } + } + + 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")) { + 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 |