aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tests/auto/qml/qmllint/data/memberNotFound.qml7
-rw-r--r--tests/auto/qml/qmllint/data/unknownJavascriptMethod.qml6
-rw-r--r--tests/auto/qml/qmllint/tst_qmllint.cpp10
-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
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