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>
This commit is contained in:
Ulf Hermann 2019-11-08 14:48:32 +01:00
parent d2b3cb0a87
commit d0b2a3b5eb
9 changed files with 312 additions and 78 deletions

View File

@ -0,0 +1,7 @@
import QtQml 2.0
QtObject {
id: self
property string n: self.objectName
property string not: self.foo
}

View File

@ -0,0 +1,6 @@
import QtQml 2.0
import "Methods.js" as Methods
QtObject {
objectName: Methods.foo2()
}

View File

@ -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()

View File

@ -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;
}
}

View File

@ -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

View File

@ -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; }

View File

@ -1,6 +1,6 @@
option(host_build)
QT = core qmldevtools-private
QT = core-private qmldevtools-private
SOURCES += main.cpp \
componentversion.cpp \

View File

@ -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;
if (currentScope->isIdInCurrentScope(idLocationPair.first)) {
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;
}
// 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;
}

View File

@ -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