diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2020-09-29 15:49:16 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2020-10-02 15:32:15 +0200 |
commit | 48426aa705cb7ed88489200864c2fc0143058671 (patch) | |
tree | 964b8588157dc122f7cf744ca8501ca4276dbeba /tools/shared | |
parent | febb6ba891616b71476b736d4c583054685c7766 (diff) |
Move the import handling code out of qmllint
We want to re-use this logic in other places.
Change-Id: I63cbee86a83265ddd241a4fae9ce8c48f38b5f18
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tools/shared')
-rw-r--r-- | tools/shared/qmljsimporter.cpp | 315 | ||||
-rw-r--r-- | tools/shared/qmljsimporter.h | 105 | ||||
-rw-r--r-- | tools/shared/shared.pri | 2 |
3 files changed, 422 insertions, 0 deletions
diff --git a/tools/shared/qmljsimporter.cpp b/tools/shared/qmljsimporter.cpp new file mode 100644 index 0000000000..28b7e7950f --- /dev/null +++ b/tools/shared/qmljsimporter.cpp @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** 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.h" +#include "typedescriptionreader.h" +#include "qmljstypereader.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('.')); + return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name); +} + +static QQmlDirParser createQmldirParserForFile(const QString &filename) +{ + QFile f(filename); + f.open(QFile::ReadOnly); + QQmlDirParser parser; + parser.parse(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, 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(), + ComponentVersion(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::ImportedTypes *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::ImportedTypes *types, + const QString &prefix) +{ + for (auto it = import.scripts.begin(); it != import.scripts.end(); ++it) { + types->importedQmlNames.insert(prefixedName(prefix, it.key()), it.value()); + types->exportedQmlNames.insert(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->importedQmlNames.insert(prefixedName(prefix, valExport.type()), val); + types->exportedQmlNames.insert(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 and the given \a qmltypesFiles. If \a qmltypesFiles + * is empty, imports any .qmltypes files without accompanying qmldir found in + * any of the import base paths. + */ +QmlJSImporter::ImportedTypes QmlJSImporter::importBaseQmlTypes(const QStringList &qmltypesFiles) +{ + ImportedTypes 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); + } + + if (qmltypesFiles.isEmpty()) { + for (auto const &qmltypesPath : m_importPaths) { + if (QFile::exists(qmltypesPath + SlashQmldir)) + continue; + Import result; + QDirIterator it { + qmltypesPath, QStringList { QLatin1String("*.qmltypes") }, QDir::Files }; + + while (it.hasNext()) { + const QString name = it.next(); + if (!name.endsWith(QLatin1String("/builtins.qmltypes"))) + readQmltypes(name, &result.objects); + } + + importDependencies(result, &types); + processImport(result, &types); + } + } else { + Import result; + + for (const auto &qmltypeFile : qmltypesFiles) + readQmltypes(qmltypeFile, &result.objects); + + importDependencies(result, &types); + processImport(result, &types); + } + + return types; +} + +QmlJSImporter::ImportedTypes QmlJSImporter::importModule( + const QString &module, const QString &prefix, QTypeRevision version) +{ + ImportedTypes result; + importHelper(module, &result, prefix, version); + return result; +} + +void QmlJSImporter::importHelper(const QString &module, ImportedTypes *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); + + ImportedTypes 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.importedQmlNames.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.importedQmlNames); + return result; +} + +QmlJSImporter::ImportedTypes QmlJSImporter::importFileOrDirectory( + const QString &fileOrDirectory, const QString &prefix) +{ + ImportedTypes result; + + QString name = fileOrDirectory; + + if (QFileInfo(name).isRelative()) + name = QDir(m_currentDir).filePath(name); + + QFileInfo fileInfo(name); + if (fileInfo.isFile()) { + ScopeTree::Ptr scope(localFile2ScopeTree(fileInfo.canonicalFilePath())); + result.importedQmlNames.insert(prefix.isEmpty() ? scope->internalName() : prefix, scope); + result.exportedQmlNames.insert(scope->internalName(), scope); + return result; + } + + QDirIterator it { + fileInfo.canonicalFilePath(), + QStringList() << QLatin1String("*.qml"), + QDir::NoFilter + }; + while (it.hasNext()) { + ScopeTree::Ptr scope(localFile2ScopeTree(it.next())); + if (!scope->internalName().isEmpty()) { + result.importedQmlNames.insert(prefixedName(prefix, scope->internalName()), scope); + result.exportedQmlNames.insert(scope->internalName(), scope); + } + } + + return result; +} diff --git a/tools/shared/qmljsimporter.h b/tools/shared/qmljsimporter.h new file mode 100644 index 0000000000..44536acc51 --- /dev/null +++ b/tools/shared/qmljsimporter.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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.h" +#include <QtQml/private/qqmldirparser_p.h> + +class QmlJSImporter +{ +public: + struct ImportedTypes + { + // C++ names used in qmltypes files for non-composite types + QHash<QString, ScopeTree::Ptr> cppNames; + + // Names a component intends to export, without prefix + QHash<QString, ScopeTree::Ptr> exportedQmlNames; + + // Names the importing component sees, possibly adding a prefix + QHash<QString, ScopeTree::Ptr> importedQmlNames; + }; + + QmlJSImporter(const QString ¤tDir, const QStringList &importPaths) : + m_currentDir(currentDir), m_importPaths(importPaths) {} + + ImportedTypes importBaseQmlTypes(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 Import { + QHash<QString, ScopeTree::Ptr> objects; + QHash<QString, ScopeTree::Ptr> scripts; + QList<QQmlDirParser::Import> imports; + QList<QQmlDirParser::Import> dependencies; + }; + + void importHelper(const QString &module, ImportedTypes *types, + const QString &prefix = QString(), + QTypeRevision version = QTypeRevision()); + void processImport(const Import &import, ImportedTypes *types, + const QString &prefix = QString()); + void importDependencies(const QmlJSImporter::Import &import, + ImportedTypes *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); + + QString m_currentDir; + 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/tools/shared/shared.pri b/tools/shared/shared.pri index 4ee3704bfb..b7eb2ec6c1 100644 --- a/tools/shared/shared.pri +++ b/tools/shared/shared.pri @@ -12,6 +12,7 @@ RESOURCEFILEMAPPER_HEADERS = \ METATYPEREADER_SOURCES = \ $$PWD/componentversion.cpp \ $$PWD/importedmembersvisitor.cpp \ + $$PWD/qmljsimporter.cpp \ $$PWD/qmljstypereader.cpp \ $$PWD/scopetree.cpp \ $$PWD/typedescriptionreader.cpp @@ -19,6 +20,7 @@ METATYPEREADER_SOURCES = \ METATYPEREADER_HEADERS = \ $$PWD/componentversion.h \ $$PWD/importedmembersvisitor.h \ + $$PWD/qmljsimporter.h \ $$PWD/qmljstypereader.h \ $$PWD/metatypes.h \ $$PWD/scopetree.h \ |