1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "qmljsfindreferences.h"
35 #include <texteditor/basetexteditor.h>
36 #include <texteditor/basefilefind.h>
37 #include <find/searchresultwindow.h>
38 #include <extensionsystem/pluginmanager.h>
39 #include <utils/filesearch.h>
40 #include <coreplugin/progressmanager/progressmanager.h>
41 #include <coreplugin/progressmanager/futureprogress.h>
42 #include <coreplugin/editormanager/editormanager.h>
43 #include <coreplugin/icore.h>
45 #include <qmljs/qmljsmodelmanagerinterface.h>
46 #include <qmljs/qmljsbind.h>
47 #include <qmljs/qmljslink.h>
48 #include <qmljs/qmljsevaluate.h>
49 #include <qmljs/qmljsscopebuilder.h>
50 #include <qmljs/parser/qmljsastvisitor_p.h>
51 #include <qmljs/parser/qmljsast_p.h>
53 #include "qmljseditorconstants.h"
55 #include <QtCore/QTime>
56 #include <QtCore/QTimer>
57 #include <QtCore/QtConcurrentRun>
58 #include <QtCore/QtConcurrentMap>
59 #include <QtCore/QDir>
60 #include <QtGui/QApplication>
61 #include <qtconcurrent/runextensions.h>
65 using namespace QmlJS;
66 using namespace QmlJS::Interpreter;
67 using namespace QmlJS::AST;
68 using namespace QmlJSEditor;
72 // ### These visitors could be useful in general
74 class FindUsages: protected Visitor
77 typedef QList<AST::SourceLocation> Result;
79 FindUsages(Document::Ptr doc, const Snapshot &snapshot, Context *context)
83 , _builder(context, doc, snapshot)
87 Result operator()(const QString &name, const ObjectValue *scope)
93 Node::accept(_doc->ast(), this);
98 void accept(AST::Node *node)
99 { AST::Node::acceptChild(node, this); }
101 using Visitor::visit;
103 virtual bool visit(AST::UiPublicMember *node)
106 && node->name->asString() == _name
107 && _context->scopeChain().qmlScopeObjects.contains(_scope)) {
108 _usages.append(node->identifierToken);
114 virtual bool visit(AST::UiObjectDefinition *node)
117 Node::accept(node->initializer, this);
122 virtual bool visit(AST::UiObjectBinding *node)
124 if (node->qualifiedId
125 && !node->qualifiedId->next
126 && node->qualifiedId->name->asString() == _name
127 && checkQmlScope()) {
128 _usages.append(node->qualifiedId->identifierToken);
132 Node::accept(node->initializer, this);
137 virtual bool visit(AST::UiScriptBinding *node)
139 if (node->qualifiedId
140 && !node->qualifiedId->next
141 && node->qualifiedId->name->asString() == _name
142 && checkQmlScope()) {
143 _usages.append(node->qualifiedId->identifierToken);
148 virtual bool visit(AST::UiArrayBinding *node)
150 if (node->qualifiedId
151 && !node->qualifiedId->next
152 && node->qualifiedId->name->asString() == _name
153 && checkQmlScope()) {
154 _usages.append(node->qualifiedId->identifierToken);
159 virtual bool visit(AST::IdentifierExpression *node)
161 if (!node->name || node->name->asString() != _name)
164 const ObjectValue *scope;
165 _context->lookup(_name, &scope);
169 _usages.append(node->identifierToken);
173 // the order of scopes in 'instantiatingComponents' is undefined,
174 // so it might still be a use - we just found a different value in a different scope first
176 // if scope is one of these, our match wasn't inside the instantiating components list
177 const ScopeChain &chain = _context->scopeChain();
178 if (chain.jsScopes.contains(scope)
179 || chain.qmlScopeObjects.contains(scope)
180 || chain.qmlTypes == scope
181 || chain.globalScope == scope)
184 if (contains(chain.qmlComponentScope.data()))
185 _usages.append(node->identifierToken);
190 virtual bool visit(AST::FieldMemberExpression *node)
192 if (!node->name || node->name->asString() != _name)
195 Evaluate evaluate(_context);
196 const Value *lhsValue = evaluate(node->base);
200 if (check(lhsValue->asObjectValue())) // passing null is ok
201 _usages.append(node->identifierToken);
206 virtual bool visit(AST::FunctionDeclaration *node)
208 return visit(static_cast<FunctionExpression *>(node));
211 virtual bool visit(AST::FunctionExpression *node)
213 if (node->name && node->name->asString() == _name) {
215 _usages.append(node->identifierToken);
217 Node::accept(node->formals, this);
219 Node::accept(node->body, this);
224 virtual bool visit(AST::VariableDeclaration *node)
226 if (node->name && node->name->asString() == _name) {
228 _usages.append(node->identifierToken);
234 bool contains(const ScopeChain::QmlComponentChain *chain)
236 if (!chain || !chain->document)
239 if (chain->document->bind()->idEnvironment()->property(_name, _context))
240 return chain->document->bind()->idEnvironment() == _scope;
241 const ObjectValue *root = chain->document->bind()->rootObjectValue();
242 if (root->property(_name, _context)) {
246 foreach (const ScopeChain::QmlComponentChain *parent, chain->instantiatingComponents) {
247 if (contains(parent))
253 bool check(const ObjectValue *s)
257 const ObjectValue *definingObject;
258 s->lookupMember(_name, _context, &definingObject);
259 return definingObject == _scope;
264 foreach (const ObjectValue *s, _context->scopeChain().qmlScopeObjects) {
273 const ObjectValue *scope = 0;
274 _context->lookup(_name, &scope);
283 ScopeBuilder _builder;
286 const ObjectValue *_scope;
289 class ScopeAstPath: protected Visitor
292 ScopeAstPath(Document::Ptr doc)
297 QList<Node *> operator()(quint32 offset)
302 Node::accept(_doc->ast(), this);
307 void accept(AST::Node *node)
308 { AST::Node::acceptChild(node, this); }
310 using Visitor::visit;
312 virtual bool preVisit(Node *node)
314 if (Statement *stmt = node->statementCast()) {
315 return containsOffset(stmt->firstSourceLocation(), stmt->lastSourceLocation());
316 } else if (ExpressionNode *exp = node->expressionCast()) {
317 return containsOffset(exp->firstSourceLocation(), exp->lastSourceLocation());
318 } else if (UiObjectMember *ui = node->uiObjectMemberCast()) {
319 return containsOffset(ui->firstSourceLocation(), ui->lastSourceLocation());
324 virtual bool visit(AST::UiObjectDefinition *node)
326 _result.append(node);
327 Node::accept(node->initializer, this);
331 virtual bool visit(AST::UiObjectBinding *node)
333 _result.append(node);
334 Node::accept(node->initializer, this);
338 virtual bool visit(AST::FunctionDeclaration *node)
340 return visit(static_cast<FunctionExpression *>(node));
343 virtual bool visit(AST::FunctionExpression *node)
345 Node::accept(node->formals, this);
346 _result.append(node);
347 Node::accept(node->body, this);
352 bool containsOffset(SourceLocation start, SourceLocation end)
354 return _offset >= start.begin() && _offset <= end.end();
357 QList<Node *> _result;
362 class FindTargetExpression: protected Visitor
365 FindTargetExpression(Document::Ptr doc, Context *context)
366 : _doc(doc), _context(context)
370 void operator()(quint32 offset)
372 _name = QString::null;
377 Node::accept(_doc->ast(), this);
383 const ObjectValue *scope()
386 _context->lookup(_name, &_scope);
391 void accept(AST::Node *node)
392 { AST::Node::acceptChild(node, this); }
394 using Visitor::visit;
396 virtual bool preVisit(Node *node)
398 if (Statement *stmt = node->statementCast()) {
399 return containsOffset(stmt->firstSourceLocation(), stmt->lastSourceLocation());
400 } else if (ExpressionNode *exp = node->expressionCast()) {
401 return containsOffset(exp->firstSourceLocation(), exp->lastSourceLocation());
402 } else if (UiObjectMember *ui = node->uiObjectMemberCast()) {
403 return containsOffset(ui->firstSourceLocation(), ui->lastSourceLocation());
408 virtual bool visit(IdentifierExpression *node)
410 if (containsOffset(node->identifierToken))
411 _name = node->name->asString();
415 virtual bool visit(FieldMemberExpression *node)
417 if (containsOffset(node->identifierToken)) {
418 setScope(node->base);
419 _name = node->name->asString();
425 virtual bool visit(UiScriptBinding *node)
427 return !checkBindingName(node->qualifiedId);
430 virtual bool visit(UiArrayBinding *node)
432 return !checkBindingName(node->qualifiedId);
435 virtual bool visit(UiObjectBinding *node)
437 if (!checkBindingName(node->qualifiedId)) {
438 Node *oldObjectNode = _objectNode;
440 accept(node->initializer);
441 _objectNode = oldObjectNode;
446 virtual bool visit(UiObjectDefinition *node)
448 Node *oldObjectNode = _objectNode;
450 accept(node->initializer);
451 _objectNode = oldObjectNode;
455 virtual bool visit(UiPublicMember *node)
457 if (containsOffset(node->identifierToken)) {
458 _scope = _doc->bind()->findQmlObject(_objectNode);
459 _name = node->name->asString();
465 virtual bool visit(FunctionDeclaration *node)
467 return visit(static_cast<FunctionExpression *>(node));
470 virtual bool visit(FunctionExpression *node)
472 if (containsOffset(node->identifierToken)) {
473 _name = node->name->asString();
479 virtual bool visit(VariableDeclaration *node)
481 if (containsOffset(node->identifierToken)) {
482 _name = node->name->asString();
489 bool containsOffset(SourceLocation start, SourceLocation end)
491 return _offset >= start.begin() && _offset <= end.end();
494 bool containsOffset(SourceLocation loc)
496 return _offset >= loc.begin() && _offset <= loc.end();
499 bool checkBindingName(UiQualifiedId *id)
501 if (id && id->name && !id->next && containsOffset(id->identifierToken)) {
502 _scope = _doc->bind()->findQmlObject(_objectNode);
503 _name = id->name->asString();
509 void setScope(Node *node)
511 Evaluate evaluate(_context);
512 const Value *v = evaluate(node);
514 _scope = v->asObjectValue();
518 const ObjectValue *_scope;
525 class ProcessFile: public std::unary_function<QString, QList<FindReferences::Usage> >
527 const Snapshot &snapshot;
528 const Context &context;
529 typedef FindReferences::Usage Usage;
531 const ObjectValue *scope;
534 ProcessFile(const Snapshot &snapshot,
535 const Context &context,
537 const ObjectValue *scope)
538 : snapshot(snapshot), context(context), name(name), scope(scope)
541 QList<Usage> operator()(const QString &fileName)
545 Document::Ptr doc = snapshot.document(fileName);
549 Context contextCopy(context);
551 // find all idenfifier expressions, try to resolve them and check if the result is in scope
552 FindUsages findUsages(doc, snapshot, &contextCopy);
553 FindUsages::Result results = findUsages(name, scope);
554 foreach (AST::SourceLocation loc, results)
555 usages.append(Usage(fileName, matchingLine(loc.offset, doc->source()), loc.startLine, loc.startColumn - 1, loc.length));
560 static QString matchingLine(unsigned position, const QString &source)
562 int start = source.lastIndexOf(QLatin1Char('\n'), position);
564 int end = source.indexOf(QLatin1Char('\n'), position);
566 return source.mid(start, end - start);
570 class UpdateUI: public std::binary_function<QList<FindReferences::Usage> &, QList<FindReferences::Usage>, void>
572 typedef FindReferences::Usage Usage;
573 QFutureInterface<Usage> *future;
576 UpdateUI(QFutureInterface<Usage> *future): future(future) {}
578 void operator()(QList<Usage> &, const QList<Usage> &usages)
580 foreach (const Usage &u, usages)
581 future->reportResult(u);
583 future->setProgressValue(future->progressValue() + 1);
587 } // end of anonymous namespace
589 FindReferences::FindReferences(QObject *parent)
591 , _resultWindow(Find::SearchResultWindow::instance())
593 m_watcher.setPendingResultsLimit(1);
594 connect(&m_watcher, SIGNAL(resultsReadyAt(int,int)), this, SLOT(displayResults(int,int)));
595 connect(&m_watcher, SIGNAL(finished()), this, SLOT(searchFinished()));
598 FindReferences::~FindReferences()
602 static void find_helper(QFutureInterface<FindReferences::Usage> &future,
603 const ModelManagerInterface::WorkingCopy workingCopy,
605 const QString fileName,
608 // update snapshot from workingCopy to make sure it's up to date
610 // ### this is a great candidate for map-reduce
611 QHashIterator< QString, QPair<QString, int> > it(workingCopy.all());
612 while (it.hasNext()) {
614 Document::Ptr oldDoc = snapshot.document(it.key());
615 if (oldDoc && oldDoc->editorRevision() == it.value().second)
618 Document::Ptr newDoc = snapshot.documentFromSource(it.key(), it.value().first);
620 snapshot.insert(newDoc);
623 // find the scope for the name we're searching
625 Document::Ptr doc = snapshot.document(fileName);
629 Link link(&context, doc, snapshot, ModelManagerInterface::instance()->importPaths());
630 ScopeBuilder builder(&context, doc, snapshot);
631 ScopeAstPath astPath(doc);
632 builder.push(astPath(offset));
634 FindTargetExpression findTarget(doc, &context);
636 const QString &name = findTarget.name();
640 const ObjectValue *scope = findTarget.scope();
643 scope->lookupMember(name, &context, &scope);
647 // report a dummy usage to indicate the search is starting
648 FindReferences::Usage usage;
649 future.reportResult(usage);
652 foreach (const Document::Ptr &doc, snapshot) {
653 // ### skip files that don't contain the name token
654 files.append(doc->fileName());
657 future.setProgressRange(0, files.size());
659 ProcessFile process(snapshot, context, name, scope);
660 UpdateUI reduce(&future);
662 QtConcurrent::blockingMappedReduced<QList<FindReferences::Usage> > (files, process, reduce);
664 future.setProgressValue(files.size());
667 void FindReferences::findUsages(const QString &fileName, quint32 offset)
669 findAll_helper(fileName, offset);
672 void FindReferences::findAll_helper(const QString &fileName, quint32 offset)
674 ModelManagerInterface *modelManager = ModelManagerInterface::instance();
677 QFuture<Usage> result = QtConcurrent::run(
678 &find_helper, modelManager->workingCopy(),
679 modelManager->snapshot(), fileName, offset);
680 m_watcher.setFuture(result);
683 void FindReferences::displayResults(int first, int last)
685 // the first usage is always a dummy to indicate we now start searching
687 Find::SearchResult *search = _resultWindow->startNewSearch(Find::SearchResultWindow::SearchOnly);
688 connect(search, SIGNAL(activated(Find::SearchResultItem)),
689 this, SLOT(openEditor(Find::SearchResultItem)));
690 _resultWindow->popup(true);
692 Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager();
693 Core::FutureProgress *progress = progressManager->addTask(
694 m_watcher.future(), tr("Searching"),
695 QmlJSEditor::Constants::TASK_SEARCH);
696 connect(progress, SIGNAL(clicked()), _resultWindow, SLOT(popup()));
701 for (int index = first; index != last; ++index) {
702 Usage result = m_watcher.future().resultAt(index);
703 _resultWindow->addResult(result.path,
711 void FindReferences::searchFinished()
713 _resultWindow->finishSearch();
717 void FindReferences::openEditor(const Find::SearchResultItem &item)
719 if (item.path.size() > 0) {
720 TextEditor::BaseTextEditorWidget::openEditorAt(item.path.first(), item.lineNumber, item.textMarkPos,
722 Core::EditorManager::ModeSwitch);
724 Core::EditorManager::instance()->openEditor(item.text, QString(), Core::EditorManager::ModeSwitch);