diff options
Diffstat (limited to 'src')
26 files changed, 2799 insertions, 11 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d3c26e73e6..bac92c5a38 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,7 @@ add_subdirectory(plugins) if(QT_FEATURE_qml_devtools) add_subdirectory(qmldevtools) + add_subdirectory(qmlcompiler) # Build qmlcachegen now, so that we can use it in src/imports. if(QT_FEATURE_qml_devtools AND QT_FEATURE_xmlstreamwriter) diff --git a/src/qmlcompiler/CMakeLists.txt b/src/qmlcompiler/CMakeLists.txt new file mode 100644 index 0000000000..d6895704a4 --- /dev/null +++ b/src/qmlcompiler/CMakeLists.txt @@ -0,0 +1,25 @@ +# Generated from qmlcompiler.pro. + +##################################################################### +## QmlCompiler Module: +##################################################################### + +qt_add_module(QmlCompiler + STATIC + INTERNAL_MODULE + SOURCES + importedmembersvisitor.cpp importedmembersvisitor_p.h + metatypes_p.h + qmljsimporter.cpp qmljsimporter_p.h + qmljstypereader.cpp qmljstypereader_p.h + qmlstreamwriter.cpp qmlstreamwriter_p.h + resourcefilemapper.cpp resourcefilemapper_p.h + scopetree.cpp scopetree_p.h + typedescriptionreader.cpp typedescriptionreader_p.h + PUBLIC_LIBRARIES + Qt::CorePrivate + Qt::QmlDevToolsPrivate +) + +#### Keys ignored in scope 1:.:.:qmlcompiler.pro:<TRUE>: +# _OPTION = "host_build" diff --git a/src/qmlcompiler/QtBootstrap/QtBootstrap b/src/qmlcompiler/QtBootstrap/QtBootstrap new file mode 100644 index 0000000000..956abd4dbe --- /dev/null +++ b/src/qmlcompiler/QtBootstrap/QtBootstrap @@ -0,0 +1 @@ +// We need this because qmake insists on including it and QtBootstrap doesn't ship it. diff --git a/src/qmlcompiler/QtQmlDevTools/QtQmlDevTools b/src/qmlcompiler/QtQmlDevTools/QtQmlDevTools new file mode 100644 index 0000000000..f4bcaed435 --- /dev/null +++ b/src/qmlcompiler/QtQmlDevTools/QtQmlDevTools @@ -0,0 +1 @@ +// We need this because qmake insists on including it and QtQmlDevTools doesn't ship it. diff --git a/src/qmlcompiler/importedmembersvisitor.cpp b/src/qmlcompiler/importedmembersvisitor.cpp new file mode 100644 index 0000000000..655b77d91f --- /dev/null +++ b/src/qmlcompiler/importedmembersvisitor.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "importedmembersvisitor_p.h" +#include "scopetree_p.h" + +using namespace QQmlJS::AST; + +ScopeTree::Ptr ImportedMembersVisitor::result(const QString &scopeName) const +{ + ScopeTree::Ptr result = ScopeTree::create(); + result->setIsComposite(true); + result->setInternalName(scopeName); + result->setBaseTypeName(m_rootObject->baseTypeName()); + const auto properties = m_rootObject->properties(); + for (auto property : properties) { + if (property.isAlias()) { + const auto it = m_objects.find(property.typeName()); + if (it != m_objects.end()) + property.setType(*it); + result->addProperty(property); + } else { + result->addProperty(property); + } + } + + for (const auto &method : m_rootObject->methods()) + result->addMethod(method); + + for (const auto &enumerator : m_rootObject->enums()) + result->addEnum(enumerator); + + return result; +} + +bool ImportedMembersVisitor::visit(UiObjectDefinition *definition) +{ + ScopeTree::Ptr scope = ScopeTree::create(); + QString superType; + for (auto segment = definition->qualifiedTypeNameId; segment; segment = segment->next) { + if (!superType.isEmpty()) + superType.append(u'.'); + superType.append(segment->name.toString()); + } + scope->setBaseTypeName(superType); + if (!m_rootObject) + m_rootObject = scope; + m_currentObjects.append(scope); + return true; +} + +void ImportedMembersVisitor::endVisit(UiObjectDefinition *) +{ + m_currentObjects.pop_back(); +} + +bool ImportedMembersVisitor::visit(UiPublicMember *publicMember) +{ + switch (publicMember->type) { + case UiPublicMember::Signal: { + UiParameterList *param = publicMember->parameters; + MetaMethod method; + method.setMethodType(MetaMethod::Signal); + method.setMethodName(publicMember->name.toString()); + while (param) { + method.addParameter(param->name.toString(), param->type->name.toString()); + param = param->next; + } + currentObject()->addMethod(method); + break; + } + case UiPublicMember::Property: { + auto typeName = publicMember->memberType->name; + const bool isAlias = (typeName == QLatin1String("alias")); + if (isAlias) { + const auto expression = cast<ExpressionStatement *>(publicMember->statement); + if (const auto idExpression = cast<IdentifierExpression *>(expression->expression)) + typeName = idExpression->name; + } + MetaProperty prop { + publicMember->name.toString(), + typeName.toString(), + false, + false, + false, + isAlias, + 0 + }; + currentObject()->addProperty(prop); + break; + } + } + return true; +} + +bool ImportedMembersVisitor::visit(UiSourceElement *sourceElement) +{ + if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { + MetaMethod method; + method.setMethodName(fexpr->name.toString()); + method.setMethodType(MetaMethod::Method); + FormalParameterList *parameters = fexpr->formals; + while (parameters) { + method.addParameter(parameters->element->bindingIdentifier.toString(), QString()); + parameters = parameters->next; + } + currentObject()->addMethod(method); + } else if (ClassExpression *clexpr = sourceElement->sourceElement->asClassDefinition()) { + MetaProperty prop { clexpr->name.toString(), QString(), false, false, false, false, 1 }; + currentObject()->addProperty(prop); + } else if (cast<VariableStatement *>(sourceElement->sourceElement)) { + // nothing to do + } else { + const auto loc = sourceElement->firstSourceLocation(); + m_errors.append( + QStringLiteral("unsupportedd sourceElement at ") + + QString::fromLatin1("%1:%2: ").arg(loc.startLine).arg(loc.startColumn) + + QString::number(sourceElement->sourceElement->kind)); + } + return true; +} + +bool ImportedMembersVisitor::visit(UiScriptBinding *scriptBinding) +{ + if (scriptBinding->qualifiedId->name == QLatin1String("id")) { + const auto *statement = cast<ExpressionStatement *>(scriptBinding->statement); + const auto *idExprension = cast<IdentifierExpression *>(statement->expression); + m_objects.insert(idExprension->name.toString(), currentObject()); + } + return true; +} + +bool ImportedMembersVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied) +{ + MetaEnum qmlEnum(uied->name.toString()); + for (const auto *member = uied->members; member; member = member->next) + qmlEnum.addKey(member->member.toString()); + currentObject()->addEnum(qmlEnum); + return true; +} + +void ImportedMembersVisitor::throwRecursionDepthError() +{ + m_errors.append(QStringLiteral("Maximum statement or expression depth exceeded")); +} diff --git a/src/qmlcompiler/importedmembersvisitor_p.h b/src/qmlcompiler/importedmembersvisitor_p.h new file mode 100644 index 0000000000..b909265f0b --- /dev/null +++ b/src/qmlcompiler/importedmembersvisitor_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef IMPORTEDMEMBERSVISITOR_H +#define IMPORTEDMEMBERSVISITOR_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "scopetree_p.h" + +#include <private/qqmljsast_p.h> + +class ImportedMembersVisitor : public QQmlJS::AST::Visitor +{ +public: + ScopeTree::Ptr result(const QString &scopeName) const; + QStringList errors() const { return m_errors; } + +private: + bool visit(QQmlJS::AST::UiObjectDefinition *) override; + void endVisit(QQmlJS::AST::UiObjectDefinition *) override; + bool visit(QQmlJS::AST::UiPublicMember *) override; + bool visit(QQmlJS::AST::UiSourceElement *) override; + bool visit(QQmlJS::AST::UiScriptBinding *) override; + bool visit(QQmlJS::AST::UiEnumDeclaration *uied) override; + void throwRecursionDepthError() override; + + ScopeTree::Ptr currentObject() const { return m_currentObjects.back(); } + + QVector<ScopeTree::Ptr> m_currentObjects; + ScopeTree::ConstPtr m_rootObject; + QHash<QString, ScopeTree::Ptr> m_objects; + + QStringList m_errors; +}; + +#endif // IMPORTEDMEMBERSVISITOR_H diff --git a/src/qmlcompiler/metatypes_p.h b/src/qmlcompiler/metatypes_p.h new file mode 100644 index 0000000000..d462fb684f --- /dev/null +++ b/src/qmlcompiler/metatypes_p.h @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef METATYPES_H +#define METATYPES_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include <QtCore/qstring.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qsharedpointer.h> + +// MetaMethod and MetaProperty have both type names and actual ScopeTree types. +// When parsing the information from the relevant QML or qmltypes files, we only +// see the names and don't have a complete picture of the types, yet. In a second +// pass we typically fill in the types. The types may have multiple exported names +// and the the name property of MetaProperty and MetaMethod still carries some +// significance regarding which name was chosen to refer to the type. In a third +// pass we may further specify the type if the context provides additional information. +// The parent of an Item, for example, is typically not just a QtObject, but rather +// some other Item with custom properties. + +class ScopeTree; +class MetaEnum +{ + QStringList m_keys; + QString m_name; + QString m_alias; + bool m_isFlag = false; + +public: + MetaEnum() = default; + explicit MetaEnum(QString name) : m_name(std::move(name)) {} + + bool isValid() const { return !m_name.isEmpty(); } + + QString name() const { return m_name; } + void setName(const QString &name) { m_name = name; } + + QString alias() const { return m_alias; } + void setAlias(const QString &alias) { m_alias = alias; } + + bool isFlag() const { return m_isFlag; } + void setIsFlag(bool isFlag) { m_isFlag = isFlag; } + + void addKey(const QString &key) { m_keys.append(key); } + QStringList keys() const { return m_keys; } +}; + +class MetaMethod +{ +public: + enum Type { + Signal, + Slot, + Method + }; + + enum Access { + Private, + Protected, + Public + }; + + MetaMethod() = default; + explicit MetaMethod(QString name, QString returnType = QString()) + : m_name(std::move(name)) + , m_returnTypeName(std::move(returnType)) + , m_methodType(Method) + , m_methodAccess(Public) + {} + + QString methodName() const { return m_name; } + void setMethodName(const QString &name) { m_name = name; } + + QString returnTypeName() const { return m_returnTypeName; } + QSharedPointer<const ScopeTree> returnType() const { return m_returnType.toStrongRef(); } + void setReturnTypeName(const QString &type) { m_returnTypeName = type; } + void setReturnType(const QSharedPointer<const ScopeTree> &type) + { + m_returnType = type; + } + + QStringList parameterNames() const { return m_paramNames; } + QStringList parameterTypeNames() const { return m_paramTypeNames; } + QList<QSharedPointer<const ScopeTree>> parameterTypes() const + { + QList<QSharedPointer<const ScopeTree>> result; + for (const auto &type : m_paramTypes) + result.append(type.toStrongRef()); + return result; + } + void setParameterTypes(const QList<QSharedPointer<const ScopeTree>> &types) + { + Q_ASSERT(types.length() == m_paramNames.length()); + m_paramTypes.clear(); + for (const auto &type : types) + m_paramTypes.append(type); + } + void addParameter(const QString &name, const QString &typeName, + const QSharedPointer<const ScopeTree> &type = {}) + { + m_paramNames.append(name); + m_paramTypeNames.append(typeName); + m_paramTypes.append(type); + } + + int methodType() const { return m_methodType; } + void setMethodType(Type methodType) { m_methodType = methodType; } + + Access access() const { return m_methodAccess; } + + int revision() const { return m_revision; } + void setRevision(int r) { m_revision = r; } + +private: + QString m_name; + QString m_returnTypeName; + QWeakPointer<const ScopeTree> m_returnType; + + QStringList m_paramNames; + QStringList m_paramTypeNames; + QList<QWeakPointer<const ScopeTree>> m_paramTypes; + + Type m_methodType = Signal; + Access m_methodAccess = Private; + int m_revision = 0; +}; + +class MetaProperty +{ + QString m_propertyName; + QString m_typeName; + QWeakPointer<const ScopeTree> m_type; + bool m_isList; + bool m_isWritable; + bool m_isPointer; + bool m_isAlias; + int m_revision; + +public: + MetaProperty(QString propertyName, QString typeName, + bool isList, bool isWritable, bool isPointer, bool isAlias, + int revision) + : m_propertyName(std::move(propertyName)) + , m_typeName(std::move(typeName)) + , m_isList(isList) + , m_isWritable(isWritable) + , m_isPointer(isPointer) + , m_isAlias(isAlias) + , m_revision(revision) + {} + + QString propertyName() const { return m_propertyName; } + QString typeName() const { return m_typeName; } + + void setType(const QSharedPointer<const ScopeTree> &type) { m_type = type; } + QSharedPointer<const ScopeTree> type() const { return m_type.toStrongRef(); } + + bool isList() const { return m_isList; } + bool isWritable() const { return m_isWritable; } + bool isPointer() const { return m_isPointer; } + bool isAlias() const { return m_isAlias; } + int revision() const { return m_revision; } +}; + +#endif // METATYPES_H diff --git a/src/qmlcompiler/qmlcompiler.pro b/src/qmlcompiler/qmlcompiler.pro new file mode 100644 index 0000000000..906286551f --- /dev/null +++ b/src/qmlcompiler/qmlcompiler.pro @@ -0,0 +1,25 @@ +option(host_build) +TARGET = QtQmlCompiler +QT = core-private qmldevtools-private +CONFIG += internal_module + +SOURCES = \ + resourcefilemapper.cpp \ + importedmembersvisitor.cpp \ + qmljsimporter.cpp \ + qmljstypereader.cpp \ + scopetree.cpp \ + typedescriptionreader.cpp \ + qmlstreamwriter.cpp + +HEADERS = \ + resourcefilemapper_p.h \ + importedmembersvisitor_p.h \ + qmljsimporter_p.h \ + qmljstypereader_p.h \ + metatypes_p.h \ + scopetree_p.h \ + typedescriptionreader_p.h \ + qmlstreamwriter_p.h + +load(qt_module) diff --git a/src/qmlcompiler/qmljsimporter.cpp b/src/qmlcompiler/qmljsimporter.cpp new file mode 100644 index 0000000000..a5701c72d4 --- /dev/null +++ b/src/qmlcompiler/qmljsimporter.cpp @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmljsimporter_p.h" +#include "typedescriptionreader_p.h" +#include "qmljstypereader_p.h" + +#include <QtQml/private/qqmlimportresolver_p.h> + +#include <QtCore/qfileinfo.h> +#include <QtCore/qdiriterator.h> + +static const QLatin1String SlashQmldir = QLatin1String("/qmldir"); +static const QLatin1String SlashPluginsDotQmltypes = QLatin1String("/plugins.qmltypes"); + +static const QString prefixedName(const QString &prefix, const QString &name) +{ + Q_ASSERT(!prefix.endsWith(u'.')); + return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name); +} + +static QQmlDirParser createQmldirParserForFile(const QString &filename) +{ + QFile f(filename); + f.open(QFile::ReadOnly); + QQmlDirParser parser; + parser.parse(QString::fromUtf8(f.readAll())); + return parser; +} + +void QmlJSImporter::readQmltypes( + const QString &filename, QHash<QString, ScopeTree::Ptr> *objects) +{ + const QFileInfo fileInfo(filename); + if (!fileInfo.exists()) { + m_warnings.append(QLatin1String("QML types file does not exist: ") + filename); + return; + } + + if (fileInfo.isDir()) { + m_warnings.append(QLatin1String("QML types file cannot be a directory: ") + filename); + return; + } + + QFile file(filename); + file.open(QFile::ReadOnly); + TypeDescriptionReader reader { filename, QString::fromUtf8(file.readAll()) }; + QStringList dependencies; + auto succ = reader(objects, &dependencies); + if (!succ) + m_warnings.append(reader.errorMessage()); +} + +QmlJSImporter::Import QmlJSImporter::readQmldir(const QString &path) +{ + Import result; + auto reader = createQmldirParserForFile(path + SlashQmldir); + result.imports.append(reader.imports()); + result.dependencies.append(reader.dependencies()); + + QHash<QString, ScopeTree::Ptr> qmlComponents; + const auto components = reader.components(); + for (auto it = components.begin(), end = components.end(); it != end; ++it) { + const QString filePath = path + QLatin1Char('/') + it->fileName; + if (!QFile::exists(filePath)) { + m_warnings.append(it->fileName + QLatin1String(" is listed as component in ") + + path + SlashQmldir + + QLatin1String(" but does not exist.\n")); + continue; + } + + auto mo = qmlComponents.find(it.key()); + if (mo == qmlComponents.end()) + mo = qmlComponents.insert(it.key(), localFile2ScopeTree(filePath)); + + (*mo)->addExport(it.key(), reader.typeNamespace(), it->version); + } + for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) + result.objects.insert(it.key(), it.value()); + + if (!reader.plugins().isEmpty() && QFile::exists(path + SlashPluginsDotQmltypes)) + readQmltypes(path + SlashPluginsDotQmltypes, &result.objects); + + const auto scripts = reader.scripts(); + for (const auto &script : scripts) { + const QString filePath = path + QLatin1Char('/') + script.fileName; + result.scripts.insert(script.nameSpace, localFile2ScopeTree(filePath)); + } + return result; +} + +void QmlJSImporter::importDependencies( + const QmlJSImporter::Import &import, + QmlJSImporter::AvailableTypes *types, const QString &prefix, QTypeRevision version) +{ + // Import the dependencies with an invalid prefix. The prefix will never be matched by actual + // QML code but the C++ types will be visible. + const QString invalidPrefix = QString::fromLatin1("$dependency$"); + for (auto const &dependency : qAsConst(import.dependencies)) + importHelper(dependency.module, types, invalidPrefix, dependency.version); + + for (auto const &import : qAsConst(import.imports)) { + importHelper(import.module, types, prefix, + import.isAutoImport ? version : import.version); + } +} + +void QmlJSImporter::processImport( + const QmlJSImporter::Import &import, + QmlJSImporter::AvailableTypes *types, + const QString &prefix) +{ + for (auto it = import.scripts.begin(); it != import.scripts.end(); ++it) + types->qmlNames.insert(prefixedName(prefix, it.key()), it.value()); + + // add objects + for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { + const auto &val = it.value(); + types->cppNames.insert(val->internalName(), val); + + const auto exports = val->exports(); + for (const auto &valExport : exports) + types->qmlNames.insert(prefixedName(prefix, valExport.type()), val); + } + + for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { + const auto &val = it.value(); + if (!val->isComposite()) // Otherwise we have already done it in localFile2ScopeTree() + val->resolveTypes(types->cppNames); + } +} + +/*! + * Imports builtins.qmltypes found in any of the import paths. + */ +QmlJSImporter::ImportedTypes QmlJSImporter::importBuiltins() +{ + AvailableTypes types; + + for (auto const &dir : m_importPaths) { + Import result; + QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter, + QDirIterator::Subdirectories }; + while (it.hasNext()) + readQmltypes(it.next(), &result.objects); + importDependencies(result, &types); + processImport(result, &types); + } + + return types.qmlNames; +} + +/*! + * Imports types from the specified \a qmltypesFiles. + */ +QmlJSImporter::ImportedTypes QmlJSImporter::importQmltypes(const QStringList &qmltypesFiles) +{ + AvailableTypes types; + Import result; + + for (const auto &qmltypeFile : qmltypesFiles) + readQmltypes(qmltypeFile, &result.objects); + + importDependencies(result, &types); + processImport(result, &types); + + return types.qmlNames; +} + +QmlJSImporter::ImportedTypes QmlJSImporter::importModule( + const QString &module, const QString &prefix, QTypeRevision version) +{ + AvailableTypes result; + importHelper(module, &result, prefix, version); + return result.qmlNames; +} + +void QmlJSImporter::importHelper(const QString &module, AvailableTypes *types, + const QString &prefix, QTypeRevision version) +{ + + const QPair<QString, QTypeRevision> importId { module, version }; + const auto it = m_seenImports.find(importId); + if (it != m_seenImports.end()) { + importDependencies(*it, types, prefix, version); + processImport(*it, types, prefix); + return; + } + + const auto qmltypesPaths = qQmlResolveImportPaths(module, m_importPaths, version); + for (auto const &qmltypesPath : qmltypesPaths) { + const QFileInfo file(qmltypesPath + SlashQmldir); + if (file.exists()) { + const auto import = readQmldir(file.canonicalPath()); + m_seenImports.insert(importId, import); + importDependencies(import, types, prefix, version); + processImport(import, types, prefix); + return; + } + } + + m_seenImports.insert(importId, {}); +} + +ScopeTree::Ptr QmlJSImporter::localFile2ScopeTree(const QString &filePath) +{ + const auto seen = m_importedFiles.find(filePath); + if (seen != m_importedFiles.end()) + return *seen; + + QmlJSTypeReader typeReader(filePath); + ScopeTree::Ptr result = typeReader(); + m_importedFiles.insert(filePath, result); + + const QStringList errors = typeReader.errors(); + for (const QString &error : errors) + m_warnings.append(error); + + AvailableTypes types; + + QDirIterator it { + QFileInfo(filePath).canonicalPath(), + QStringList() << QLatin1String("*.qml"), + QDir::NoFilter + }; + while (it.hasNext()) { + ScopeTree::Ptr scope(localFile2ScopeTree(it.next())); + if (!scope->internalName().isEmpty()) + types.qmlNames.insert(scope->internalName(), scope); + } + + const auto imports = typeReader.imports(); + for (const auto &import : imports) + importHelper(import.module, &types, import.prefix, import.version); + + result->resolveTypes(types.qmlNames); + return result; +} + +QmlJSImporter::ImportedTypes QmlJSImporter::importFileOrDirectory( + const QString &fileOrDirectory, const QString &prefix) +{ + AvailableTypes result; + + QString name = fileOrDirectory; + + QFileInfo fileInfo(name); + if (fileInfo.isFile()) { + ScopeTree::Ptr scope(localFile2ScopeTree(fileInfo.canonicalFilePath())); + result.qmlNames.insert(prefix.isEmpty() ? scope->internalName() : prefix, scope); + return result.qmlNames; + } + + QDirIterator it { + fileInfo.canonicalFilePath(), + QStringList() << QLatin1String("*.qml"), + QDir::NoFilter + }; + while (it.hasNext()) { + ScopeTree::Ptr scope(localFile2ScopeTree(it.next())); + if (!scope->internalName().isEmpty()) + result.qmlNames.insert(prefixedName(prefix, scope->internalName()), scope); + } + + return result.qmlNames; +} diff --git a/src/qmlcompiler/qmljsimporter_p.h b/src/qmlcompiler/qmljsimporter_p.h new file mode 100644 index 0000000000..0fb81dac5f --- /dev/null +++ b/src/qmlcompiler/qmljsimporter_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLJSIMPORTER_H +#define QMLJSIMPORTER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "scopetree_p.h" +#include <QtQml/private/qqmldirparser_p.h> + +class QmlJSImporter +{ +public: + using ImportedTypes = QHash<QString, ScopeTree::ConstPtr>; + + QmlJSImporter(const QStringList &importPaths) : m_importPaths(importPaths) {} + + ImportedTypes importBuiltins(); + ImportedTypes importQmltypes(const QStringList &qmltypesFiles); + ImportedTypes importFileOrDirectory( + const QString &fileOrDirectory, const QString &prefix = QString()); + ImportedTypes importModule( + const QString &module, const QString &prefix = QString(), + QTypeRevision version = QTypeRevision()); + + QStringList takeWarnings() + { + QStringList result = std::move(m_warnings); + m_warnings.clear(); + return result; + } + +private: + struct AvailableTypes + { + // C++ names used in qmltypes files for non-composite types + QHash<QString, ScopeTree::ConstPtr> cppNames; + + // Names the importing component sees, including any prefixes + QHash<QString, ScopeTree::ConstPtr> qmlNames; + }; + + struct Import { + QHash<QString, ScopeTree::Ptr> objects; + QHash<QString, ScopeTree::Ptr> scripts; + QList<QQmlDirParser::Import> imports; + QList<QQmlDirParser::Import> dependencies; + }; + + void importHelper(const QString &module, AvailableTypes *types, + const QString &prefix = QString(), + QTypeRevision version = QTypeRevision()); + void processImport(const Import &import, AvailableTypes *types, + const QString &prefix = QString()); + void importDependencies(const QmlJSImporter::Import &import, + AvailableTypes *types, + const QString &prefix = QString(), + QTypeRevision version = QTypeRevision()); + void readQmltypes(const QString &filename, QHash<QString, ScopeTree::Ptr> *objects); + Import readQmldir(const QString &dirname); + ScopeTree::Ptr localFile2ScopeTree(const QString &filePath); + + QStringList m_importPaths; + QHash<QPair<QString, QTypeRevision>, Import> m_seenImports; + QHash<QString, ScopeTree::Ptr> m_importedFiles; + QStringList m_warnings; +}; + +#endif // QMLJSIMPORTER_H diff --git a/src/qmlcompiler/qmljstypereader.cpp b/src/qmlcompiler/qmljstypereader.cpp new file mode 100644 index 0000000000..ef2f5c2cbb --- /dev/null +++ b/src/qmlcompiler/qmljstypereader.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmljstypereader_p.h" +#include "importedmembersvisitor_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQml/private/qqmlimportresolver_p.h> + +#include <QtCore/qfileinfo.h> +#include <QtCore/qdebug.h> + +static QList<QmlJSTypeReader::Import> parseHeaders(QQmlJS::AST::UiHeaderItemList *header) +{ + using namespace QQmlJS::AST; + QList<QmlJSTypeReader::Import> imports; + + for (; header; header = header->next) { + auto import = cast<UiImport *>(header->headerItem); + if (!import) + continue; + + QString path; + auto uri = import->importUri; + while (uri) { + path.append(uri->name); + path.append(u'.'); + uri = uri->next; + } + path.chop(1); + imports.append({ + path, + import->version ? import->version->version : QTypeRevision(), + import->asToken.isValid() ? import->importId.toString() : QString() + }); + } + + return imports; +} + +static ScopeTree::Ptr parseProgram(QQmlJS::AST::Program *program, const QString &name) +{ + using namespace QQmlJS::AST; + ScopeTree::Ptr result = ScopeTree::create(ScopeType::JSLexicalScope); + result->setInternalName(name); + for (auto *statement = program->statements; statement; statement = statement->next) { + if (auto *function = cast<FunctionDeclaration *>(statement->statement)) { + MetaMethod method(function->name.toString()); + method.setMethodType(MetaMethod::Method); + for (auto *parameters = function->formals; parameters; parameters = parameters->next) + method.addParameter(parameters->element->bindingIdentifier.toString(), QString()); + result->addMethod(method); + } + } + return result; +} + +ScopeTree::Ptr QmlJSTypeReader::operator()() +{ + using namespace QQmlJS::AST; + const QFileInfo info { m_file }; + QString baseName = info.baseName(); + const QString scopeName = baseName.endsWith(QStringLiteral(".ui")) ? baseName.chopped(3) + : baseName; + + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + + const QString lowerSuffix = info.suffix().toLower(); + const bool isESModule = lowerSuffix == QLatin1String("mjs"); + const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js"); + + QFile file(m_file); + if (!file.open(QFile::ReadOnly)) { + ScopeTree::Ptr result = ScopeTree::create( + isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope); + result->setInternalName(scopeName); + return result; + } + + QString code = QString::fromUtf8(file.readAll()); + file.close(); + + lexer.setCode(code, /*line = */ 1, /*qmlMode=*/ !isJavaScript); + QQmlJS::Parser parser(&engine); + + const bool success = isJavaScript ? (isESModule ? parser.parseModule() + : parser.parseProgram()) + : parser.parse(); + if (!success) { + ScopeTree::Ptr result = ScopeTree::create( + isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope); + result->setInternalName(scopeName); + return result; + } + + if (!isJavaScript) { + QQmlJS::AST::UiProgram *program = parser.ast(); + m_imports = parseHeaders(program->headers); + ImportedMembersVisitor membersVisitor; + program->members->accept(&membersVisitor); + m_errors = membersVisitor.errors(); + return membersVisitor.result(scopeName); + } + + // TODO: Anything special to do with ES modules here? + return parseProgram(QQmlJS::AST::cast<QQmlJS::AST::Program *>(parser.rootNode()), scopeName); +} diff --git a/src/qmlcompiler/qmljstypereader_p.h b/src/qmlcompiler/qmljstypereader_p.h new file mode 100644 index 0000000000..d3c361ff97 --- /dev/null +++ b/src/qmlcompiler/qmljstypereader_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLJSTYPERADER_H +#define QMLJSTYPERADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "scopetree_p.h" + +#include <QtQml/private/qqmljsastfwd_p.h> + +#include <QtCore/qpair.h> +#include <QtCore/qset.h> + +class QmlJSTypeReader +{ +public: + struct Import { + QString module; + QTypeRevision version; + QString prefix; + }; + + QmlJSTypeReader(const QString &file) : m_file(file) {} + + ScopeTree::Ptr operator()(); + QList<Import> imports() const { return m_imports; } + QStringList errors() const { return m_errors; } + +private: + QString m_file; + QList<Import> m_imports; + QStringList m_errors; +}; + +#endif // QMLJSTYPEREADER_H diff --git a/src/qmlcompiler/qmlstreamwriter.cpp b/src/qmlcompiler/qmlstreamwriter.cpp new file mode 100644 index 0000000000..b5b9ee0d4a --- /dev/null +++ b/src/qmlcompiler/qmlstreamwriter.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlstreamwriter_p.h" + +#include <QtCore/QBuffer> +#include <QtCore/QStringList> + +QmlStreamWriter::QmlStreamWriter(QByteArray *array) + : m_indentDepth(0) + , m_pendingLineLength(0) + , m_maybeOneline(false) + , m_stream(new QBuffer(array)) +{ + m_stream->open(QIODevice::WriteOnly); +} + +void QmlStreamWriter::writeStartDocument() +{ +} + +void QmlStreamWriter::writeEndDocument() +{ +} + +void QmlStreamWriter::writeLibraryImport(const QString &uri, int majorVersion, int minorVersion, const QString &as) +{ + m_stream->write(QString::fromLatin1("import %1 %2.%3").arg(uri, QString::number(majorVersion), QString::number(minorVersion)).toUtf8()); + if (!as.isEmpty()) + m_stream->write(QString::fromLatin1(" as %1").arg(as).toUtf8()); + m_stream->write("\n"); +} + +void QmlStreamWriter::writeStartObject(const QString &component) +{ + flushPotentialLinesWithNewlines(); + writeIndent(); + m_stream->write(QString::fromLatin1("%1 {").arg(component).toUtf8()); + ++m_indentDepth; + m_maybeOneline = true; +} + +void QmlStreamWriter::writeEndObject() +{ + if (m_maybeOneline && !m_pendingLines.isEmpty()) { + --m_indentDepth; + for (int i = 0; i < m_pendingLines.size(); ++i) { + m_stream->write(" "); + m_stream->write(m_pendingLines.at(i).trimmed()); + if (i != m_pendingLines.size() - 1) + m_stream->write(";"); + } + m_stream->write(" }\n"); + m_pendingLines.clear(); + m_pendingLineLength = 0; + m_maybeOneline = false; + } else { + flushPotentialLinesWithNewlines(); + --m_indentDepth; + writeIndent(); + m_stream->write("}\n"); + } +} + +void QmlStreamWriter::writeScriptBinding(const QString &name, const QString &rhs) +{ + writePotentialLine(QString::fromLatin1("%1: %2").arg(name, rhs).toUtf8()); +} + +void QmlStreamWriter::writeBooleanBinding(const QString &name, bool value) +{ + writeScriptBinding(name, value ? QLatin1String("true") : QLatin1String("false")); +} + +void QmlStreamWriter::writeArrayBinding(const QString &name, const QStringList &elements) +{ + flushPotentialLinesWithNewlines(); + writeIndent(); + + // try to use a single line + QString singleLine; + singleLine += QString::fromLatin1("%1: [").arg(name); + for (int i = 0; i < elements.size(); ++i) { + singleLine += elements.at(i); + if (i != elements.size() - 1) + singleLine += QLatin1String(", "); + } + singleLine += QLatin1String("]\n"); + if (singleLine.size() + m_indentDepth * 4 < 80) { + m_stream->write(singleLine.toUtf8()); + return; + } + + // write multi-line + m_stream->write(QString::fromLatin1("%1: [\n").arg(name).toUtf8()); + ++m_indentDepth; + for (int i = 0; i < elements.size(); ++i) { + writeIndent(); + m_stream->write(elements.at(i).toUtf8()); + if (i != elements.size() - 1) { + m_stream->write(",\n"); + } else { + m_stream->write("\n"); + } + } + --m_indentDepth; + writeIndent(); + m_stream->write("]\n"); +} + +void QmlStreamWriter::write(const QString &data) +{ + flushPotentialLinesWithNewlines(); + m_stream->write(data.toUtf8()); +} + +void QmlStreamWriter::writeScriptObjectLiteralBinding(const QString &name, const QList<QPair<QString, QString> > &keyValue) +{ + flushPotentialLinesWithNewlines(); + writeIndent(); + m_stream->write(QString::fromLatin1("%1: {\n").arg(name).toUtf8()); + ++m_indentDepth; + for (int i = 0; i < keyValue.size(); ++i) { + const QString key = keyValue.at(i).first; + const QString value = keyValue.at(i).second; + writeIndent(); + m_stream->write(QString::fromLatin1("%1: %2").arg(key, value).toUtf8()); + if (i != keyValue.size() - 1) { + m_stream->write(",\n"); + } else { + m_stream->write("\n"); + } + } + --m_indentDepth; + writeIndent(); + m_stream->write("}\n"); +} + +void QmlStreamWriter::writeIndent() +{ + m_stream->write(QByteArray(m_indentDepth * 4, ' ')); +} + +void QmlStreamWriter::writePotentialLine(const QByteArray &line) +{ + m_pendingLines.append(line); + m_pendingLineLength += line.size(); + if (m_pendingLineLength >= 80) { + flushPotentialLinesWithNewlines(); + } +} + +void QmlStreamWriter::flushPotentialLinesWithNewlines() +{ + if (m_maybeOneline) + m_stream->write("\n"); + for (const QByteArray &line : qAsConst(m_pendingLines)) { + writeIndent(); + m_stream->write(line); + m_stream->write("\n"); + } + m_pendingLines.clear(); + m_pendingLineLength = 0; + m_maybeOneline = false; +} diff --git a/src/qmlcompiler/qmlstreamwriter_p.h b/src/qmlcompiler/qmlstreamwriter_p.h new file mode 100644 index 0000000000..260923feed --- /dev/null +++ b/src/qmlcompiler/qmlstreamwriter_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLSTREAMWRITER_H +#define QMLSTREAMWRITER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include <QtCore/QIODevice> +#include <QtCore/QList> +#include <QtCore/QString> +#include <QtCore/QScopedPointer> +#include <QtCore/QPair> + +class QmlStreamWriter +{ +public: + QmlStreamWriter(QByteArray *array); + + void writeStartDocument(); + void writeEndDocument(); + void writeLibraryImport(const QString &uri, int majorVersion, int minorVersion, const QString &as = QString()); + //void writeFilesystemImport(const QString &file, const QString &as = QString()); + void writeStartObject(const QString &component); + void writeEndObject(); + void writeScriptBinding(const QString &name, const QString &rhs); + void writeScriptObjectLiteralBinding(const QString &name, const QList<QPair<QString, QString> > &keyValue); + void writeArrayBinding(const QString &name, const QStringList &elements); + void write(const QString &data); + void writeBooleanBinding(const QString &name, bool value); + +private: + void writeIndent(); + void writePotentialLine(const QByteArray &line); + void flushPotentialLinesWithNewlines(); + + int m_indentDepth; + QList<QByteArray> m_pendingLines; + int m_pendingLineLength; + bool m_maybeOneline; + QScopedPointer<QIODevice> m_stream; +}; + +#endif // QMLSTREAMWRITER_H diff --git a/src/qmlcompiler/resourcefilemapper.cpp b/src/qmlcompiler/resourcefilemapper.cpp new file mode 100644 index 0000000000..d97aa27695 --- /dev/null +++ b/src/qmlcompiler/resourcefilemapper.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "resourcefilemapper_p.h" + +#include <QFileInfo> +#include <QDir> +#include <QXmlStreamReader> + +ResourceFileMapper::ResourceFileMapper(const QStringList &resourceFiles) +{ + for (const QString &fileName: resourceFiles) { + QFile f(fileName); + if (!f.open(QIODevice::ReadOnly)) + continue; + populateFromQrcFile(f); + } +} + +bool ResourceFileMapper::isEmpty() const +{ + return qrcPathToFileSystemPath.isEmpty(); +} + +QStringList ResourceFileMapper::resourcePaths(const QString &fileName) +{ + const QString absPath = QDir::cleanPath(QDir::current().absoluteFilePath(fileName)); + QStringList resourcePaths; + for (auto it = qrcPathToFileSystemPath.cbegin(), end = qrcPathToFileSystemPath.cend(); it != end; ++it) { + if (QFileInfo(it.value()) == QFileInfo(absPath)) + resourcePaths.append(it.key()); + } + return resourcePaths; +} + +QStringList ResourceFileMapper::qmlCompilerFiles(FileOutput fo) const +{ + QStringList files; + for (auto it = qrcPathToFileSystemPath.constBegin(), end = qrcPathToFileSystemPath.constEnd(); + it != end; ++it) { + const QString &qrcPath = it.key(); + const QString suffix = QFileInfo(qrcPath).suffix(); + if (suffix != QStringLiteral("qml") && suffix != QStringLiteral("js") && suffix != QStringLiteral("mjs")) + continue; + if (fo == FileOutput::AbsoluteFilePath) + files << it.value(); + else + files << qrcPath; + } + return files; +} + +void ResourceFileMapper::populateFromQrcFile(QFile &file) +{ + enum State { + InitialState, + InRCC, + InResource, + InFile + }; + State state = InitialState; + + QDir qrcDir = QFileInfo(file).absoluteDir(); + + QString prefix; + QString currentFileName; + QXmlStreamAttributes currentFileAttributes; + + QXmlStreamReader reader(&file); + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: + if (reader.name() == QStringLiteral("RCC")) { + if (state != InitialState) + return; + state = InRCC; + continue; + } else if (reader.name() == QStringLiteral("qresource")) { + if (state != InRCC) + return; + state = InResource; + QXmlStreamAttributes attributes = reader.attributes(); + if (attributes.hasAttribute(QStringLiteral("prefix"))) + prefix = attributes.value(QStringLiteral("prefix")).toString(); + if (!prefix.startsWith(QLatin1Char('/'))) + prefix.prepend(QLatin1Char('/')); + if (!prefix.endsWith(QLatin1Char('/'))) + prefix.append(QLatin1Char('/')); + continue; + } else if (reader.name() == QStringLiteral("file")) { + if (state != InResource) + return; + state = InFile; + currentFileAttributes = reader.attributes(); + continue; + } + return; + + case QXmlStreamReader::EndElement: + if (reader.name() == QStringLiteral("file")) { + if (state != InFile) + return; + state = InResource; + continue; + } else if (reader.name() == QStringLiteral("qresource")) { + if (state != InResource) + return; + state = InRCC; + continue; + } else if (reader.name() == QStringLiteral("RCC")) { + if (state != InRCC) + return; + state = InitialState; + continue; + } + return; + + case QXmlStreamReader::Characters: { + if (reader.isWhitespace()) + break; + if (state != InFile) + return; + currentFileName = reader.text().toString(); + if (currentFileName.isEmpty()) + continue; + + const QString fsPath = QDir::cleanPath(qrcDir.absoluteFilePath(currentFileName)); + + if (currentFileAttributes.hasAttribute(QStringLiteral("alias"))) + currentFileName = currentFileAttributes.value(QStringLiteral("alias")).toString(); + + currentFileName = QDir::cleanPath(currentFileName); + while (currentFileName.startsWith(QLatin1String("../"))) + currentFileName.remove(0, 3); + + const QString qrcPath = prefix + currentFileName; + if (QFile::exists(fsPath)) + qrcPathToFileSystemPath.insert(qrcPath, fsPath); + continue; + } + + default: break; + } + } +} diff --git a/src/qmlcompiler/resourcefilemapper_p.h b/src/qmlcompiler/resourcefilemapper_p.h new file mode 100644 index 0000000000..7fa35e4a6d --- /dev/null +++ b/src/qmlcompiler/resourcefilemapper_p.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef RESOURCEFILEMAPPER_H +#define RESOURCEFILEMAPPER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include <QStringList> +#include <QHash> +#include <QFile> + +struct ResourceFileMapper +{ + enum class FileOutput { + RelativeFilePath, + AbsoluteFilePath + }; + ResourceFileMapper(const QStringList &resourceFiles); + + bool isEmpty() const; + + QStringList resourcePaths(const QString &fileName); + QStringList qmlCompilerFiles(FileOutput fo = FileOutput::RelativeFilePath) const; + +private: + void populateFromQrcFile(QFile &file); + + QHash<QString, QString> qrcPathToFileSystemPath; +}; + +#endif // RESOURCEFILEMAPPER_H diff --git a/src/qmlcompiler/scopetree.cpp b/src/qmlcompiler/scopetree.cpp new file mode 100644 index 0000000000..5e24aeca73 --- /dev/null +++ b/src/qmlcompiler/scopetree.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "scopetree_p.h" + +#include <QtCore/qqueue.h> +#include <QtCore/qsharedpointer.h> + +#include <algorithm> + +ScopeTree::ScopeTree(ScopeType type, const ScopeTree::Ptr &parentScope) + : m_parentScope(parentScope), m_scopeType(type) {} + +ScopeTree::Ptr ScopeTree::create(ScopeType type, const ScopeTree::Ptr &parentScope) +{ + ScopeTree::Ptr childScope(new ScopeTree{type, parentScope}); + if (parentScope) { + Q_ASSERT(type != ScopeType::QMLScope + || !parentScope->m_parentScope + || parentScope->parentScope()->m_scopeType == ScopeType::QMLScope + || parentScope->parentScope()->m_internalName == QLatin1String("global")); + parentScope->m_childScopes.push_back(childScope); + } + return childScope; +} + +void ScopeTree::insertJSIdentifier(const QString &name, const JavaScriptIdentifier &identifier) +{ + Q_ASSERT(m_scopeType != ScopeType::QMLScope); + if (identifier.kind == JavaScriptIdentifier::LexicalScoped + || identifier.kind == JavaScriptIdentifier::Injected + || m_scopeType == ScopeType::JSFunctionScope) { + m_jsIdentifiers.insert(name, identifier); + } else { + auto targetScope = parentScope(); + while (targetScope->m_scopeType != ScopeType::JSFunctionScope) + targetScope = targetScope->parentScope(); + targetScope->m_jsIdentifiers.insert(name, identifier); + } +} + +void ScopeTree::insertPropertyIdentifier(const MetaProperty &property) +{ + addProperty(property); + MetaMethod method(property.propertyName() + QLatin1String("Changed"), QLatin1String("void")); + addMethod(method); +} + +bool ScopeTree::isIdInCurrentScope(const QString &id) const +{ + return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); +} + +bool ScopeTree::isIdInCurrentQMlScopes(const QString &id) const +{ + if (m_scopeType == ScopeType::QMLScope) + return m_properties.contains(id) || m_methods.contains(id) || m_enums.contains(id); + + const auto qmlScope = findCurrentQMLScope(parentScope()); + return qmlScope->m_properties.contains(id) + || qmlScope->m_methods.contains(id) + || qmlScope->m_enums.contains(id); +} + +bool ScopeTree::isIdInCurrentJSScopes(const QString &id) const +{ + if (m_scopeType != ScopeType::QMLScope && m_jsIdentifiers.contains(id)) + return true; + + for (auto jsScope = parentScope(); jsScope; jsScope = jsScope->parentScope()) { + if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_jsIdentifiers.contains(id)) + return true; + } + + return false; +} + +bool ScopeTree::isIdInjectedFromSignal(const QString &id) const +{ + const auto found = findJSIdentifier(id); + return found.has_value() && found->kind == JavaScriptIdentifier::Injected; +} + +std::optional<JavaScriptIdentifier> ScopeTree::findJSIdentifier(const QString &id) const +{ + for (const auto *scope = this; scope; scope = scope->parentScope().data()) { + if (scope->m_scopeType == ScopeType::JSFunctionScope + || scope->m_scopeType == ScopeType::JSLexicalScope) { + auto it = scope->m_jsIdentifiers.find(id); + if (it != scope->m_jsIdentifiers.end()) + return *it; + } + } + + return std::optional<JavaScriptIdentifier>{}; +} + +void ScopeTree::resolveTypes(const QHash<QString, ScopeTree::ConstPtr> &contextualTypes) +{ + auto findType = [&](const QString &name) { + auto type = contextualTypes.constFind(name); + if (type != contextualTypes.constEnd()) + return *type; + + return ScopeTree::ConstPtr(); + }; + + m_baseType = findType(m_baseTypeName); + m_attachedType = findType(m_attachedTypeName); + + for (auto it = m_properties.begin(), end = m_properties.end(); it != end; ++it) + it->setType(findType(it->typeName())); + + for (auto it = m_methods.begin(), end = m_methods.end(); it != end; ++it) { + it->setReturnType(findType(it->returnTypeName())); + const auto paramNames = it->parameterTypeNames(); + QList<ScopeTree::ConstPtr> paramTypes; + + for (const QString ¶mName: paramNames) + paramTypes.append(findType(paramName)); + + it->setParameterTypes(paramTypes); + } +} + +ScopeTree::ConstPtr ScopeTree::findCurrentQMLScope(const ScopeTree::ConstPtr &scope) +{ + auto qmlScope = scope; + while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) + qmlScope = qmlScope->parentScope(); + return qmlScope; +} + +void ScopeTree::addExport(const QString &name, const QString &package, const QTypeRevision &version) +{ + m_exports.append(Export(package, name, version, 0)); +} + +void ScopeTree::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision) +{ + m_exports[exportIndex].setMetaObjectRevision(metaObjectRevision); +} + +ScopeTree::Export::Export(QString package, QString type, const QTypeRevision &version, + int metaObjectRevision) : + m_package(std::move(package)), + m_type(std::move(type)), + m_version(version), + m_metaObjectRevision(metaObjectRevision) +{ +} + +bool ScopeTree::Export::isValid() const +{ + return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty(); +} diff --git a/src/qmlcompiler/scopetree_p.h b/src/qmlcompiler/scopetree_p.h new file mode 100644 index 0000000000..95fca89315 --- /dev/null +++ b/src/qmlcompiler/scopetree_p.h @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SCOPETREE_H +#define SCOPETREE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "metatypes_p.h" + +#include <QtQml/private/qqmljssourcelocation_p.h> + +#include <QtCore/qset.h> +#include <QtCore/qhash.h> +#include <QtCore/qstring.h> +#include <QtCore/qversionnumber.h> + +#include <optional> + +enum class ScopeType +{ + JSFunctionScope, + JSLexicalScope, + QMLScope +}; + +struct JavaScriptIdentifier +{ + enum Kind { + Parameter, + FunctionScoped, + LexicalScoped, + Injected + }; + + Kind kind = FunctionScoped; + QQmlJS::SourceLocation location; +}; + +class ScopeTree +{ + Q_DISABLE_COPY_MOVE(ScopeTree) +public: + using Ptr = QSharedPointer<ScopeTree>; + using WeakPtr = QWeakPointer<ScopeTree>; + using ConstPtr = QSharedPointer<const ScopeTree>; + using WeakConstPtr = QWeakPointer<const ScopeTree>; + + enum class AccessSemantics { + Reference, + Value, + None + }; + + enum Flag { + Creatable = 0x1, + Composite = 0x2, + Singleton = 0x4 + }; + Q_DECLARE_FLAGS(Flags, Flag) + Q_FLAGS(Flags); + + class Export { + public: + Export() = default; + Export(QString package, QString type, const QTypeRevision &version, + int metaObjectRevision); + + bool isValid() const; + + int metaObjectRevision() const { return m_metaObjectRevision; } + void setMetaObjectRevision(int metaObjectRevision) + { + m_metaObjectRevision = metaObjectRevision; + } + + QString package() const { return m_package; } + QString type() const { return m_type; } + + private: + QString m_package; + QString m_type; + QTypeRevision m_version; + int m_metaObjectRevision = 0; + }; + + static ScopeTree::Ptr create(ScopeType type = ScopeType::QMLScope, + const ScopeTree::Ptr &parentScope = ScopeTree::Ptr()); + static ScopeTree::ConstPtr findCurrentQMLScope(const ScopeTree::ConstPtr &scope); + + ScopeTree::Ptr parentScope() const { return m_parentScope.toStrongRef(); } + + void insertJSIdentifier(const QString &name, const JavaScriptIdentifier &identifier); + + // inserts property as qml identifier as well as the corresponding + void insertPropertyIdentifier(const MetaProperty &prop); + + bool isIdInCurrentScope(const QString &id) const; + + ScopeType scopeType() const { return m_scopeType; } + + void addMethods(const QMultiHash<QString, MetaMethod> &methods) { m_methods.unite(methods); } + void addMethod(const MetaMethod &method) { m_methods.insert(method.methodName(), method); } + QMultiHash<QString, MetaMethod> methods() const { return m_methods; } + + void addEnum(const MetaEnum &fakeEnum) { m_enums.insert(fakeEnum.name(), fakeEnum); } + QHash<QString, MetaEnum> enums() const { return m_enums; } + + QString fileName() const { return m_fileName; } + void setFileName(const QString &file) { m_fileName = file; } + + // The name the type uses to refer to itself. Either C++ class name or base name of + // QML file. isComposite tells us if this is a C++ or a QML name. + QString internalName() const { return m_internalName; } + void setInternalName(const QString &internalName) { m_internalName = internalName; } + + void addExport(const QString &name, const QString &package, const QTypeRevision &version); + void setExportMetaObjectRevision(int exportIndex, int metaObjectRevision); + QList<Export> exports() const { return m_exports; } + + // If isComposite(), this is the QML/JS name of the prototype. Otherwise it's the + // relevant base class (in the hierarchy starting from QObject) of a C++ type. + void setBaseTypeName(const QString &baseTypeName) { m_baseTypeName = baseTypeName; } + QString baseTypeName() const { return m_baseTypeName; } + ScopeTree::ConstPtr baseType() const { return m_baseType; } + + void addProperty(const MetaProperty &prop) { m_properties.insert(prop.propertyName(), prop); } + QHash<QString, MetaProperty> properties() const { return m_properties; } + + QString defaultPropertyName() const { return m_defaultPropertyName; } + void setDefaultPropertyName(const QString &name) { m_defaultPropertyName = name; } + + QString attachedTypeName() const { return m_attachedTypeName; } + void setAttachedTypeName(const QString &name) { m_attachedTypeName = name; } + ScopeTree::ConstPtr attachedType() const { return m_attachedType; } + + bool isSingleton() const { return m_flags & Singleton; } + bool isCreatable() const { return m_flags & Creatable; } + bool isComposite() const { return m_flags & Composite; } + void setIsSingleton(bool v) { m_flags = v ? (m_flags | Singleton) : (m_flags & ~Singleton); } + void setIsCreatable(bool v) { m_flags = v ? (m_flags | Creatable) : (m_flags & ~Creatable); } + void setIsComposite(bool v) { m_flags = v ? (m_flags | Composite) : (m_flags & ~Composite); } + + void setAccessSemantics(AccessSemantics semantics) { m_semantics = semantics; } + AccessSemantics accessSemantics() const { return m_semantics; } + + bool isIdInCurrentQMlScopes(const QString &id) const; + bool isIdInCurrentJSScopes(const QString &id) const; + bool isIdInjectedFromSignal(const QString &id) const; + + std::optional<JavaScriptIdentifier> findJSIdentifier(const QString &id) const; + + QVector<ScopeTree::Ptr> childScopes() const + { + return m_childScopes; + } + + void resolveTypes(const QHash<QString, ConstPtr> &contextualTypes); + +private: + ScopeTree(ScopeType type, const ScopeTree::Ptr &parentScope = ScopeTree::Ptr()); + + QHash<QString, JavaScriptIdentifier> m_jsIdentifiers; + + QMultiHash<QString, MetaMethod> m_methods; + QHash<QString, MetaProperty> m_properties; + QHash<QString, MetaEnum> m_enums; + + QVector<ScopeTree::Ptr> m_childScopes; + ScopeTree::WeakPtr m_parentScope; + + QString m_fileName; + QString m_internalName; + QString m_baseTypeName; + ScopeTree::WeakConstPtr m_baseType; + + ScopeType m_scopeType = ScopeType::QMLScope; + QList<Export> m_exports; + + QString m_defaultPropertyName; + QString m_attachedTypeName; + ScopeTree::WeakConstPtr m_attachedType; + + Flags m_flags; + AccessSemantics m_semantics = AccessSemantics::Reference; +}; + +#endif // SCOPETREE_H diff --git a/src/qmlcompiler/typedescriptionreader.cpp b/src/qmlcompiler/typedescriptionreader.cpp new file mode 100644 index 0000000000..30808e6e99 --- /dev/null +++ b/src/qmlcompiler/typedescriptionreader.cpp @@ -0,0 +1,689 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "typedescriptionreader_p.h" + +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsengine_p.h> + +#include <QtCore/qdir.h> + +using namespace QQmlJS; +using namespace QQmlJS::AST; + +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 TypeDescriptionReader::operator()( + QHash<QString, ScopeTree::Ptr> *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 TypeDescriptionReader::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 TypeDescriptionReader::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 TypeDescriptionReader::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 TypeDescriptionReader::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 TypeDescriptionReader::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 TypeDescriptionReader::readComponent(UiObjectDefinition *ast) +{ + ScopeTree::Ptr scope = ScopeTree::create(); + + 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->setFileName(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->setDefaultPropertyName(readStringBinding(script)); + } else if (name == QLatin1String("exports")) { + readExports(script, scope); + } else if (name == QLatin1String("exportMetaObjectRevisions")) { + readMetaObjectRevisions(script, scope); + } else if (name == QLatin1String("attachedType")) { + scope->setAttachedTypeName(readStringBinding(script)); + } else if (name == QLatin1String("isSingleton")) { + scope->setIsSingleton(readBoolBinding(script)); + } else if (name == QLatin1String("isCreatable")) { + scope->setIsCreatable(readBoolBinding(script)); + } else if (name == QLatin1String("isComposite")) { + scope->setIsComposite(readBoolBinding(script)); + } else if (name == QLatin1String("accessSemantics")) { + const QString semantics = readStringBinding(script); + if (semantics == QLatin1String("reference")) { + scope->setAccessSemantics(ScopeTree::AccessSemantics::Reference); + } else if (semantics == QLatin1String("value")) { + scope->setAccessSemantics(ScopeTree::AccessSemantics::Value); + } else if (semantics == QLatin1String("none")) { + scope->setAccessSemantics(ScopeTree::AccessSemantics::None); + } else { + addWarning(script->firstSourceLocation(), + tr("Unknown access semantics \"%1\".").arg(semantics)); + } + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name, prototype, defaultProperty, attachedType, " + "exports, isSingleton, isCreatable, isComposite and " + "exportMetaObjectRevisions 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; + } + + m_objects->insert(scope->internalName(), scope); +} + +void TypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod, + const ScopeTree::Ptr &scope) +{ + MetaMethod metaMethod; + // ### confusion between Method and Slot. Method should be removed. + if (isMethod) + metaMethod.setMethodType(MetaMethod::Slot); + else + metaMethod.setMethodType(MetaMethod::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 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 { + addWarning(script->firstSourceLocation(), + tr("Expected only name and type 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; + } + + scope->addMethod(metaMethod); +} + +void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, const ScopeTree::Ptr &scope) +{ + QString name; + QString type; + bool isPointer = false; + bool isReadonly = false; + bool isList = false; + int revision = 0; + + 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")) { + name = readStringBinding(script); + } else if (id == QLatin1String("type")) { + type = readStringBinding(script); + } else if (id == QLatin1String("isPointer")) { + isPointer = readBoolBinding(script); + } else if (id == QLatin1String("isReadonly")) { + isReadonly = readBoolBinding(script); + } else if (id == QLatin1String("isList")) { + isList = readBoolBinding(script); + } else if (id == QLatin1String("revision")) { + revision = readIntBinding(script); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only type, name, revision, isPointer, isReadonly and" + " isList script bindings.")); + } + } + + if (name.isEmpty() || type.isEmpty()) { + addError(ast->firstSourceLocation(), + tr("Property object is missing a name or type script binding.")); + return; + } + + scope->addProperty(MetaProperty(name, type, isList, !isReadonly, isPointer, false, revision)); +} + +void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, const ScopeTree::Ptr &scope) +{ + MetaEnum 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 { + addWarning(script->firstSourceLocation(), + tr("Expected only name and values script bindings.")); + } + } + + scope->addEnum(metaEnum); +} + +void TypeDescriptionReader::readParameter(UiObjectDefinition *ast, MetaMethod *metaMethod) +{ + QString name; + QString type; + + 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")) { + // ### unhandled + } else if (id == QLatin1String("isReadonly")) { + // ### unhandled + } else if (id == QLatin1String("isList")) { + // ### unhandled + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name and type script bindings.")); + } + } + + metaMethod->addParameter(name, type); +} + +QString TypeDescriptionReader::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 TypeDescriptionReader::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 TypeDescriptionReader::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 TypeDescriptionReader::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 TypeDescriptionReader::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; +} + +void TypeDescriptionReader::readExports(UiScriptBinding *ast, const ScopeTree::Ptr &scope) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected array of strings after colon.")); + return; + } + + auto *expStmt = cast<ExpressionStatement *>(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), + tr("Expected array of strings after colon.")); + return; + } + + auto *arrayLit = cast<ArrayPattern *>(expStmt->expression); + if (!arrayLit) { + addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon.")); + return; + } + + 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; + } + 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? + scope->addExport(name, package, version); + } +} + +void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast, + const ScopeTree::Ptr &scope) +{ + 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 = scope->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; + } + + scope->setExportMetaObjectRevision(exportIndex, metaObjectRevision); + } +} + +void TypeDescriptionReader::readEnumValues(UiScriptBinding *ast, MetaEnum *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)) { + 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()); + 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.")); + } +} diff --git a/src/qmlcompiler/typedescriptionreader_p.h b/src/qmlcompiler/typedescriptionreader_p.h new file mode 100644 index 0000000000..ca79a68a4f --- /dev/null +++ b/src/qmlcompiler/typedescriptionreader_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TYPEDESCRIPTIONREADER_H +#define TYPEDESCRIPTIONREADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "scopetree_p.h" + +#include <QtQml/private/qqmljsastfwd_p.h> + +// for Q_DECLARE_TR_FUNCTIONS +#include <QtCore/qcoreapplication.h> + +class TypeDescriptionReader +{ + Q_DECLARE_TR_FUNCTIONS(TypeDescriptionReader) +public: + TypeDescriptionReader() = default; + explicit TypeDescriptionReader(QString fileName, QString data) + : m_fileName(std::move(fileName)), m_source(std::move(data)) {} + + bool operator()( + QHash<QString, ScopeTree::Ptr> *objects, + QStringList *dependencies); + + QString errorMessage() const { return m_errorMessage; } + QString warningMessage() const { return m_warningMessage; } + +private: + void readDocument(QQmlJS::AST::UiProgram *ast); + void readModule(QQmlJS::AST::UiObjectDefinition *ast); + void readDependencies(QQmlJS::AST::UiScriptBinding *ast); + void readComponent(QQmlJS::AST::UiObjectDefinition *ast); + void readSignalOrMethod(QQmlJS::AST::UiObjectDefinition *ast, bool isMethod, + const ScopeTree::Ptr &scope); + void readProperty(QQmlJS::AST::UiObjectDefinition *ast, const ScopeTree::Ptr &scope); + void readEnum(QQmlJS::AST::UiObjectDefinition *ast, const ScopeTree::Ptr &scope); + void readParameter(QQmlJS::AST::UiObjectDefinition *ast, MetaMethod *metaMethod); + + QString readStringBinding(QQmlJS::AST::UiScriptBinding *ast); + bool readBoolBinding(QQmlJS::AST::UiScriptBinding *ast); + double readNumericBinding(QQmlJS::AST::UiScriptBinding *ast); + QTypeRevision readNumericVersionBinding(QQmlJS::AST::UiScriptBinding *ast); + int readIntBinding(QQmlJS::AST::UiScriptBinding *ast); + void readExports(QQmlJS::AST::UiScriptBinding *ast, const ScopeTree::Ptr &scope); + void readMetaObjectRevisions(QQmlJS::AST::UiScriptBinding *ast, const ScopeTree::Ptr &scope); + void readEnumValues(QQmlJS::AST::UiScriptBinding *ast, MetaEnum *metaEnum); + + void addError(const QQmlJS::SourceLocation &loc, const QString &message); + void addWarning(const QQmlJS::SourceLocation &loc, const QString &message); + + QString m_fileName; + QString m_source; + QString m_errorMessage; + QString m_warningMessage; + QHash<QString, ScopeTree::Ptr> *m_objects = nullptr; + QStringList *m_dependencies = nullptr; +}; + +#endif // TYPEDESCRIPTIONREADER_H diff --git a/src/qmltyperegistrar/.prev_CMakeLists.txt b/src/qmltyperegistrar/.prev_CMakeLists.txt index 365aa30e40..7c2bfe5de7 100644 --- a/src/qmltyperegistrar/.prev_CMakeLists.txt +++ b/src/qmltyperegistrar/.prev_CMakeLists.txt @@ -8,7 +8,7 @@ qt_get_tool_target_name(target_name qmltyperegistrar) qt_add_tool(${target_name} TARGET_DESCRIPTION "QML Types Registrar" SOURCES - ../../tools/shared/qmlstreamwriter.cpp ../../tools/shared/qmlstreamwriter.h + ../qmlcompiler/qmlstreamwriter.cpp ../qmlcompiler/qmlstreamwriter_p.h qmltyperegistrar.cpp qmltypesclassdescription.cpp qmltypesclassdescription.h qmltypescreator.cpp qmltypescreator.h @@ -16,7 +16,7 @@ qt_add_tool(${target_name} QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII INCLUDE_DIRECTORIES - ../../tools/shared + ../qmlcompiler PUBLIC_LIBRARIES Qt::CorePrivate ) diff --git a/src/qmltyperegistrar/CMakeLists.txt b/src/qmltyperegistrar/CMakeLists.txt index 27c7e0cadc..5f6568ac37 100644 --- a/src/qmltyperegistrar/CMakeLists.txt +++ b/src/qmltyperegistrar/CMakeLists.txt @@ -9,7 +9,7 @@ qt_add_tool(${target_name} TARGET_DESCRIPTION "QML Types Registrar" TOOLS_TARGET Qml # special case SOURCES - ../../tools/shared/qmlstreamwriter.cpp ../../tools/shared/qmlstreamwriter.h + ../qmlcompiler/qmlstreamwriter.cpp ../qmlcompiler/qmlstreamwriter_p.h qmltyperegistrar.cpp qmltypesclassdescription.cpp qmltypesclassdescription.h qmltypescreator.cpp qmltypescreator.h @@ -17,7 +17,7 @@ qt_add_tool(${target_name} QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII INCLUDE_DIRECTORIES - ../../tools/shared + ../qmlcompiler PUBLIC_LIBRARIES Qt::CorePrivate ) diff --git a/src/qmltyperegistrar/qmltyperegistrar.pro b/src/qmltyperegistrar/qmltyperegistrar.pro index 7ed3986dd7..eafaab6559 100644 --- a/src/qmltyperegistrar/qmltyperegistrar.pro +++ b/src/qmltyperegistrar/qmltyperegistrar.pro @@ -5,16 +5,19 @@ DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII QMAKE_TARGET_DESCRIPTION = QML Types Registrar -include(../../tools/shared/shared.pri) +# We cannot link against libQmlCompiler as qmltyperegistrar +# has to be built before libQmlCompiler. + +INCLUDEPATH += $$PWD/../qmlcompiler SOURCES += \ - $$QMLSTREAMWRITER_SOURCES \ + ../qmlcompiler/qmlstreamwriter.cpp \ qmltyperegistrar.cpp \ qmltypesclassdescription.cpp \ qmltypescreator.cpp HEADERS += \ - $$QMLSTREAMWRITER_HEADERS \ + ../qmlcompiler/qmlstreamwriter_p.h \ qmltypesclassdescription.h \ qmltypescreator.h diff --git a/src/qmltyperegistrar/qmltypescreator.cpp b/src/qmltyperegistrar/qmltypescreator.cpp index 0c2a70c6d1..7e5fca0c53 100644 --- a/src/qmltyperegistrar/qmltypescreator.cpp +++ b/src/qmltyperegistrar/qmltypescreator.cpp @@ -27,7 +27,6 @@ ****************************************************************************/ #include "qmltypescreator.h" -#include "qmlstreamwriter.h" #include "qmltypesclassdescription.h" #include <QtCore/qset.h> diff --git a/src/qmltyperegistrar/qmltypescreator.h b/src/qmltyperegistrar/qmltypescreator.h index 53976e775e..9fc62f9a9e 100644 --- a/src/qmltyperegistrar/qmltypescreator.h +++ b/src/qmltyperegistrar/qmltypescreator.h @@ -29,8 +29,8 @@ #ifndef QMLTYPESCREATOR_H #define QMLTYPESCREATOR_H -#include "qmlstreamwriter.h" #include "qmltypesclassdescription.h" +#include "qmlstreamwriter_p.h" #include <QtCore/qstring.h> #include <QtCore/qset.h> diff --git a/src/src.pro b/src/src.pro index d48b6390bf..d4e66be413 100644 --- a/src/src.pro +++ b/src/src.pro @@ -38,9 +38,15 @@ SUBDIRS += \ plugins \ imports -qtConfig(qml-devtools): SUBDIRS += qmldevtools +qtConfig(qml-devtools) { + SUBDIRS += \ + qmldevtools \ + qmlcompiler + + qmldevtools.depends = qml + qmlcompiler.depends = qmldevtools +} -qmldevtools.depends = qml qtConfig(qml-network) { QT_FOR_CONFIG += network |