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 "qmljsscopechain.h"
27 #include "qmljsbind.h"
28 #include "qmljsevaluate.h"
29 #include "qmljsmodelmanagerinterface.h"
30 #include "parser/qmljsengine_p.h"
31
32 #include <QRegularExpression>
33
34 using namespace QmlJS;
35
36 /*!
37 \class QmlJS::ScopeChain
38 \brief The ScopeChain class describes the scopes used for global lookup in
39 a specific location.
40 \sa Document Context ScopeBuilder
41
42 A ScopeChain is used to perform global lookup with the lookup() function and
43 to access information about the enclosing scopes.
44
45 Once constructed for a Document in a Context it represents the root scope of
46 that Document. From there, a ScopeBuilder can be used to push and pop scopes
47 corresponding to functions, object definitions, etc.
48
49 It is an error to use the same ScopeChain from multiple threads; use a copy.
50 Copying is cheap. Initial construction is currently expensive.
51
52 When a QmlJSEditor::QmlJSEditorDocument is available, there's no need to
53 construct a new ScopeChain. Instead use
54 QmlJSEditorDocument::semanticInfo()::scopeChain().
55 */
56
QmlComponentChain(const Document::Ptr & document)57 QmlComponentChain::QmlComponentChain(const Document::Ptr &document)
58 : m_document(document)
59 {
60 }
61
~QmlComponentChain()62 QmlComponentChain::~QmlComponentChain()
63 {
64 qDeleteAll(m_instantiatingComponents);
65 }
66
document() const67 Document::Ptr QmlComponentChain::document() const
68 {
69 return m_document;
70 }
71
instantiatingComponents() const72 QList<const QmlComponentChain *> QmlComponentChain::instantiatingComponents() const
73 {
74 return m_instantiatingComponents;
75 }
76
idScope() const77 const ObjectValue *QmlComponentChain::idScope() const
78 {
79 if (!m_document)
80 return nullptr;
81 return m_document->bind()->idEnvironment();
82 }
83
rootObjectScope() const84 const ObjectValue *QmlComponentChain::rootObjectScope() const
85 {
86 if (!m_document)
87 return nullptr;
88 return m_document->bind()->rootObjectValue();
89 }
90
addInstantiatingComponent(const QmlComponentChain * component)91 void QmlComponentChain::addInstantiatingComponent(const QmlComponentChain *component)
92 {
93 m_instantiatingComponents.append(component);
94 }
95
96
ScopeChain(const Document::Ptr & document,const ContextPtr & context)97 ScopeChain::ScopeChain(const Document::Ptr &document, const ContextPtr &context)
98 : m_document(document)
99 , m_context(context)
100 , m_globalScope(nullptr)
101 , m_cppContextProperties(nullptr)
102 , m_qmlTypes(nullptr)
103 , m_jsImports(nullptr)
104 , m_modified(false)
105 {
106 initializeRootScope();
107 }
108
document() const109 Document::Ptr ScopeChain::document() const
110 {
111 return m_document;
112 }
113
context() const114 const ContextPtr &ScopeChain::context() const
115 {
116 return m_context;
117 }
118
lookup(const QString & name,const ObjectValue ** foundInScope) const119 const Value * ScopeChain::lookup(const QString &name, const ObjectValue **foundInScope) const
120 {
121 QList<const ObjectValue *> scopes = all();
122 for (int index = scopes.size() - 1; index != -1; --index) {
123 const ObjectValue *scope = scopes.at(index);
124
125 if (const Value *member = scope->lookupMember(name, m_context)) {
126 if (foundInScope)
127 *foundInScope = scope;
128 return member;
129 }
130 }
131
132 if (foundInScope)
133 *foundInScope = nullptr;
134
135 // we're confident to implement global lookup correctly, so return 'undefined'
136 return m_context->valueOwner()->undefinedValue();
137 }
138
evaluate(AST::Node * node) const139 const Value *ScopeChain::evaluate(AST::Node *node) const
140 {
141 Evaluate evaluator(this);
142 return evaluator(node);
143 }
144
globalScope() const145 const ObjectValue *ScopeChain::globalScope() const
146 {
147 return m_globalScope;
148 }
149
setGlobalScope(const ObjectValue * globalScope)150 void ScopeChain::setGlobalScope(const ObjectValue *globalScope)
151 {
152 m_modified = true;
153 m_globalScope = globalScope;
154 }
155
cppContextProperties() const156 const ObjectValue *ScopeChain::cppContextProperties() const
157 {
158 return m_cppContextProperties;
159 }
160
setCppContextProperties(const ObjectValue * cppContextProperties)161 void ScopeChain::setCppContextProperties(const ObjectValue *cppContextProperties)
162 {
163 m_modified = true;
164 m_cppContextProperties = cppContextProperties;
165 }
166
qmlComponentChain() const167 QSharedPointer<const QmlComponentChain> ScopeChain::qmlComponentChain() const
168 {
169 return m_qmlComponentScope;
170 }
171
setQmlComponentChain(const QSharedPointer<const QmlComponentChain> & qmlComponentChain)172 void ScopeChain::setQmlComponentChain(const QSharedPointer<const QmlComponentChain> &qmlComponentChain)
173 {
174 m_modified = true;
175 m_qmlComponentScope = qmlComponentChain;
176 }
177
qmlScopeObjects() const178 QList<const ObjectValue *> ScopeChain::qmlScopeObjects() const
179 {
180 return m_qmlScopeObjects;
181 }
182
setQmlScopeObjects(const QList<const ObjectValue * > & qmlScopeObjects)183 void ScopeChain::setQmlScopeObjects(const QList<const ObjectValue *> &qmlScopeObjects)
184 {
185 m_modified = true;
186 m_qmlScopeObjects = qmlScopeObjects;
187 }
188
qmlTypes() const189 const TypeScope *ScopeChain::qmlTypes() const
190 {
191 return m_qmlTypes;
192 }
193
setQmlTypes(const TypeScope * qmlTypes)194 void ScopeChain::setQmlTypes(const TypeScope *qmlTypes)
195 {
196 m_modified = true;
197 m_qmlTypes = qmlTypes;
198 }
199
jsImports() const200 const JSImportScope *ScopeChain::jsImports() const
201 {
202 return m_jsImports;
203 }
204
setJsImports(const JSImportScope * jsImports)205 void ScopeChain::setJsImports(const JSImportScope *jsImports)
206 {
207 m_modified = true;
208 m_jsImports = jsImports;
209 }
210
jsScopes() const211 QList<const ObjectValue *> ScopeChain::jsScopes() const
212 {
213 return m_jsScopes;
214 }
215
setJsScopes(const QList<const ObjectValue * > & jsScopes)216 void ScopeChain::setJsScopes(const QList<const ObjectValue *> &jsScopes)
217 {
218 m_modified = true;
219 m_jsScopes = jsScopes;
220 }
221
appendJsScope(const ObjectValue * scope)222 void ScopeChain::appendJsScope(const ObjectValue *scope)
223 {
224 m_modified = true;
225 m_jsScopes += scope;
226 }
227
all() const228 QList<const ObjectValue *> ScopeChain::all() const
229 {
230 if (m_modified)
231 update();
232 return m_all;
233 }
234
collectScopes(const QmlComponentChain * chain,QList<const ObjectValue * > * target)235 static void collectScopes(const QmlComponentChain *chain, QList<const ObjectValue *> *target)
236 {
237 foreach (const QmlComponentChain *parent, chain->instantiatingComponents())
238 collectScopes(parent, target);
239
240 if (!chain->document())
241 return;
242
243 if (const ObjectValue *root = chain->rootObjectScope())
244 target->append(root);
245 if (const ObjectValue *ids = chain->idScope())
246 target->append(ids);
247 }
248
update() const249 void ScopeChain::update() const
250 {
251 m_modified = false;
252 m_all.clear();
253
254 m_all += m_globalScope;
255
256 if (m_cppContextProperties)
257 m_all += m_cppContextProperties;
258
259 // the root scope in js files doesn't see instantiating components
260 if (m_document->language() != Dialect::JavaScript || m_jsScopes.count() != 1) {
261 if (m_qmlComponentScope) {
262 foreach (const QmlComponentChain *parent, m_qmlComponentScope->instantiatingComponents())
263 collectScopes(parent, &m_all);
264 }
265 }
266
267 ObjectValue *root = nullptr;
268 ObjectValue *ids = nullptr;
269 if (m_qmlComponentScope && m_qmlComponentScope->document()) {
270 const Bind *bind = m_qmlComponentScope->document()->bind();
271 root = bind->rootObjectValue();
272 ids = bind->idEnvironment();
273 }
274
275 if (root && !m_qmlScopeObjects.contains(root))
276 m_all += root;
277 m_all += m_qmlScopeObjects;
278 if (ids)
279 m_all += ids;
280 if (m_qmlTypes)
281 m_all += m_qmlTypes;
282 if (m_jsImports)
283 m_all += m_jsImports;
284 m_all += m_jsScopes;
285 }
286
addInstantiatingComponents(ContextPtr context,QmlComponentChain * chain)287 static void addInstantiatingComponents(ContextPtr context, QmlComponentChain *chain)
288 {
289 const QRegularExpression importCommentPattern(QLatin1String("@scope\\s+(.*)"));
290 foreach (const SourceLocation &commentLoc, chain->document()->engine()->comments()) {
291 const QString &comment = chain->document()->source().mid(commentLoc.begin(), commentLoc.length);
292
293 // find all @scope annotations
294 QStringList additionalScopes;
295 int lastOffset = -1;
296 QRegularExpressionMatch match;
297 forever {
298 match = importCommentPattern.match(comment, lastOffset + 1);
299 lastOffset = match.capturedStart();
300 if (lastOffset == -1)
301 break;
302 additionalScopes << QFileInfo(chain->document()->path() + QLatin1Char('/') + match.captured(1).trimmed()).absoluteFilePath();
303 }
304
305 foreach (const QmlComponentChain *c, chain->instantiatingComponents())
306 additionalScopes.removeAll(c->document()->fileName());
307
308 foreach (const QString &scope, additionalScopes) {
309 Document::Ptr doc = context->snapshot().document(scope);
310 if (doc) {
311 QmlComponentChain *ch = new QmlComponentChain(doc);
312 chain->addInstantiatingComponent(ch);
313 addInstantiatingComponents(context, ch);
314 }
315 }
316 }
317 }
318
initializeRootScope()319 void ScopeChain::initializeRootScope()
320 {
321 ValueOwner *valueOwner = m_context->valueOwner();
322 const Snapshot &snapshot = m_context->snapshot();
323 Bind *bind = m_document->bind();
324
325 m_globalScope = valueOwner->globalObject();
326 m_cppContextProperties = valueOwner->cppQmlTypes().cppContextProperties();
327
328 QHash<const Document *, QmlComponentChain *> componentScopes;
329 QmlComponentChain *chain = new QmlComponentChain(m_document);
330 m_qmlComponentScope = QSharedPointer<const QmlComponentChain>(chain);
331
332 if (const Imports *imports = m_context->imports(m_document.data())) {
333 m_qmlTypes = imports->typeScope();
334 m_jsImports = imports->jsImportScope();
335 }
336
337 if (m_document->qmlProgram()) {
338 componentScopes.insert(m_document.data(), chain);
339 makeComponentChain(chain, snapshot, &componentScopes);
340 } else {
341 // add scope chains for all components that import this file
342 // unless there's .pragma library
343 if (!m_document->bind()->isJsLibrary()) {
344 foreach (Document::Ptr otherDoc, snapshot) {
345 foreach (const ImportInfo &import, otherDoc->bind()->imports()) {
346 if ((import.type() == ImportType::File && m_document->fileName() == import.path())
347 || (import.type() == ImportType::QrcFile
348 && ModelManagerInterface::instance()->filesAtQrcPath(import.path())
349 .contains(m_document->fileName()))) {
350 QmlComponentChain *component = new QmlComponentChain(otherDoc);
351 componentScopes.insert(otherDoc.data(), component);
352 chain->addInstantiatingComponent(component);
353 makeComponentChain(component, snapshot, &componentScopes);
354 }
355 }
356 }
357 }
358
359 if (bind->rootObjectValue())
360 m_jsScopes += bind->rootObjectValue();
361 }
362 addInstantiatingComponents(m_context, chain);
363 m_modified = true;
364 }
365
makeComponentChain(QmlComponentChain * target,const Snapshot & snapshot,QHash<const Document *,QmlComponentChain * > * components)366 void ScopeChain::makeComponentChain(
367 QmlComponentChain *target,
368 const Snapshot &snapshot,
369 QHash<const Document *, QmlComponentChain *> *components)
370 {
371 Document::Ptr doc = target->document();
372 if (!doc->qmlProgram())
373 return;
374
375 const Bind *bind = doc->bind();
376
377 // add scopes for all components instantiating this one
378 foreach (Document::Ptr otherDoc, snapshot) {
379 if (otherDoc == doc)
380 continue;
381 if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), m_context)) {
382 if (!components->contains(otherDoc.data())) {
383 QmlComponentChain *component = new QmlComponentChain(otherDoc);
384 components->insert(otherDoc.data(), component);
385 target->addInstantiatingComponent(component);
386
387 makeComponentChain(component, snapshot, components);
388 }
389 }
390 }
391 }
392