diff options
-rw-r--r-- | tests/auto/qml/qmllint/data/AttachedType.qml | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/importing_js.qml | 1 | ||||
-rw-r--r-- | tools/qmllint/findwarnings.cpp | 219 | ||||
-rw-r--r-- | tools/qmllint/findwarnings.h | 16 | ||||
-rw-r--r-- | tools/shared/scopetree.cpp | 27 | ||||
-rw-r--r-- | tools/shared/scopetree.h | 4 |
6 files changed, 169 insertions, 99 deletions
diff --git a/tests/auto/qml/qmllint/data/AttachedType.qml b/tests/auto/qml/qmllint/data/AttachedType.qml index 02fa514e83..f852a39a22 100644 --- a/tests/auto/qml/qmllint/data/AttachedType.qml +++ b/tests/auto/qml/qmllint/data/AttachedType.qml @@ -1,4 +1,5 @@ import Things 1.0 +import QtQuick Item { property real test: Screen.pixelDensity * 0.5 diff --git a/tests/auto/qml/qmllint/data/importing_js.qml b/tests/auto/qml/qmllint/data/importing_js.qml index fa6cf797db..b2d7a34fe6 100644 --- a/tests/auto/qml/qmllint/data/importing_js.qml +++ b/tests/auto/qml/qmllint/data/importing_js.qml @@ -1,4 +1,5 @@ import "QTBUG-45916.js" as JSTest +import QtQuick Item { id: root diff --git a/tools/qmllint/findwarnings.cpp b/tools/qmllint/findwarnings.cpp index 5516f86654..e06305b47b 100644 --- a/tools/qmllint/findwarnings.cpp +++ b/tools/qmllint/findwarnings.cpp @@ -136,44 +136,55 @@ FindWarningVisitor::Importer::Import FindWarningVisitor::Importer::readQmldir(co return result; } -void FindWarningVisitor::Importer::processImport( - const QString &prefix, const FindWarningVisitor::Importer::Import &import, - QTypeRevision version) +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, invalidPrefix, dependency.version); + importHelper(dependency.module, types, invalidPrefix, dependency.version); for (auto const &import : qAsConst(import.imports)) { - importHelper(import.module, prefix, + 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) { - m_exportedName2Scope.importedQmlNames.insert(prefixedName(prefix, it.key()), it.value()); - m_exportedName2Scope.exportedQmlNames.insert(it.key(), it.value()); + 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(); - m_exportedName2Scope.cppNames.insert(val->internalName(), val); + types->cppNames.insert(val->internalName(), val); const auto exports = val->exports(); for (const auto &valExport : exports) { - m_exportedName2Scope.importedQmlNames.insert(prefixedName(prefix, valExport.type()), val); - m_exportedName2Scope.exportedQmlNames.insert(valExport.type(), val); + 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 result; - qSwap(result, m_exportedName2Scope); + ImportedTypes types; for (auto const &dir : m_importPaths) { Import result; @@ -181,7 +192,8 @@ FindWarningVisitor::ImportedTypes FindWarningVisitor::Importer::importBareQmlTyp QDirIterator::Subdirectories }; while (it.hasNext()) readQmltypes(it.next(), &result.objects); - processImport("", result, QTypeRevision()); + importDependencies(result, &types); + processImport(result, &types); } if (qmltypesFiles.isEmpty()) { @@ -198,7 +210,8 @@ FindWarningVisitor::ImportedTypes FindWarningVisitor::Importer::importBareQmlTyp readQmltypes(name, &result.objects); } - processImport("", result, QTypeRevision()); + importDependencies(result, &types); + processImport(result, &types); } } else { Import result; @@ -206,53 +219,80 @@ FindWarningVisitor::ImportedTypes FindWarningVisitor::Importer::importBareQmlTyp for (const auto &qmltypeFile : qmltypesFiles) readQmltypes(qmltypeFile, &result.objects); - processImport("", result, QTypeRevision()); + importDependencies(result, &types); + processImport(result, &types); } - qSwap(result, m_exportedName2Scope); - return result; + return types; } FindWarningVisitor::ImportedTypes FindWarningVisitor::Importer::importModule( const QString &module, const QString &prefix, QTypeRevision version) { ImportedTypes result; - qSwap(result, m_exportedName2Scope); - importHelper(module, prefix, version); - qSwap(result, m_exportedName2Scope); + importHelper(module, &result, prefix, version); return result; } -void FindWarningVisitor::Importer::importHelper( - const QString &module, const QString &prefix, QTypeRevision version) +void FindWarningVisitor::Importer::importHelper(const QString &module, ImportedTypes *types, + const QString &prefix, QTypeRevision version) { - const QPair<QString, QString> importId { module, prefix }; - if (m_seenImports.contains(importId)) + + 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; - m_seenImports.insert(importId); + } const auto qmltypesPaths = qQmlResolveImportPaths(module, m_importPaths, version); for (auto const &qmltypesPath : qmltypesPaths) { - if (QFile::exists(qmltypesPath + SlashQmldir)) { - processImport(prefix, readQmldir(qmltypesPath), version); - break; + 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, import.prefix, import.version); + importHelper(import.module, &types, import.prefix, import.version); + result->resolveTypes(types.importedQmlNames); return result; } @@ -260,85 +300,78 @@ FindWarningVisitor::ImportedTypes FindWarningVisitor::Importer::importFileOrDire const QString &fileOrDirectory, const QString &prefix) { ImportedTypes result; - qSwap(result, m_exportedName2Scope); QString name = fileOrDirectory; if (QFileInfo(name).isRelative()) name = QDir(m_currentDir).filePath(name); - if (QFileInfo(name).isFile()) { - ScopeTree::Ptr scope(localFile2ScopeTree(name)); - m_exportedName2Scope.importedQmlNames.insert( - prefix.isEmpty() ? scope->internalName() : prefix, scope); - m_exportedName2Scope.exportedQmlNames.insert(scope->internalName(), scope); - qSwap(result, m_exportedName2Scope); + 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 { name, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; + QDirIterator it { + fileInfo.canonicalFilePath(), + QStringList() << QLatin1String("*.qml"), + QDir::NoFilter + }; while (it.hasNext()) { ScopeTree::Ptr scope(localFile2ScopeTree(it.next())); if (!scope->internalName().isEmpty()) { - m_exportedName2Scope.importedQmlNames.insert(prefixedName(prefix, scope->internalName()), scope); - m_exportedName2Scope.exportedQmlNames.insert(scope->internalName(), scope); + result.importedQmlNames.insert(prefixedName(prefix, scope->internalName()), scope); + result.exportedQmlNames.insert(scope->internalName(), scope); } } - qSwap(result, m_exportedName2Scope); return result; } -void FindWarningVisitor::importExportedNames(QStringView prefix, QString name) +void FindWarningVisitor::importExportedNames(ScopeTree::ConstPtr scope) { QList<ScopeTree::ConstPtr> scopes; - ScopeTree::ConstPtr scope = m_rootScopeImports.importedQmlNames.value( - m_rootScopeImports.importedQmlNames.contains(name) - ? name - : prefix + QLatin1Char('.') + name); - for (;;) { - if (scope) { - if (scopes.contains(scope)) { - QString inheritenceCycle = name; - for (const auto &seen: qAsConst(scopes)) { + while (!scope.isNull()) { + if (scopes.contains(scope)) { + QString inheritenceCycle; + for (const auto &seen: qAsConst(scopes)) { + if (!inheritenceCycle.isEmpty()) inheritenceCycle.append(QLatin1String(" -> ")); - inheritenceCycle.append(seen->baseTypeName()); - } + inheritenceCycle.append(seen->baseTypeName()); + } + if (m_warnInheritanceCycle) { + m_colorOut.write(QLatin1String("Warning: "), Warning); + m_colorOut.write(QString::fromLatin1("%1 is part of an inheritance cycle: %2\n") + .arg(scope->internalName()) + .arg(inheritenceCycle)); + } - if (m_warnInheritanceCycle) { - m_colorOut.write(QLatin1String("Warning: "), Warning); - m_colorOut.write(QString::fromLatin1("%1 is part of an inheritance cycle: %2\n") - .arg(name) - .arg(inheritenceCycle)); - } + m_unknownImports.insert(scope->internalName()); + m_visitFailed = true; + break; + } - m_unknownImports.insert(name); - m_visitFailed = true; - break; - } - scopes.append(scope); - const auto properties = scope->properties(); - for (auto property : properties) { - property.setType(scope->isComposite() - ? m_rootScopeImports.importedQmlNames.value(property.typeName()) - : m_rootScopeImports.cppNames.value(property.typeName())); - m_currentScope->insertPropertyIdentifier(property); - } + scopes.append(scope); + + const auto properties = scope->properties(); + for (auto property : properties) + m_currentScope->insertPropertyIdentifier(property); - m_currentScope->addMethods(scope->methods()); - name = scope->baseTypeName(); - if (name.isEmpty() || name == QLatin1String("QObject")) - break; + m_currentScope->addMethods(scope->methods()); - scope = scope->isComposite() - ? m_rootScopeImports.importedQmlNames.value(name) - : m_rootScopeImports.cppNames.value(name); + if (scope->baseTypeName().isEmpty()) { + break; + } else if (auto newScope = scope->baseType()) { + scope = newScope; } else { m_colorOut.write(QLatin1String("warning: "), Warning); - m_colorOut.write(name + QLatin1String(" was not found." - " Did you add all import paths?\n")); - m_unknownImports.insert(name); + m_colorOut.write(scope->baseTypeName() + + QLatin1String(" was not found." + " Did you add all import paths?\n")); + m_unknownImports.insert(scope->baseTypeName()); m_visitFailed = true; break; } @@ -759,13 +792,10 @@ bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) { // property QtObject __styleData: QtObject {...} - QString name {}; - auto id = uiob->qualifiedTypeNameId; - QStringView prefix = uiob->qualifiedTypeNameId->name; - while (id) { + QString name; + for (auto id = uiob->qualifiedTypeNameId; id; id = id->next) name += id->name.toString() + QLatin1Char('.'); - id = id->next; - } + name.chop(1); MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true, @@ -774,7 +804,8 @@ bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) m_currentScope->addProperty(prop); enterEnvironment(ScopeType::QMLScope, name); - importExportedNames(prefix, name); + m_currentScope->resolveTypes(m_rootScopeImports.importedQmlNames); + importExportedNames(m_currentScope); return true; } @@ -795,19 +826,19 @@ bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) { using namespace QQmlJS::AST; - QString name {}; - auto id = uiod->qualifiedTypeNameId; - QStringView prefix = uiod->qualifiedTypeNameId->name; - while (id) { + QString name; + for (auto id = uiod->qualifiedTypeNameId; id; id = id->next) name += id->name.toString() + QLatin1Char('.'); - id = id->next; - } + name.chop(1); + enterEnvironment(ScopeType::QMLScope, name); + m_currentScope->resolveTypes(m_rootScopeImports.importedQmlNames); + importExportedNames(m_currentScope); + if (name.isLower()) return false; // Ignore grouped properties for now - importExportedNames(prefix, name); if (name.endsWith("Connections")) { QString target; auto member = uiod->initializer->members; diff --git a/tools/qmllint/findwarnings.h b/tools/qmllint/findwarnings.h index cc56f628ee..2a0929b379 100644 --- a/tools/qmllint/findwarnings.h +++ b/tools/qmllint/findwarnings.h @@ -100,19 +100,25 @@ private: QList<QQmlDirParser::Import> dependencies; }; - void importHelper(const QString &module, const QString &prefix = QString(), + void importHelper(const QString &module, ImportedTypes *types, + const QString &prefix = QString(), QTypeRevision version = QTypeRevision()); - void processImport(const QString &prefix, const Import &import, QTypeRevision version); + 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; - QSet<QPair<QString, QString>> m_seenImports; + QHash<QPair<QString, QTypeRevision>, Import> m_seenImports; + QHash<QString, ScopeTree::Ptr> m_importedFiles; QStringList m_warnings; - ImportedTypes m_exportedName2Scope; }; ImportedTypes m_rootScopeImports; @@ -147,7 +153,7 @@ private: void enterEnvironment(ScopeType type, const QString &name); void leaveEnvironment(); - void importExportedNames(QStringView prefix, QString name); + void importExportedNames(ScopeTree::ConstPtr scope); void parseHeaders(QQmlJS::AST::UiHeaderItemList *headers); ScopeTree::Ptr parseProgram(QQmlJS::AST::Program *program, const QString &name); diff --git a/tools/shared/scopetree.cpp b/tools/shared/scopetree.cpp index 4d02c67491..2ae2dfe054 100644 --- a/tools/shared/scopetree.cpp +++ b/tools/shared/scopetree.cpp @@ -144,6 +144,33 @@ bool ScopeTree::isIdInjectedFromSignal(const QString &id) const return findCurrentQMLScope(parentScope())->m_injectedSignalIdentifiers.contains(id); } +void ScopeTree::resolveTypes(const QHash<QString, ScopeTree::Ptr> &contextualTypes) +{ + auto findType = [&](const QString &name) { + auto type = contextualTypes.constFind(name); + if (type != contextualTypes.constEnd()) + return *type; + + return ScopeTree::Ptr(); + }; + + m_baseType = findType(m_baseTypeName); + + 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; diff --git a/tools/shared/scopetree.h b/tools/shared/scopetree.h index 9c16f6b05f..ecb9646db2 100644 --- a/tools/shared/scopetree.h +++ b/tools/shared/scopetree.h @@ -155,6 +155,7 @@ public: // 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; } @@ -207,6 +208,8 @@ public: return m_injectedSignalIdentifiers; } + void resolveTypes(const QHash<QString, ScopeTree::Ptr> &contextualTypes); + private: ScopeTree(ScopeType type, const ScopeTree::Ptr &parentScope = ScopeTree::Ptr()); @@ -226,6 +229,7 @@ private: QString m_fileName; QString m_internalName; QString m_baseTypeName; + ScopeTree::WeakPtr m_baseType; ScopeType m_scopeType = ScopeType::QMLScope; QList<Export> m_exports; |