aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2019-11-08 14:48:32 +0100
committerUlf Hermann <ulf.hermann@qt.io>2020-01-13 16:54:08 +0100
commitd0b2a3b5eb4021fb19b634e550cbc6f6664ad775 (patch)
tree4fd37a9c1993451554d0a694bcfaf0cfc5c07a1e /tools
parentd2b3cb0a8794dacbe929ae67447d16377efbccd7 (diff)
qmllint: Analyze member access
We can analyze access to many field member expressions and figure out if the accessed members exist. There are limits to this, of course. Generic JavaScript values are out of scope here. Change-Id: Id2e7613e56f06555cc3a2ba1c51683d9ea0bb84b Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tools')
-rw-r--r--tools/qmllint/findunqualified.cpp68
-rw-r--r--tools/qmllint/findunqualified.h10
-rw-r--r--tools/qmllint/metatypes.h19
-rw-r--r--tools/qmllint/qmllint.pro2
-rw-r--r--tools/qmllint/scopetree.cpp225
-rw-r--r--tools/qmllint/scopetree.h43
6 files changed, 290 insertions, 77 deletions
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