1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of Qt Creator.
7**
8** Commercial License Usage
9** Licensees holding valid commercial Qt licenses may use this file in
10** accordance with the commercial license agreement provided with the
11** Software or, alternatively, in accordance with the terms contained in
12** a written agreement between you and The Qt Company. For licensing terms
13** and conditions see https://www.qt.io/terms-conditions. For further
14** information use the contact form at https://www.qt.io/contact-us.
15**
16** GNU General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU
18** General Public License version 3 as published by the Free Software
19** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20** included in the packaging of this file. Please review the following
21** information to ensure the GNU General Public License requirements will
22** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23**
24****************************************************************************/
25
26#include "qmljsscopebuilder.h"
27
28#include "qmljsbind.h"
29#include "qmljsevaluate.h"
30#include "qmljsscopechain.h"
31#include "qmljsutils.h"
32#include "parser/qmljsast_p.h"
33
34#include <utils/qtcassert.h>
35
36using namespace QmlJS;
37using namespace QmlJS::AST;
38
39ScopeBuilder::ScopeBuilder(ScopeChain *scopeChain)
40 : _scopeChain(scopeChain)
41{
42}
43
44ScopeBuilder::~ScopeBuilder()
45{
46}
47
48void ScopeBuilder::push(AST::Node *node)
49{
50 _nodes += node;
51
52 // QML scope object
53 Node *qmlObject = cast<UiObjectDefinition *>(node);
54 if (! qmlObject)
55 qmlObject = cast<UiObjectBinding *>(node);
56 if (qmlObject) {
57 // save the previous scope objects
58 _qmlScopeObjects.push(_scopeChain->qmlScopeObjects());
59 setQmlScopeObject(qmlObject);
60 }
61
62 // JS signal handler scope
63 if (UiScriptBinding *script = cast<UiScriptBinding *>(node)) {
64 QString name;
65 if (script->qualifiedId) {
66 name = script->qualifiedId->name.toString();
67 if (!_scopeChain->qmlScopeObjects().isEmpty()
68 && name.startsWith(QLatin1String("on"))
69 && !script->qualifiedId->next) {
70 const ObjectValue *owner = nullptr;
71 const Value *value = nullptr;
72 // try to find the name on the scope objects
73 foreach (const ObjectValue *scope, _scopeChain->qmlScopeObjects()) {
74 value = scope->lookupMember(name, _scopeChain->context(), &owner);
75 if (value)
76 break;
77 }
78 // signals defined in QML
79 if (const ASTSignal *astsig = value_cast<ASTSignal>(value)) {
80 _scopeChain->appendJsScope(astsig->bodyScope());
81 // signals defined in C++
82 } else if (const CppComponentValue *qmlObject = value_cast<CppComponentValue>(owner)) {
83 if (const ObjectValue *scope = qmlObject->signalScope(name))
84 _scopeChain->appendJsScope(scope);
85 }
86 }
87 }
88 }
89
90 // JS scopes
91 switch (node->kind) {
92 case Node::Kind_UiScriptBinding:
93 case Node::Kind_FunctionDeclaration:
94 case Node::Kind_FunctionExpression:
95 case Node::Kind_UiPublicMember:
96 {
97 ObjectValue *scope = _scopeChain->document()->bind()->findAttachedJSScope(node);
98 if (scope)
99 _scopeChain->appendJsScope(scope);
100 break;
101 }
102 default:
103 break;
104 }
105}
106
107void ScopeBuilder::push(const QList<AST::Node *> &nodes)
108{
109 foreach (Node *node, nodes)
110 push(node);
111}
112
113void ScopeBuilder::pop()
114{
115 Node *toRemove = _nodes.last();
116 _nodes.removeLast();
117
118 // JS scopes
119 switch (toRemove->kind) {
120 case Node::Kind_UiScriptBinding:
121 case Node::Kind_FunctionDeclaration:
122 case Node::Kind_FunctionExpression:
123 case Node::Kind_UiPublicMember:
124 {
125 ObjectValue *scope = _scopeChain->document()->bind()->findAttachedJSScope(toRemove);
126 if (scope) {
127 QList<const ObjectValue *> jsScopes = _scopeChain->jsScopes();
128 if (!jsScopes.isEmpty()) {
129 jsScopes.removeLast();
130 _scopeChain->setJsScopes(jsScopes);
131 }
132 }
133 break;
134 }
135 default:
136 break;
137 }
138
139 // QML scope object
140 if (cast<UiObjectDefinition *>(toRemove) || cast<UiObjectBinding *>(toRemove)) {
141 // restore the previous scope objects
142 QTC_ASSERT(!_qmlScopeObjects.isEmpty(), return);
143 _scopeChain->setQmlScopeObjects(_qmlScopeObjects.pop());
144 }
145}
146
147void ScopeBuilder::setQmlScopeObject(Node *node)
148{
149 QList<const ObjectValue *> qmlScopeObjects;
150 if (_scopeChain->document()->bind()->isGroupedPropertyBinding(node)) {
151 UiObjectDefinition *definition = cast<UiObjectDefinition *>(node);
152 if (!definition)
153 return;
154 const Value *v = scopeObjectLookup(definition->qualifiedTypeNameId);
155 if (!v)
156 return;
157 const ObjectValue *object = v->asObjectValue();
158 if (!object)
159 return;
160
161 qmlScopeObjects += object;
162 _scopeChain->setQmlScopeObjects(qmlScopeObjects);
163 return;
164 }
165
166 const ObjectValue *scopeObject = _scopeChain->document()->bind()->findQmlObject(node);
167 if (scopeObject)
168 qmlScopeObjects += scopeObject;
169 else
170 return; // Probably syntax errors, where we're working with a "recovered" AST.
171
172 // check if the object has a Qt.ListElement or Qt.Connections ancestor
173 // ### allow only signal bindings for Connections
174 PrototypeIterator iter(scopeObject, _scopeChain->context());
175 iter.next();
176 while (iter.hasNext()) {
177 const ObjectValue *prototype = iter.next();
178 if (const CppComponentValue *qmlMetaObject = value_cast<CppComponentValue>(prototype)) {
179 if ((qmlMetaObject->className() == QLatin1String("ListElement")
180 || qmlMetaObject->className() == QLatin1String("Connections")
181 ) && (qmlMetaObject->moduleName() == QLatin1String("Qt")
182 || qmlMetaObject->moduleName() == QLatin1String("QtQuick"))) {
183 qmlScopeObjects.clear();
184 break;
185 }
186 }
187 }
188
189 // check if the object has a Qt.PropertyChanges ancestor
190 const ObjectValue *prototype = scopeObject->prototype(_scopeChain->context());
191 prototype = isPropertyChangesObject(_scopeChain->context(), prototype);
192 // find the target script binding
193 if (prototype) {
194 UiObjectInitializer *initializer = initializerOfObject(node);
195 if (initializer) {
196 for (UiObjectMemberList *m = initializer->members; m; m = m->next) {
197 if (UiScriptBinding *scriptBinding = cast<UiScriptBinding *>(m->member)) {
198 if (scriptBinding->qualifiedId
199 && scriptBinding->qualifiedId->name == QLatin1String("target")
200 && ! scriptBinding->qualifiedId->next) {
201 Evaluate evaluator(_scopeChain);
202 const Value *targetValue = evaluator(scriptBinding->statement);
203
204 if (const ObjectValue *target = value_cast<ObjectValue>(targetValue))
205 qmlScopeObjects.prepend(target);
206 else
207 qmlScopeObjects.clear();
208 }
209 }
210 }
211 }
212 }
213
214 _scopeChain->setQmlScopeObjects(qmlScopeObjects);
215}
216
217const Value *ScopeBuilder::scopeObjectLookup(AST::UiQualifiedId *id)
218{
219 // do a name lookup on the scope objects
220 const Value *result = nullptr;
221 foreach (const ObjectValue *scopeObject, _scopeChain->qmlScopeObjects()) {
222 const ObjectValue *object = scopeObject;
223 for (UiQualifiedId *it = id; it; it = it->next) {
224 if (it->name.isEmpty())
225 return nullptr;
226 result = object->lookupMember(it->name.toString(), _scopeChain->context());
227 if (!result)
228 break;
229 if (it->next) {
230 object = result->asObjectValue();
231 if (!object) {
232 result = nullptr;
233 break;
234 }
235 }
236 }
237 if (result)
238 break;
239 }
240
241 return result;
242}
243
244
245const ObjectValue *ScopeBuilder::isPropertyChangesObject(const ContextPtr &context,
246 const ObjectValue *object)
247{
248 PrototypeIterator iter(object, context);
249 while (iter.hasNext()) {
250 const ObjectValue *prototype = iter.next();
251 if (const CppComponentValue *qmlMetaObject = value_cast<CppComponentValue>(prototype)) {
252 if (qmlMetaObject->className() == QLatin1String("PropertyChanges")
253 && (qmlMetaObject->moduleName() == QLatin1String("Qt")
254 || qmlMetaObject->moduleName() == QLatin1String("QtQuick")))
255 return prototype;
256 }
257 }
258 return nullptr;
259}
260