diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri index a5e154a0eb0c285468aa1c76519ccba1e07e7847..e79c071181c1acd970297c53a0b2307aae6232cf 100644 --- a/src/libs/qmljs/qmljs-lib.pri +++ b/src/libs/qmljs/qmljs-lib.pri @@ -17,7 +17,8 @@ HEADERS += \ $$PWD/qmljsscanner.h \ $$PWD/qmljsinterpreter.h \ $$PWD/qmljslink.h \ - $$PWD/qmljscheck.h + $$PWD/qmljscheck.h \ + $$PWD/qmljsscopebuilder.h SOURCES += \ $$PWD/qmljsbind.cpp \ @@ -27,7 +28,8 @@ SOURCES += \ $$PWD/qmljsinterpreter.cpp \ $$PWD/qmljsmetatypesystem.cpp \ $$PWD/qmljslink.cpp \ - $$PWD/qmljscheck.cpp + $$PWD/qmljscheck.cpp \ + $$PWD/qmljsscopebuilder.cpp contains(QT_CONFIG, declarative) { QT += declarative diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index 383ab9ee0082c5ac52d644ef6dc98862c4700101..c8dcc6db638cf6cff1fef1d262fb0b7efd6bfaef 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -61,7 +61,7 @@ Check::Check(Document::Ptr doc, const Snapshot &snapshot) , _snapshot(snapshot) , _context(&_engine) , _link(&_context, doc, snapshot) - , _allowAnyProperty(false) + , _scopeBuilder(doc, &_context) { } @@ -109,71 +109,20 @@ void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId, return; } - const bool oldAllowAnyProperty = _allowAnyProperty; + _scopeBuilder.push(ast); if (! _context.lookupType(_doc.data(), typeId)) { warning(typeId->identifierToken, tr(Messages::unknown_type)); - _allowAnyProperty = true; // suppress subsequent "unknown property" errors + // suppress subsequent errors about scope object lookup by clearing + // the scope object list + // ### todo: better way? + _context.scopeChain().qmlScopeObjects.clear(); + _context.scopeChain().update(); } - QList<const ObjectValue *> oldScopeObjects = _context.scopeChain().qmlScopeObjects; - - _context.scopeChain().qmlScopeObjects.clear(); - const ObjectValue *scopeObject = _doc->bind()->findQmlObject(ast); - _context.scopeChain().qmlScopeObjects += scopeObject; - _context.scopeChain().update(); - -#ifndef NO_DECLARATIVE_BACKEND - // check if the object has a Qt.ListElement ancestor - const ObjectValue *prototype = scopeObject->prototype(&_context); - while (prototype) { - if (const QmlObjectValue *qmlMetaObject = dynamic_cast<const QmlObjectValue *>(prototype)) { - // ### Also check for Qt package. Involves changes in QmlObjectValue. - if (qmlMetaObject->qmlTypeName() == QLatin1String("ListElement")) { - _allowAnyProperty = true; - break; - } - } - prototype = prototype->prototype(&_context); - } - - // check if the object has a Qt.PropertyChanges ancestor - prototype = scopeObject->prototype(&_context); - while (prototype) { - if (const QmlObjectValue *qmlMetaObject = dynamic_cast<const QmlObjectValue *>(prototype)) { - // ### Also check for Qt package. Involves changes in QmlObjectValue. - if (qmlMetaObject->qmlTypeName() == QLatin1String("PropertyChanges")) - break; - } - prototype = prototype->prototype(&_context); - } - // find the target script binding - if (prototype && initializer) { - for (UiObjectMemberList *m = initializer->members; m; m = m->next) { - if (UiScriptBinding *scriptBinding = cast<UiScriptBinding *>(m->member)) { - if (scriptBinding->qualifiedId - && scriptBinding->qualifiedId->name->asString() == QLatin1String("target") - && ! scriptBinding->qualifiedId->next) { - if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(scriptBinding->statement)) { - Evaluate evaluator(&_context); - const Value *targetValue = evaluator(expStmt->expression); - - if (const ObjectValue *target = value_cast<const ObjectValue *>(targetValue)) { - _context.scopeChain().qmlScopeObjects.prepend(target); - } else { - _allowAnyProperty = true; - } - } - } - } - } - } -#endif - Node::accept(initializer, this); - _context.scopeChain().qmlScopeObjects = oldScopeObjects; - _allowAnyProperty = oldAllowAnyProperty; + _scopeBuilder.pop(); } bool Check::visit(UiScriptBinding *ast) @@ -231,10 +180,9 @@ bool Check::visit(UiArrayBinding *ast) const Value *Check::checkScopeObjectMember(const UiQualifiedId *id) { - if (_allowAnyProperty) - return 0; - QList<const ObjectValue *> scopeObjects = _context.scopeChain().qmlScopeObjects; + if (scopeObjects.isEmpty()) + return 0; if (! id) return 0; // ### error? diff --git a/src/libs/qmljs/qmljscheck.h b/src/libs/qmljs/qmljscheck.h index 8810865f46ea4260f5f5a1ca359111135621e223..778952bf939a3308d028ad78c978aed362f8ba81 100644 --- a/src/libs/qmljs/qmljscheck.h +++ b/src/libs/qmljs/qmljscheck.h @@ -33,6 +33,7 @@ #include <qmljs/qmljsdocument.h> #include <qmljs/qmljsinterpreter.h> #include <qmljs/qmljslink.h> +#include <qmljs/qmljsscopebuilder.h> #include <qmljs/parser/qmljsastvisitor_p.h> namespace QmlJS { @@ -72,10 +73,9 @@ private: Interpreter::Engine _engine; Interpreter::Context _context; Link _link; + ScopeBuilder _scopeBuilder; QList<DiagnosticMessage> _messages; - - bool _allowAnyProperty; }; } // namespace QmlJS diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index b41c7b11ccd283456c4162a13416c8c5a3026ba5..07762a94b095e7257bc20a65e1574e3ca8bfc275 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -3,6 +3,7 @@ #include "parser/qmljsast_p.h" #include "qmljsdocument.h" #include "qmljsbind.h" +#include "qmljsscopebuilder.h" #include <QtCore/QFileInfo> #include <QtCore/QDir> @@ -44,37 +45,16 @@ void Link::scopeChainAt(Document::Ptr doc, const QList<Node *> &astPath) Bind *bind = doc->bind(); QHash<Document *, ScopeChain::QmlComponentChain *> componentScopes; - int qmlScopeObjectIndex = -1; - if (doc->qmlProgram()) { _context->setLookupMode(Context::QmlLookup); makeComponentChain(doc, &scopeChain.qmlComponentScope, &componentScopes); - // find the last object definition or object binding: that is the scope object - for (int i = astPath.size() - 1; i >= 0; --i) { - Node *node = astPath.at(i); - - ObjectValue *scopeObject = 0; - if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(node)) - scopeObject = bind->findQmlObject(definition); - else if (UiObjectBinding *binding = cast<UiObjectBinding *>(node)) - scopeObject = bind->findQmlObject(binding); - - if (scopeObject) { - if (scopeObject != scopeChain.qmlComponentScope.rootObject) - scopeChain.qmlScopeObjects += scopeObject; - qmlScopeObjectIndex = i; - break; - } - } - if (const ObjectValue *typeEnvironment = _context->typeEnvironment(doc.data())) scopeChain.qmlTypes = typeEnvironment; } else { // the global scope of a js file does not see the instantiating component - qDebug() << "ast path length: " << astPath.size(); - if (astPath.size() != 0) { + if (astPath.size() > 0) { // add scope chains for all components that source this document foreach (Document::Ptr otherDoc, _docs) { if (otherDoc->bind()->includedScripts().contains(doc->fileName())) { @@ -91,20 +71,9 @@ void Link::scopeChainAt(Document::Ptr doc, const QList<Node *> &astPath) scopeChain.jsScopes += bind->rootObjectValue(); } - for (int i = qmlScopeObjectIndex + 1; i < astPath.size(); ++i) { - Node *node = astPath.at(i); - - if (FunctionDeclaration *fun = cast<FunctionDeclaration *>(node)) { - ObjectValue *activation = engine()->newObject(/*prototype = */ 0); - for (FormalParameterList *it = fun->formals; it; it = it->next) { - if (it->name) - activation->setProperty(it->name->asString(), engine()->undefinedValue()); - } - scopeChain.jsScopes += activation; - } - } - - scopeChain.update(); + ScopeBuilder scopeBuilder(doc, _context); + foreach (Node *node, astPath) + scopeBuilder.push(node); } void Link::makeComponentChain( diff --git a/src/libs/qmljs/qmljsscopebuilder.cpp b/src/libs/qmljs/qmljsscopebuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4f3f6fa1233145143957465c716849d5693aea12 --- /dev/null +++ b/src/libs/qmljs/qmljsscopebuilder.cpp @@ -0,0 +1,129 @@ +#include "qmljsscopebuilder.h" + +#include "qmljsbind.h" +#include "qmljsinterpreter.h" +#include "qmljsevaluate.h" +#include "parser/qmljsast_p.h" + +using namespace QmlJS; +using namespace QmlJS::Interpreter; +using namespace QmlJS::AST; + +ScopeBuilder::ScopeBuilder(Document::Ptr doc, Interpreter::Context *context) + : _doc(doc) + , _context(context) +{ +} + +ScopeBuilder::~ScopeBuilder() +{ +} + +void ScopeBuilder::push(AST::Node *node) +{ + _nodes += node; + + // QML scope object + Node *qmlObject = cast<UiObjectDefinition *>(node); + if (! qmlObject) + qmlObject = cast<UiObjectBinding *>(node); + if (qmlObject) + setQmlScopeObject(qmlObject); + + // JS scopes + if (FunctionDeclaration *fun = cast<FunctionDeclaration *>(node)) { + ObjectValue *activation = _context->engine()->newObject(/*prototype = */ 0); + for (FormalParameterList *it = fun->formals; it; it = it->next) { + if (it->name) + activation->setProperty(it->name->asString(), _context->engine()->undefinedValue()); + } + _context->scopeChain().jsScopes += activation; + } + + _context->scopeChain().update(); +} + +void ScopeBuilder::pop() +{ + Node *toRemove = _nodes.last(); + _nodes.removeLast(); + + // JS scopes + if (cast<FunctionDeclaration *>(toRemove)) + _context->scopeChain().jsScopes.removeLast(); + + // QML scope object + if (! _nodes.isEmpty() + && (cast<UiObjectDefinition *>(toRemove) || cast<UiObjectBinding *>(toRemove))) + setQmlScopeObject(_nodes.last()); + + _context->scopeChain().update(); +} + +void ScopeBuilder::setQmlScopeObject(Node *node) +{ + ScopeChain &scopeChain = _context->scopeChain(); + + scopeChain.qmlScopeObjects.clear(); + + const ObjectValue *scopeObject = _doc->bind()->findQmlObject(node); + if (scopeObject) { + if (scopeObject != scopeChain.qmlComponentScope.rootObject) + scopeChain.qmlScopeObjects += scopeObject; + } + +#ifndef NO_DECLARATIVE_BACKEND + // check if the object has a Qt.ListElement ancestor + const ObjectValue *prototype = scopeObject->prototype(_context); + while (prototype) { + if (const QmlObjectValue *qmlMetaObject = dynamic_cast<const QmlObjectValue *>(prototype)) { + // ### Also check for Qt package. Involves changes in QmlObjectValue. + if (qmlMetaObject->qmlTypeName() == QLatin1String("ListElement")) { + scopeChain.qmlScopeObjects.clear(); + break; + } + } + prototype = prototype->prototype(_context); + } + + // check if the object has a Qt.PropertyChanges ancestor + prototype = scopeObject->prototype(_context); + while (prototype) { + if (const QmlObjectValue *qmlMetaObject = dynamic_cast<const QmlObjectValue *>(prototype)) { + // ### Also check for Qt package. Involves changes in QmlObjectValue. + if (qmlMetaObject->qmlTypeName() == QLatin1String("PropertyChanges")) + break; + } + prototype = prototype->prototype(_context); + } + // find the target script binding + if (prototype) { + UiObjectInitializer *initializer = 0; + if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(node)) + initializer = definition->initializer; + if (UiObjectBinding *binding = cast<UiObjectBinding *>(node)) + initializer = binding->initializer; + if (initializer) { + for (UiObjectMemberList *m = initializer->members; m; m = m->next) { + if (UiScriptBinding *scriptBinding = cast<UiScriptBinding *>(m->member)) { + if (scriptBinding->qualifiedId + && scriptBinding->qualifiedId->name->asString() == QLatin1String("target") + && ! scriptBinding->qualifiedId->next) { + // ### make Evaluate understand statements. + if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(scriptBinding->statement)) { + Evaluate evaluator(_context); + const Value *targetValue = evaluator(expStmt->expression); + + if (const ObjectValue *target = value_cast<const ObjectValue *>(targetValue)) { + scopeChain.qmlScopeObjects.prepend(target); + } else { + scopeChain.qmlScopeObjects.clear(); + } + } + } + } + } + } + } +#endif +} diff --git a/src/libs/qmljs/qmljsscopebuilder.h b/src/libs/qmljs/qmljsscopebuilder.h new file mode 100644 index 0000000000000000000000000000000000000000..cec9268dcf57ea0d7f2c490e4b9a0bb900232df2 --- /dev/null +++ b/src/libs/qmljs/qmljsscopebuilder.h @@ -0,0 +1,37 @@ +#ifndef QMLJSSCOPEBUILDER_H +#define QMLJSSCOPEBUILDER_H + +#include <qmljs/qmljsdocument.h> + +#include <QtCore/QList> + +namespace QmlJS { + +namespace AST { + class Node; +} + +namespace Interpreter { + class Context; +} + +class ScopeBuilder +{ +public: + ScopeBuilder(Document::Ptr doc, Interpreter::Context *context); + ~ScopeBuilder(); + + void push(AST::Node *node); + void pop(); + +private: + void setQmlScopeObject(AST::Node *node); + + Document::Ptr _doc; + Interpreter::Context *_context; + QList<AST::Node *> _nodes; +}; + +} // namespace QmlJS + +#endif // QMLJSSCOPEBUILDER_H