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 | |
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')
-rw-r--r-- | tools/qmllint/.prev_CMakeLists.txt | 1 | ||||
-rw-r--r-- | tools/qmllint/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tools/qmllint/checkidentifiers.cpp | 2 | ||||
-rw-r--r-- | tools/qmllint/checkidentifiers.h | 4 | ||||
-rw-r--r-- | tools/qmllint/findwarnings.cpp | 277 | ||||
-rw-r--r-- | tools/qmllint/findwarnings.h | 66 | ||||
-rw-r--r-- | tools/shared/qmljsimporter.cpp | 315 | ||||
-rw-r--r-- | tools/shared/qmljsimporter.h | 105 | ||||
-rw-r--r-- | tools/shared/shared.pri | 2 |
9 files changed, 431 insertions, 342 deletions
diff --git a/tools/qmllint/.prev_CMakeLists.txt b/tools/qmllint/.prev_CMakeLists.txt index 8e4affe73c..cf5dc7e28e 100644 --- a/tools/qmllint/.prev_CMakeLists.txt +++ b/tools/qmllint/.prev_CMakeLists.txt @@ -11,6 +11,7 @@ qt_add_tool(${target_name} ../shared/componentversion.cpp ../shared/componentversion.h ../shared/importedmembersvisitor.cpp ../shared/importedmembersvisitor.h ../shared/metatypes.h + ../shared/qmljsimporter.cpp ../shared/qmljsimporter.h ../shared/qmljstypereader.cpp ../shared/qmljstypereader.h ../shared/scopetree.cpp ../shared/scopetree.h ../shared/typedescriptionreader.cpp ../shared/typedescriptionreader.h diff --git a/tools/qmllint/CMakeLists.txt b/tools/qmllint/CMakeLists.txt index 663bcb9cbf..ba4cd62e65 100644 --- a/tools/qmllint/CMakeLists.txt +++ b/tools/qmllint/CMakeLists.txt @@ -12,6 +12,7 @@ qt_add_tool(${target_name} ../shared/componentversion.cpp ../shared/componentversion.h ../shared/importedmembersvisitor.cpp ../shared/importedmembersvisitor.h ../shared/metatypes.h + ../shared/qmljsimporter.cpp ../shared/qmljsimporter.h ../shared/qmljstypereader.cpp ../shared/qmljstypereader.h ../shared/scopetree.cpp ../shared/scopetree.h ../shared/typedescriptionreader.cpp ../shared/typedescriptionreader.h diff --git a/tools/qmllint/checkidentifiers.cpp b/tools/qmllint/checkidentifiers.cpp index a75c9c9b83..9a6495310d 100644 --- a/tools/qmllint/checkidentifiers.cpp +++ b/tools/qmllint/checkidentifiers.cpp @@ -84,7 +84,7 @@ void CheckIdentifiers::printContext(const QQmlJS::SourceLocation &location) cons } static bool walkViaParentAndAttachedScopes(ScopeTree::ConstPtr rootType, - const FindWarningVisitor::ImportedTypes &allTypes, + const QmlJSImporter::ImportedTypes &allTypes, std::function<bool(ScopeTree::ConstPtr)> visit) { if (rootType == nullptr) diff --git a/tools/qmllint/checkidentifiers.h b/tools/qmllint/checkidentifiers.h index 653b398301..bdd83f1e57 100644 --- a/tools/qmllint/checkidentifiers.h +++ b/tools/qmllint/checkidentifiers.h @@ -38,7 +38,7 @@ class CheckIdentifiers { public: CheckIdentifiers(ColorOutput *colorOut, const QString &code, - const FindWarningVisitor::ImportedTypes &types, const QString &fileName) : + const QmlJSImporter::ImportedTypes &types, const QString &fileName) : m_colorOut(colorOut), m_code(code), m_types(types), m_fileName(fileName) {} @@ -53,7 +53,7 @@ private: ColorOutput *m_colorOut = nullptr; QString m_code; - FindWarningVisitor::ImportedTypes m_types; + QmlJSImporter::ImportedTypes m_types; QString m_fileName; }; diff --git a/tools/qmllint/findwarnings.cpp b/tools/qmllint/findwarnings.cpp index 09ef63d4bd..b28a4ee695 100644 --- a/tools/qmllint/findwarnings.cpp +++ b/tools/qmllint/findwarnings.cpp @@ -43,21 +43,6 @@ #include <QtCore/qdiriterator.h> #include <QtCore/qscopedvaluerollback.h> -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 FindWarningVisitor::enterEnvironment(ScopeType type, const QString &name) { m_currentScope = ScopeTree::create(type, m_currentScope); @@ -70,266 +55,6 @@ void FindWarningVisitor::leaveEnvironment() m_currentScope = m_currentScope->parentScope(); } -static const QLatin1String SlashQmldir = QLatin1String("/qmldir"); -static const QLatin1String SlashPluginsDotQmltypes = QLatin1String("/plugins.qmltypes"); - -void FindWarningVisitor::Importer::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()); -} - -FindWarningVisitor::Importer::Import FindWarningVisitor::Importer::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 FindWarningVisitor::Importer::importDependencies( - const FindWarningVisitor::Importer::Import &import, - FindWarningVisitor::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 FindWarningVisitor::Importer::processImport( - const FindWarningVisitor::Importer::Import &import, - FindWarningVisitor::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); - } -} - -FindWarningVisitor::ImportedTypes FindWarningVisitor::Importer::importBareQmlTypes( - 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; -} - -FindWarningVisitor::ImportedTypes FindWarningVisitor::Importer::importModule( - const QString &module, const QString &prefix, QTypeRevision version) -{ - ImportedTypes result; - importHelper(module, &result, prefix, version); - return result; -} - -void FindWarningVisitor::Importer::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 FindWarningVisitor::Importer::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; -} - -FindWarningVisitor::ImportedTypes FindWarningVisitor::Importer::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; -} - void FindWarningVisitor::importExportedNames(ScopeTree::ConstPtr scope) { QList<ScopeTree::ConstPtr> scopes; @@ -388,7 +113,7 @@ void FindWarningVisitor::throwRecursionDepthError() bool FindWarningVisitor::visit(QQmlJS::AST::UiProgram *) { enterEnvironment(ScopeType::QMLScope, "program"); - m_rootScopeImports = m_importer.importBareQmlTypes(m_qmltypesFiles); + m_rootScopeImports = m_importer.importBaseQmlTypes(m_qmltypesFiles); // add "self" (as we only ever check the first part of a qualified identifier, we get away with // using an empty ScopeTree diff --git a/tools/qmllint/findwarnings.h b/tools/qmllint/findwarnings.h index 2a0929b379..a8673d54e8 100644 --- a/tools/qmllint/findwarnings.h +++ b/tools/qmllint/findwarnings.h @@ -42,6 +42,7 @@ #include "typedescriptionreader.h" #include "scopetree.h" #include "qcoloroutput.h" +#include "qmljsimporter.h" #include <QtQml/private/qqmldirparser_p.h> #include <QtQml/private/qqmljsastvisitor_p.h> @@ -53,18 +54,6 @@ class FindWarningVisitor : public QQmlJS::AST::Visitor { Q_DISABLE_COPY_MOVE(FindWarningVisitor) 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; - }; - explicit FindWarningVisitor( QStringList qmltypeDirs, QStringList qmltypesFiles, QString code, QString fileName, bool silent, bool warnUnqualified, bool warnWithStatement, bool warnInheritanceCycle); @@ -72,56 +61,7 @@ public: bool check(); private: - class Importer - { - public: - Importer(const QString ¤tDir, const QStringList &importPaths) : - m_currentDir(currentDir), m_importPaths(importPaths) {} - - ImportedTypes importBareQmlTypes(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 FindWarningVisitor::Importer::Import &import, - FindWarningVisitor::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; - - }; - - ImportedTypes m_rootScopeImports; + QmlJSImporter::ImportedTypes m_rootScopeImports; ScopeTree::Ptr m_rootScope; ScopeTree::Ptr m_currentScope; @@ -148,7 +88,7 @@ private: QVarLengthArray<OutstandingConnection, 3> m_outstandingConnections; // Connections whose target we have not encountered - Importer m_importer; + QmlJSImporter m_importer; void enterEnvironment(ScopeType type, const QString &name); void leaveEnvironment(); 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 \ |