aboutsummaryrefslogtreecommitdiffstats
path: root/tools/shared
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2020-09-29 15:49:16 +0200
committerUlf Hermann <ulf.hermann@qt.io>2020-10-02 15:32:15 +0200
commit48426aa705cb7ed88489200864c2fc0143058671 (patch)
tree964b8588157dc122f7cf744ca8501ca4276dbeba /tools/shared
parentfebb6ba891616b71476b736d4c583054685c7766 (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.cpp315
-rw-r--r--tools/shared/qmljsimporter.h105
-rw-r--r--tools/shared/shared.pri2
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 &currentDir, 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 \