diff options
-rw-r--r-- | tests/auto/qml/qmllint/data/memberNotFound.qml | 7 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/unknownJavascriptMethod.qml | 6 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 10 | ||||
-rw-r--r-- | tools/qmllint/findunqualified.cpp | 68 | ||||
-rw-r--r-- | tools/qmllint/findunqualified.h | 10 | ||||
-rw-r--r-- | tools/qmllint/metatypes.h | 19 | ||||
-rw-r--r-- | tools/qmllint/qmllint.pro | 2 | ||||
-rw-r--r-- | tools/qmllint/scopetree.cpp | 225 | ||||
-rw-r--r-- | tools/qmllint/scopetree.h | 43 |
9 files changed, 312 insertions, 78 deletions
diff --git a/tests/auto/qml/qmllint/data/memberNotFound.qml b/tests/auto/qml/qmllint/data/memberNotFound.qml new file mode 100644 index 0000000000..da2e353227 --- /dev/null +++ b/tests/auto/qml/qmllint/data/memberNotFound.qml @@ -0,0 +1,7 @@ +import QtQml 2.0 + +QtObject { + id: self + property string n: self.objectName + property string not: self.foo +} diff --git a/tests/auto/qml/qmllint/data/unknownJavascriptMethod.qml b/tests/auto/qml/qmllint/data/unknownJavascriptMethod.qml new file mode 100644 index 0000000000..2718e07c60 --- /dev/null +++ b/tests/auto/qml/qmllint/data/unknownJavascriptMethod.qml @@ -0,0 +1,6 @@ +import QtQml 2.0 +import "Methods.js" as Methods + +QtObject { + objectName: Methods.foo2() +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index d31fd9309b..d63017e5b1 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -109,7 +109,7 @@ void TestQmllint::testUnqualified_data() void TestQmllint::testUnqualifiedNoSpuriousParentWarning() { - const QString unknownNotFound = runQmllint("spuriousParentWarning.qml", true); + const QString unknownNotFound = runQmllint("spuriousParentWarning.qml", false); QVERIFY(unknownNotFound.contains( QStringLiteral("warning: Unknown was not found. Did you add all import paths?"))); } @@ -132,6 +132,14 @@ void TestQmllint::dirtyQmlCode_data() << QStringLiteral("AutomatchedSignalHandler.qml") << QString("Warning: unqualified access at 12:36") << QStringLiteral("no matching signal found"); + QTest::newRow("MemberNotFound") + << QStringLiteral("memberNotFound.qml") + << QString("Warning: Property \"foo\" not found on type \"QtObject\" at 6:31") + << QString(); + QTest::newRow("UnknownJavascriptMethd") + << QStringLiteral("unknownJavascriptMethod.qml") + << QString("Warning: Property \"foo2\" not found on type \"Methods\" at 5:25") + << QString(); } void TestQmllint::dirtyQmlCode() diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 9cf26be4ad..ea28884f7b 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -62,9 +62,9 @@ static TypeDescriptionReader createQmltypesReaderForFile(const QString &filename return reader; } -void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, QString name) +void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, const QString &name) { - m_currentScope = m_currentScope->createNewChildScope(type, std::move(name)); + m_currentScope = m_currentScope->createNewChildScope(type, name).get(); } void FindUnqualifiedIDVisitor::leaveEnvironment() @@ -150,7 +150,7 @@ void FindUnqualifiedIDVisitor::parseMembers(QQmlJS::AST::UiObjectMemberList *mem case UiPublicMember::Property: { MetaProperty prop { publicMember->name.toString(), - publicMember->typeModifier.toString(), + publicMember->memberType->name.toString(), false, false, false, @@ -352,6 +352,7 @@ void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUn // add objects for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { const auto &val = it.value(); + m_types[it.key()] = val; m_exportedName2Scope.insert(prefixedName(prefix, val->className()), val); const auto exports = val->exports(); @@ -467,8 +468,10 @@ void FindUnqualifiedIDVisitor::importExportedNames(const QStringRef &prefix, QSt : prefix + QLatin1Char('.') + name); if (scope) { const auto properties = scope->properties(); - for (const auto &property : properties) + for (auto property : properties) { + property.setType(m_exportedName2Scope.value(property.typeName()).get()); m_currentScope->insertPropertyIdentifier(property); + } m_currentScope->addMethods(scope->methods()); name = scope->superclassName(); @@ -508,8 +511,9 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) } } // add builtins - for (auto ob_it = objects.begin(); ob_it != objects.end(); ++ob_it) { - auto val = ob_it.value(); + for (auto objectIt = objects.begin(); objectIt != objects.end(); ++objectIt) { + auto val = objectIt.value(); + m_types[objectIt.key()] = val; const auto exports = val->exports(); for (const auto &valExport : exports) @@ -655,7 +659,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) auto expstat = cast<ExpressionStatement *>(uisb->statement); auto identexp = cast<IdentifierExpression *>(expstat->expression); QString elementName = m_currentScope->name(); - m_qmlid2scope.insert(identexp->name.toString(), m_exportedName2Scope.value(elementName)); + m_qmlid2scope.insert(identexp->name.toString(), m_currentScope); if (m_currentScope->isVisualRootScope()) m_rootId = identexp->name.toString(); } else { @@ -695,23 +699,23 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm) { // property bool inactive: !active // extract name inactive - m_currentScope->insertPropertyIdentifier(MetaProperty( + MetaProperty property( uipm->name.toString(), // TODO: signals, complex types etc. uipm->memberType ? uipm->memberType->name.toString() : QString(), uipm->typeModifier == QLatin1String("list"), !uipm->isReadonlyMember, - false, 0)); + false, 0); + property.setType(m_exportedName2Scope.value(property.typeName()).get()); + m_currentScope->insertPropertyIdentifier(property); return true; } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) { auto name = idexp->name; - if (!m_exportedName2Scope.contains(name.toString())) { - m_currentScope->addIdToAccssedIfNotInParentScopes( - { name.toString(), idexp->firstSourceLocation() }, m_unknownImports); - } + m_currentScope->addIdToAccessed(name.toString(), idexp->firstSourceLocation()); + m_fieldMemberBase = idexp; return true; } @@ -766,8 +770,8 @@ bool FindUnqualifiedIDVisitor::check() QScopedValueRollback<ScopeTree*> rollback(m_currentScope, outstandingConnection.scope); outstandingConnection.uiod->initializer->accept(this); } - return m_rootScope->recheckIdentifiers(m_code, m_qmlid2scope, m_rootScope.get(), m_rootId, - m_colorOut); + return m_rootScope->recheckIdentifiers(m_code, m_qmlid2scope, m_exportedName2Scope, + m_rootScope.get(), m_rootId, m_colorOut); } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl) @@ -839,7 +843,8 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) QString path {}; if (!import->importId.isEmpty()) { // TODO: do not put imported ids into the same space as qml IDs - m_qmlid2scope.insert(import->importId.toString(), {}); + const QString importId = import->importId.toString(); + m_qmlid2scope.insert(importId, m_exportedName2Scope.value(importId).get()); } if (import->version) { auto uri = import->importUri; @@ -877,7 +882,8 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) } name.chop(1); - const MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true, 0); + MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true, 0); + prop.setType(m_exportedName2Scope.value(uiob->qualifiedTypeNameId->name.toString()).get()); m_currentScope->addProperty(prop); enterEnvironment(ScopeType::QMLScope, name); @@ -885,9 +891,15 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) return true; } -void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *) +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) { + const auto childScope = m_currentScope; leaveEnvironment(); + MetaProperty property(uiob->qualifiedId->name.toString(), + uiob->qualifiedTypeNameId->name.toString(), + false, true, true, 0); + property.setType(childScope); + m_currentScope->addProperty(property); } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) @@ -927,14 +939,14 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) } member = member->next; } - ScopeTree::ConstPtr targetScope; + const ScopeTree *targetScope; if (target.isEmpty()) { // no target set, connection comes from parentF ScopeTree* scope = m_currentScope; do { scope = scope->parentScope(); // TODO: rename method } while (scope->scopeType() != ScopeType::QMLScope); - targetScope = m_exportedName2Scope.value(scope->name()); + targetScope = m_exportedName2Scope.value(scope->name()).get(); } else { // there was a target, check if we already can find it auto scopeIt = m_qmlid2scope.find(target); @@ -967,3 +979,19 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *) { leaveEnvironment(); } + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FieldMemberExpression *) +{ + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember) +{ + if (m_fieldMemberBase == fieldMember->base) { + m_currentScope->accessMember(fieldMember->name.toString(), + fieldMember->identifierToken); + m_fieldMemberBase = fieldMember; + } else { + m_fieldMemberBase = nullptr; + } +} diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index a70bf8032f..37ba259638 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -65,11 +65,13 @@ private: }; QScopedPointer<ScopeTree> m_rootScope; - ScopeTree *m_currentScope = nullptr; + ScopeTree *m_currentScope; + QQmlJS::AST::ExpressionNode *m_fieldMemberBase = nullptr; + QHash<QString, ScopeTree::ConstPtr> m_types; QHash<QString, ScopeTree::ConstPtr> m_exportedName2Scope; QStringList m_qmltypeDirs; QString m_code; - QHash<QString, ScopeTree::ConstPtr> m_qmlid2scope; + QHash<QString, const ScopeTree *> m_qmlid2scope; QString m_rootId; QString m_filePath; QSet<QPair<QString, QString>> m_alreadySeenImports; @@ -86,7 +88,7 @@ private: QVarLengthArray<OutstandingConnection, 3> m_outstandingConnections; // Connections whose target we have not encountered - void enterEnvironment(ScopeType type, QString name); + void enterEnvironment(ScopeType type, const QString &name); void leaveEnvironment(); void importHelper(const QString &module, const QString &prefix = QString(), int major = -1, int minor = -1); @@ -158,6 +160,8 @@ private: bool visit(QQmlJS::AST::IdentifierExpression *idexp) override; bool visit(QQmlJS::AST::PatternElement *) override; + bool visit(QQmlJS::AST::FieldMemberExpression *idprop) override; + void endVisit(QQmlJS::AST::FieldMemberExpression *) override; }; #endif // FINDUNQUALIFIED_H diff --git a/tools/qmllint/metatypes.h b/tools/qmllint/metatypes.h index 24f8aa291e..4710ac1613 100644 --- a/tools/qmllint/metatypes.h +++ b/tools/qmllint/metatypes.h @@ -114,28 +114,33 @@ private: int m_revision = 0; }; +class ScopeTree; class MetaProperty { QString m_propertyName; - QString m_type; + QString m_typeName; + const ScopeTree *m_type = nullptr; bool m_isList; bool m_isWritable; bool m_isPointer; int m_revision; public: - MetaProperty(QString name, QString type, - bool isList, bool isWritable, bool isPointer, int revision) - : m_propertyName(std::move(name)) - , m_type(std::move(type)) + MetaProperty(QString propertyName, QString typeName, + bool isList, bool isWritable, bool isPointer, int revision) + : m_propertyName(std::move(propertyName)) + , m_typeName(std::move(typeName)) , m_isList(isList) , m_isWritable(isWritable) , m_isPointer(isPointer) , m_revision(revision) {} - QString name() const { return m_propertyName; } - QString typeName() const { return m_type; } + QString propertyName() const { return m_propertyName; } + QString typeName() const { return m_typeName; } + + void setType(const ScopeTree *type) { m_type = type; } + const ScopeTree *type() const { return m_type; } bool isList() const { return m_isList; } bool isWritable() const { return m_isWritable; } diff --git a/tools/qmllint/qmllint.pro b/tools/qmllint/qmllint.pro index 2631768b81..8ab31bc83c 100644 --- a/tools/qmllint/qmllint.pro +++ b/tools/qmllint/qmllint.pro @@ -1,6 +1,6 @@ option(host_build) -QT = core qmldevtools-private +QT = core-private qmldevtools-private SOURCES += main.cpp \ componentversion.cpp \ diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index 7e9be92673..cac064eb27 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -36,13 +36,13 @@ ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope) : m_parentScope(parentScope), m_name(std::move(name)), m_scopeType(type) {} -ScopeTree *ScopeTree::createNewChildScope(ScopeType type, QString name) +ScopeTree::Ptr ScopeTree::createNewChildScope(ScopeType type, const QString &name) { Q_ASSERT(type != ScopeType::QMLScope || !m_parentScope || m_parentScope->m_scopeType == ScopeType::QMLScope || m_parentScope->m_name == "global"); - auto childScope = new ScopeTree{type, std::move(name), this}; + auto childScope = ScopeTree::Ptr(new ScopeTree{type, name, this}); m_childScopes.push_back(childScope); return childScope; } @@ -72,7 +72,7 @@ void ScopeTree::insertSignalIdentifier(const QString &id, const MetaMethod &meth void ScopeTree::insertPropertyIdentifier(const MetaProperty &property) { addProperty(property); - MetaMethod method(property.name() + QLatin1String("Changed"), "void"); + MetaMethod method(property.propertyName() + QLatin1String("Changed"), "void"); addMethod(method); } @@ -87,21 +87,22 @@ bool ScopeTree::isIdInCurrentScope(const QString &id) const return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); } -void ScopeTree::addIdToAccssedIfNotInParentScopes( - const QPair<QString, QQmlJS::AST::SourceLocation> &idLocationPair, - const QSet<QString> &unknownImports) +void ScopeTree::addIdToAccessed(const QString &id, const QQmlJS::AST::SourceLocation &location) { + m_currentFieldMember = new FieldMemberList {id, location, {}}; + m_accessedIdentifiers.push_back(std::unique_ptr<FieldMemberList>(m_currentFieldMember)); +} + +void ScopeTree::accessMember(const QString &name, const QQmlJS::AST::SourceLocation &location) { - // also do not add id if it is parent - // parent is almost always defined valid in QML, and if we could not find a definition for the current QML component - // not skipping "parent" will lead to many false positives - // Moreover, if the top level item is Item or inherits from it, it will have a parent property to which we would point the user - // which makes for a very nonsensical warning - const auto *qmlScope = currentQMLScope(); - if (!isIdInCurrentScope(idLocationPair.first) - && !(idLocationPair.first == QLatin1String("parent") - && qmlScope && unknownImports.contains(qmlScope->name()))) { - m_accessedIdentifiers.push_back(idLocationPair); - } + Q_ASSERT(m_currentFieldMember); + auto *fieldMember = new FieldMemberList {name, location, {}}; + m_currentFieldMember->m_child.reset(fieldMember); + m_currentFieldMember = fieldMember; +} + +void ScopeTree::resetMemberScope() +{ + m_currentFieldMember = nullptr; } bool ScopeTree::isVisualRootScope() const @@ -132,9 +133,121 @@ private: QStringRef m_afterText; }; +bool ScopeTree::checkMemberAccess( + const QString &code, + FieldMemberList *members, + const ScopeTree *scope, + const QHash<QString, ScopeTree::ConstPtr> &types, + ColorOutput& colorOut) const +{ + if (!members->m_child) + return true; + + Q_ASSERT(scope != nullptr); + + const QString scopeName = scope->name().isEmpty() ? scope->className() : scope->name(); + const auto &access = members->m_child; + + const auto scopeIt = scope->m_properties.find(access->m_name); + if (scopeIt != scope->m_properties.end()) { + if (scopeIt->isList() || scopeIt->typeName() == QLatin1String("string")) { + if (access->m_child && access->m_child->m_name != QLatin1String("length")) { + colorOut.write("Warning: ", Warning); + colorOut.write( + QString::fromLatin1( + "\"%1\" is a %2. You cannot access \"%3\" on it at %4:%5\n") + .arg(access->m_name) + .arg(QLatin1String(scopeIt->isList() ? "list" : "string")) + .arg(access->m_child->m_name) + .arg(access->m_child->m_location.startLine) + .arg(access->m_child->m_location.startColumn), Normal); + printContext(colorOut, code, access->m_child->m_location); + return false; + } + return true; + } + const ScopeTree *type = scopeIt->type() ? scopeIt->type() + : types.value(scopeIt->typeName()).get(); + return checkMemberAccess(code, access.get(), type, types, colorOut); + } + + const auto scopeMethodIt = scope->m_methods.find(access->m_name); + if (scopeMethodIt != scope->m_methods.end()) + return true; // Access to property of JS function + + for (const auto enumerator : scope->m_enums) { + for (const QString &key : enumerator.keys()) { + if (access->m_name != key) + continue; + + if (!access->m_child) + return true; + + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "\"%1\" is an enum value. You cannot access \"%2\" on it at %3:%4\n") + .arg(access->m_name) + .arg(access->m_child->m_name) + .arg(access->m_child->m_location.startLine) + .arg(access->m_child->m_location.startColumn), Normal); + printContext(colorOut, code, access->m_child->m_location); + return false; + } + } + + auto type = types.value(scopeName); + while (type) { + const auto typeIt = type->m_properties.find(access->m_name); + if (typeIt != type->m_properties.end()) { + const auto propType = typeIt->type(); + return checkMemberAccess(code, access.get(), + propType ? propType : types.value(typeIt->typeName()).get(), + types, colorOut); + } + + const auto typeMethodIt = type->m_methods.find(access->m_name); + if (typeMethodIt != type->m_methods.end()) { + if (access->m_child == nullptr) + return true; + + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "\"%1\" is a method. You cannot access \"%2\" on it at %3:%4\n") + .arg(access->m_name) + .arg(access->m_child->m_name) + .arg(access->m_child->m_location.startLine) + .arg(access->m_child->m_location.startColumn), Normal); + printContext(colorOut, code, access->m_child->m_location); + return false; + } + + type = types.value(type->superclassName()); + } + + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "Property \"%1\" not found on type \"%2\" at %3:%4\n") + .arg(access->m_name) + .arg(scopeName) + .arg(access->m_location.startLine) + .arg(access->m_location.startColumn), Normal); + printContext(colorOut, code, access->m_location); + return false; +} + +static const QStringList unknownBuiltins = { + QStringLiteral("alias"), // TODO: we cannot properly resolve aliases, yet + QStringLiteral("QRectF"), // TODO: should be added to builtins.qmltypes + QStringLiteral("QJSValue"), // We cannot say anything intelligent about untyped JS values. + QStringLiteral("variant"), // Same for generic variants +}; + bool ScopeTree::recheckIdentifiers( - const QString &code, const QHash<QString, ScopeTree::ConstPtr> &qmlIDs, - const ScopeTree *root, const QString &rootId, ColorOutput &colorOut) const + const QString &code, + const QHash<QString, const ScopeTree *> &qmlIDs, + const QHash<QString, ScopeTree::ConstPtr> &types, + const ScopeTree *root, const QString &rootId, + ColorOutput& colorOut) const { bool noUnqualifiedIdentifier = true; @@ -152,15 +265,61 @@ bool ScopeTree::recheckIdentifiers( printContext(colorOut, code, handler.second); } - for (const auto &idLocationPair : qAsConst(currentScope->m_accessedIdentifiers)) { - if (qmlIDs.contains(idLocationPair.first)) + for (const auto &memberAccessTree : qAsConst(currentScope->m_accessedIdentifiers)) { + if (currentScope->isIdInCurrentJSScopes(memberAccessTree->m_name)) + continue; + + auto it = qmlIDs.find(memberAccessTree->m_name); + if (it != qmlIDs.end()) { + if (!checkMemberAccess(code, memberAccessTree.get(), *it, types, colorOut)) + noUnqualifiedIdentifier = false; + continue; + } + + auto qmlScope = currentScope->currentQMLScope(); + if (qmlScope->methods().contains(memberAccessTree->m_name)) { + // a property of a JavaScript function + continue; + } + + const auto qmlIt = qmlScope->m_properties.find(memberAccessTree->m_name); + if (qmlIt != qmlScope->m_properties.end()) { + if (!memberAccessTree->m_child || unknownBuiltins.contains(qmlIt->typeName())) + continue; + + if (!qmlIt->type()) { + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "Type of property \"%2\" not found at %3:%4\n") + .arg(memberAccessTree->m_name) + .arg(memberAccessTree->m_location.startLine) + .arg(memberAccessTree->m_location.startColumn), Normal); + printContext(colorOut, code, memberAccessTree->m_location); + noUnqualifiedIdentifier = false; + } else if (!checkMemberAccess(code, memberAccessTree.get(), qmlIt->type(), types, + colorOut)) { + noUnqualifiedIdentifier = false; + } + continue; - if (currentScope->isIdInCurrentScope(idLocationPair.first)) { + } + + // TODO: Lots of builtins are missing + if (memberAccessTree->m_name == "Qt") + continue; + + const auto typeIt = types.find(memberAccessTree->m_name); + if (typeIt != types.end()) { + if (!checkMemberAccess(code, memberAccessTree.get(), typeIt->get(), types, + colorOut)) { + noUnqualifiedIdentifier = false; + } continue; } + noUnqualifiedIdentifier = false; colorOut.write("Warning: ", Warning); - auto location = idLocationPair.second; + auto location = memberAccessTree->m_location; colorOut.write(QString::fromLatin1("unqualified access at %1:%2\n") .arg(location.startLine).arg(location.startColumn), Normal); @@ -169,11 +328,11 @@ bool ScopeTree::recheckIdentifiers( // root(JS) --> program(qml) --> (first element) const auto firstElement = root->m_childScopes[0]->m_childScopes[0]; - if (firstElement->m_properties.contains(idLocationPair.first) - || firstElement->m_methods.contains(idLocationPair.first) - || firstElement->m_enums.contains(idLocationPair.first)) { + if (firstElement->m_properties.contains(memberAccessTree->m_name) + || firstElement->m_methods.contains(memberAccessTree->m_name) + || firstElement->m_enums.contains(memberAccessTree->m_name)) { colorOut.write("Note: ", Info); - colorOut.write( idLocationPair.first + QLatin1String(" is a meber of the root element\n"), Normal ); + colorOut.write(memberAccessTree->m_name + QLatin1String(" is a meber of the root element\n"), Normal ); colorOut.write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal); if (rootId == QLatin1String("<id>")) { colorOut.write("Note: ", Warning); @@ -184,10 +343,10 @@ bool ScopeTree::recheckIdentifiers( colorOut.write(rootId + QLatin1Char('.'), Hint); colorOut.write(issueLocationWithContext.issueText().toString(), Normal); colorOut.write(issueLocationWithContext.afterText() + QLatin1Char('\n'), Normal); - } else if (currentScope->isIdInjectedFromSignal(idLocationPair.first)) { + } else if (currentScope->isIdInjectedFromSignal(memberAccessTree->m_name)) { auto methodUsages = currentScope->currentQMLScope()->m_injectedSignalIdentifiers - .values(idLocationPair.first); - auto location = idLocationPair.second; + .values(memberAccessTree->m_name); + auto location = memberAccessTree->m_location; // sort the list of signal handlers by their occurrence in the source code // then, we select the first one whose location is after the unqualified id // and go one step backwards to get the one which we actually need @@ -206,7 +365,7 @@ bool ScopeTree::recheckIdentifiers( auto methodUsage = *(--oneBehindIt); colorOut.write("Note:", Info); colorOut.write( - idLocationPair.first + QString::fromLatin1( + memberAccessTree->m_name + QString::fromLatin1( " is accessible in this scope because " "you are handling a signal at %1:%2\n") .arg(methodUsage.loc.startLine).arg(methodUsage.loc.startColumn), @@ -226,8 +385,8 @@ bool ScopeTree::recheckIdentifiers( } colorOut.write("\n\n\n", Normal); } - for (auto const& childScope: currentScope->m_childScopes) - workQueue.enqueue(childScope); + for (auto const &childScope: currentScope->m_childScopes) + workQueue.enqueue(childScope.get()); } return noUnqualifiedIdentifier; } diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index b7619c4352..00cb466eb9 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -104,11 +104,11 @@ public: int m_metaObjectRevision = 0; }; - ScopeTree(ScopeType type, QString name="<none given>", ScopeTree *parentScope=nullptr); - ~ScopeTree() { qDeleteAll(m_childScopes); } + ScopeTree(ScopeType type, QString name = QString(), + ScopeTree *parentScope = nullptr); - ScopeTree *createNewChildScope(ScopeType type, QString name); - ScopeTree *parentScope() { return m_parentScope; } + ScopeTree::Ptr createNewChildScope(ScopeType type, const QString &name); + ScopeTree *parentScope() const { return m_parentScope; } void insertJSIdentifier(const QString &id, QQmlJS::AST::VariableScope scope); void insertSignalIdentifier(const QString &id, const MetaMethod &method, @@ -119,16 +119,18 @@ public: const QQmlJS::AST::SourceLocation &location); bool isIdInCurrentScope(const QString &id) const; - void addIdToAccssedIfNotInParentScopes( - const QPair<QString, QQmlJS::AST::SourceLocation> &idLocationPair, - const QSet<QString> &unknownImports); + void addIdToAccessed(const QString &id, const QQmlJS::AST::SourceLocation &location); + void accessMember(const QString &name, const QQmlJS::AST::SourceLocation &location); + void resetMemberScope(); bool isVisualRootScope() const; QString name() const { return m_name; } bool recheckIdentifiers( - const QString &code, const QHash<QString, ScopeTree::ConstPtr> &qmlIDs, - const ScopeTree *root, const QString &rootId, ColorOutput &colorOut) const; + const QString &code, + const QHash<QString, const ScopeTree *> &qmlIDs, + const QHash<QString, ScopeTree::ConstPtr> &types, + const ScopeTree *root, const QString& rootId, ColorOutput &colorOut) const; ScopeType scopeType() const { return m_scopeType; } @@ -149,7 +151,7 @@ public: void setSuperclassName(const QString &superclass) { m_superName = superclass; } QString superclassName() const { return m_superName; } - void addProperty(const MetaProperty &prop) { m_properties.insert(prop.name(), prop); } + 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; } @@ -166,6 +168,13 @@ public: void setIsComposite(bool value) { m_isSingleton = value; } private: + struct FieldMemberList + { + QString m_name; + QQmlJS::AST::SourceLocation m_location; + std::unique_ptr<FieldMemberList> m_child; + }; + QSet<QString> m_jsIdentifiers; QMultiHash<QString, MethodUsage> m_injectedSignalIdentifiers; @@ -173,11 +182,13 @@ private: QHash<QString, MetaProperty> m_properties; QHash<QString, MetaEnum> m_enums; - QVector<QPair<QString, QQmlJS::AST::SourceLocation>> m_accessedIdentifiers; + std::vector<std::unique_ptr<FieldMemberList>> m_accessedIdentifiers; + FieldMemberList *m_currentFieldMember = nullptr; + QVector<QPair<QString, QQmlJS::AST::SourceLocation>> m_unmatchedSignalHandlers; - QVector<ScopeTree *> m_childScopes; - ScopeTree *m_parentScope = nullptr; + QVector<ScopeTree::Ptr> m_childScopes; + ScopeTree *m_parentScope; QString m_name; QString m_className; @@ -198,6 +209,12 @@ private: const ScopeTree *currentQMLScope() const; void printContext(ColorOutput &colorOut, const QString &code, const QQmlJS::AST::SourceLocation &location) const; + bool checkMemberAccess( + const QString &code, + FieldMemberList *members, + const ScopeTree *scope, + const QHash<QString, ScopeTree::ConstPtr> &types, + ColorOutput& colorOut) const; }; #endif // SCOPETREE_H |