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