OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qmljseditor / qmljsfindreferences.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
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.
18 **
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.
22 **
23 ** Other Usage
24 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "qmljsfindreferences.h"
34
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>
44
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>
52
53 #include "qmljseditorconstants.h"
54
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>
62
63 #include <functional>
64
65 using namespace QmlJS;
66 using namespace QmlJS::Interpreter;
67 using namespace QmlJS::AST;
68 using namespace QmlJSEditor;
69
70 namespace {
71
72 // ### These visitors could be useful in general
73
74 class FindUsages: protected Visitor
75 {
76 public:
77     typedef QList<AST::SourceLocation> Result;
78
79     FindUsages(Document::Ptr doc, const Snapshot &snapshot, Context *context)
80         : _doc(doc)
81         , _snapshot(snapshot)
82         , _context(context)
83         , _builder(context, doc, snapshot)
84     {
85     }
86
87     Result operator()(const QString &name, const ObjectValue *scope)
88     {
89         _name = name;
90         _scope = scope;
91         _usages.clear();
92         if (_doc)
93             Node::accept(_doc->ast(), this);
94         return _usages;
95     }
96
97 protected:
98     void accept(AST::Node *node)
99     { AST::Node::acceptChild(node, this); }
100
101     using Visitor::visit;
102
103     virtual bool visit(AST::UiPublicMember *node)
104     {
105         if (node->name
106                 && node->name->asString() == _name
107                 && _context->scopeChain().qmlScopeObjects.contains(_scope)) {
108             _usages.append(node->identifierToken);
109         }
110
111         return true;
112     }
113
114     virtual bool visit(AST::UiObjectDefinition *node)
115     {
116         _builder.push(node);
117         Node::accept(node->initializer, this);
118         _builder.pop();
119         return false;
120     }
121
122     virtual bool visit(AST::UiObjectBinding *node)
123     {
124         if (node->qualifiedId
125                 && !node->qualifiedId->next
126                 && node->qualifiedId->name->asString() == _name
127                 && checkQmlScope()) {
128             _usages.append(node->qualifiedId->identifierToken);
129         }
130
131         _builder.push(node);
132         Node::accept(node->initializer, this);
133         _builder.pop();
134         return false;
135     }
136
137     virtual bool visit(AST::UiScriptBinding *node)
138     {
139         if (node->qualifiedId
140                 && !node->qualifiedId->next
141                 && node->qualifiedId->name->asString() == _name
142                 && checkQmlScope()) {
143             _usages.append(node->qualifiedId->identifierToken);
144         }
145         return true;
146     }
147
148     virtual bool visit(AST::UiArrayBinding *node)
149     {
150         if (node->qualifiedId
151                 && !node->qualifiedId->next
152                 && node->qualifiedId->name->asString() == _name
153                 && checkQmlScope()) {
154             _usages.append(node->qualifiedId->identifierToken);
155         }
156         return true;
157     }
158
159     virtual bool visit(AST::IdentifierExpression *node)
160     {
161         if (!node->name || node->name->asString() != _name)
162             return false;
163
164         const ObjectValue *scope;
165         _context->lookup(_name, &scope);
166         if (!scope)
167             return false;
168         if (check(scope)) {
169             _usages.append(node->identifierToken);
170             return false;
171         }
172
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
175
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)
182             return false;
183
184         if (contains(chain.qmlComponentScope.data()))
185             _usages.append(node->identifierToken);
186
187         return false;
188     }
189
190     virtual bool visit(AST::FieldMemberExpression *node)
191     {
192         if (!node->name || node->name->asString() != _name)
193             return true;
194
195         Evaluate evaluate(_context);
196         const Value *lhsValue = evaluate(node->base);
197         if (!lhsValue)
198             return true;
199
200         if (check(lhsValue->asObjectValue())) // passing null is ok
201             _usages.append(node->identifierToken);
202
203         return true;
204     }
205
206     virtual bool visit(AST::FunctionDeclaration *node)
207     {
208         return visit(static_cast<FunctionExpression *>(node));
209     }
210
211     virtual bool visit(AST::FunctionExpression *node)
212     {
213         if (node->name && node->name->asString() == _name) {
214             if (checkLookup())
215                 _usages.append(node->identifierToken);
216         }
217         Node::accept(node->formals, this);
218         _builder.push(node);
219         Node::accept(node->body, this);
220         _builder.pop();
221         return false;
222     }
223
224     virtual bool visit(AST::VariableDeclaration *node)
225     {
226         if (node->name && node->name->asString() == _name) {
227             if (checkLookup())
228                 _usages.append(node->identifierToken);
229         }
230         return true;
231     }
232
233 private:
234     bool contains(const ScopeChain::QmlComponentChain *chain)
235     {
236         if (!chain || !chain->document)
237             return false;
238
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)) {
243             return check(root);
244         }
245
246         foreach (const ScopeChain::QmlComponentChain *parent, chain->instantiatingComponents) {
247             if (contains(parent))
248                 return true;
249         }
250         return false;
251     }
252
253     bool check(const ObjectValue *s)
254     {
255         if (!s)
256             return false;
257         const ObjectValue *definingObject;
258         s->lookupMember(_name, _context, &definingObject);
259         return definingObject == _scope;
260     }
261
262     bool checkQmlScope()
263     {
264         foreach (const ObjectValue *s, _context->scopeChain().qmlScopeObjects) {
265             if (check(s))
266                 return true;
267         }
268         return false;
269     }
270
271     bool checkLookup()
272     {
273         const ObjectValue *scope = 0;
274         _context->lookup(_name, &scope);
275         return check(scope);
276     }
277
278     Result _usages;
279
280     Document::Ptr _doc;
281     Snapshot _snapshot;
282     Context *_context;
283     ScopeBuilder _builder;
284
285     QString _name;
286     const ObjectValue *_scope;
287 };
288
289 class ScopeAstPath: protected Visitor
290 {
291 public:
292     ScopeAstPath(Document::Ptr doc)
293         : _doc(doc)
294     {
295     }
296
297     QList<Node *> operator()(quint32 offset)
298     {
299         _result.clear();
300         _offset = offset;
301         if (_doc)
302             Node::accept(_doc->ast(), this);
303         return _result;
304     }
305
306 protected:
307     void accept(AST::Node *node)
308     { AST::Node::acceptChild(node, this); }
309
310     using Visitor::visit;
311
312     virtual bool preVisit(Node *node)
313     {
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());
320         }
321         return true;
322     }
323
324     virtual bool visit(AST::UiObjectDefinition *node)
325     {
326         _result.append(node);
327         Node::accept(node->initializer, this);
328         return false;
329     }
330
331     virtual bool visit(AST::UiObjectBinding *node)
332     {
333         _result.append(node);
334         Node::accept(node->initializer, this);
335         return false;
336     }
337
338     virtual bool visit(AST::FunctionDeclaration *node)
339     {
340         return visit(static_cast<FunctionExpression *>(node));
341     }
342
343     virtual bool visit(AST::FunctionExpression *node)
344     {
345         Node::accept(node->formals, this);
346         _result.append(node);
347         Node::accept(node->body, this);
348         return false;
349     }
350
351 private:
352     bool containsOffset(SourceLocation start, SourceLocation end)
353     {
354         return _offset >= start.begin() && _offset <= end.end();
355     }
356
357     QList<Node *> _result;
358     Document::Ptr _doc;
359     quint32 _offset;
360 };
361
362 class FindTargetExpression: protected Visitor
363 {
364 public:
365     FindTargetExpression(Document::Ptr doc, Context *context)
366         : _doc(doc), _context(context)
367     {
368     }
369
370     void operator()(quint32 offset)
371     {
372         _name = QString::null;
373         _scope = 0;
374         _objectNode = 0;
375         _offset = offset;
376         if (_doc)
377             Node::accept(_doc->ast(), this);
378     }
379
380     QString name() const
381     { return _name; }
382
383     const ObjectValue *scope()
384     {
385         if (!_scope)
386             _context->lookup(_name, &_scope);
387         return _scope;
388     }
389
390 protected:
391     void accept(AST::Node *node)
392     { AST::Node::acceptChild(node, this); }
393
394     using Visitor::visit;
395
396     virtual bool preVisit(Node *node)
397     {
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());
404         }
405         return true;
406     }
407
408     virtual bool visit(IdentifierExpression *node)
409     {
410         if (containsOffset(node->identifierToken))
411             _name = node->name->asString();
412         return true;
413     }
414
415     virtual bool visit(FieldMemberExpression *node)
416     {
417         if (containsOffset(node->identifierToken)) {
418             setScope(node->base);
419             _name = node->name->asString();
420             return false;
421         }
422         return true;
423     }
424
425     virtual bool visit(UiScriptBinding *node)
426     {
427         return !checkBindingName(node->qualifiedId);
428     }
429
430     virtual bool visit(UiArrayBinding *node)
431     {
432         return !checkBindingName(node->qualifiedId);
433     }
434
435     virtual bool visit(UiObjectBinding *node)
436     {
437         if (!checkBindingName(node->qualifiedId)) {
438             Node *oldObjectNode = _objectNode;
439             _objectNode = node;
440             accept(node->initializer);
441             _objectNode = oldObjectNode;
442         }
443         return false;
444     }
445
446     virtual bool visit(UiObjectDefinition *node)
447     {
448         Node *oldObjectNode = _objectNode;
449         _objectNode = node;
450         accept(node->initializer);
451         _objectNode = oldObjectNode;
452         return false;
453     }
454
455     virtual bool visit(UiPublicMember *node)
456     {
457         if (containsOffset(node->identifierToken)) {
458             _scope = _doc->bind()->findQmlObject(_objectNode);
459             _name = node->name->asString();
460             return false;
461         }
462         return true;
463     }
464
465     virtual bool visit(FunctionDeclaration *node)
466     {
467         return visit(static_cast<FunctionExpression *>(node));
468     }
469
470     virtual bool visit(FunctionExpression *node)
471     {
472         if (containsOffset(node->identifierToken)) {
473             _name = node->name->asString();
474             return false;
475         }
476         return true;
477     }
478
479     virtual bool visit(VariableDeclaration *node)
480     {
481         if (containsOffset(node->identifierToken)) {
482             _name = node->name->asString();
483             return false;
484         }
485         return true;
486     }
487
488 private:
489     bool containsOffset(SourceLocation start, SourceLocation end)
490     {
491         return _offset >= start.begin() && _offset <= end.end();
492     }
493
494     bool containsOffset(SourceLocation loc)
495     {
496         return _offset >= loc.begin() && _offset <= loc.end();
497     }
498
499     bool checkBindingName(UiQualifiedId *id)
500     {
501         if (id && id->name && !id->next && containsOffset(id->identifierToken)) {
502             _scope = _doc->bind()->findQmlObject(_objectNode);
503             _name = id->name->asString();
504             return true;
505         }
506         return false;
507     }
508
509     void setScope(Node *node)
510     {
511         Evaluate evaluate(_context);
512         const Value *v = evaluate(node);
513         if (v)
514             _scope = v->asObjectValue();
515     }
516
517     QString _name;
518     const ObjectValue *_scope;
519     Node *_objectNode;
520     Document::Ptr _doc;
521     Context *_context;
522     quint32 _offset;
523 };
524
525 class ProcessFile: public std::unary_function<QString, QList<FindReferences::Usage> >
526 {
527     const Snapshot &snapshot;
528     const Context &context;
529     typedef FindReferences::Usage Usage;
530     QString name;
531     const ObjectValue *scope;
532
533 public:
534     ProcessFile(const Snapshot &snapshot,
535                 const Context &context,
536                 QString name,
537                 const ObjectValue *scope)
538         : snapshot(snapshot), context(context), name(name), scope(scope)
539     { }
540
541     QList<Usage> operator()(const QString &fileName)
542     {
543         QList<Usage> usages;
544
545         Document::Ptr doc = snapshot.document(fileName);
546         if (!doc)
547             return usages;
548
549         Context contextCopy(context);
550
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));
556
557         return usages;
558     }
559
560     static QString matchingLine(unsigned position, const QString &source)
561     {
562         int start = source.lastIndexOf(QLatin1Char('\n'), position);
563         start += 1;
564         int end = source.indexOf(QLatin1Char('\n'), position);
565
566         return source.mid(start, end - start);
567     }
568 };
569
570 class UpdateUI: public std::binary_function<QList<FindReferences::Usage> &, QList<FindReferences::Usage>, void>
571 {
572     typedef FindReferences::Usage Usage;
573     QFutureInterface<Usage> *future;
574
575 public:
576     UpdateUI(QFutureInterface<Usage> *future): future(future) {}
577
578     void operator()(QList<Usage> &, const QList<Usage> &usages)
579     {
580         foreach (const Usage &u, usages)
581             future->reportResult(u);
582
583         future->setProgressValue(future->progressValue() + 1);
584     }
585 };
586
587 } // end of anonymous namespace
588
589 FindReferences::FindReferences(QObject *parent)
590     : QObject(parent)
591     , _resultWindow(Find::SearchResultWindow::instance())
592 {
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()));
596 }
597
598 FindReferences::~FindReferences()
599 {
600 }
601
602 static void find_helper(QFutureInterface<FindReferences::Usage> &future,
603                         const ModelManagerInterface::WorkingCopy workingCopy,
604                         Snapshot snapshot,
605                         const QString fileName,
606                         quint32 offset)
607 {
608     // update snapshot from workingCopy to make sure it's up to date
609     // ### remove?
610     // ### this is a great candidate for map-reduce
611     QHashIterator< QString, QPair<QString, int> > it(workingCopy.all());
612     while (it.hasNext()) {
613         it.next();
614         Document::Ptr oldDoc = snapshot.document(it.key());
615         if (oldDoc && oldDoc->editorRevision() == it.value().second)
616             continue;
617
618         Document::Ptr newDoc = snapshot.documentFromSource(it.key(), it.value().first);
619         newDoc->parse();
620         snapshot.insert(newDoc);
621     }
622
623     // find the scope for the name we're searching
624     Context context;
625     Document::Ptr doc = snapshot.document(fileName);
626     if (!doc)
627         return;
628
629     Link link(&context, doc, snapshot, ModelManagerInterface::instance()->importPaths());
630     ScopeBuilder builder(&context, doc, snapshot);
631     ScopeAstPath astPath(doc);
632     builder.push(astPath(offset));
633
634     FindTargetExpression findTarget(doc, &context);
635     findTarget(offset);
636     const QString &name = findTarget.name();
637     if (name.isEmpty())
638         return;
639
640     const ObjectValue *scope = findTarget.scope();
641     if (!scope)
642         return;
643     scope->lookupMember(name, &context, &scope);
644     if (!scope)
645         return;
646
647     // report a dummy usage to indicate the search is starting
648     FindReferences::Usage usage;
649     future.reportResult(usage);
650
651     QStringList files;
652     foreach (const Document::Ptr &doc, snapshot) {
653         // ### skip files that don't contain the name token
654         files.append(doc->fileName());
655     }
656
657     future.setProgressRange(0, files.size());
658
659     ProcessFile process(snapshot, context, name, scope);
660     UpdateUI reduce(&future);
661
662     QtConcurrent::blockingMappedReduced<QList<FindReferences::Usage> > (files, process, reduce);
663
664     future.setProgressValue(files.size());
665 }
666
667 void FindReferences::findUsages(const QString &fileName, quint32 offset)
668 {
669     findAll_helper(fileName, offset);
670 }
671
672 void FindReferences::findAll_helper(const QString &fileName, quint32 offset)
673 {
674     ModelManagerInterface *modelManager = ModelManagerInterface::instance();
675
676
677     QFuture<Usage> result = QtConcurrent::run(
678                 &find_helper, modelManager->workingCopy(),
679                 modelManager->snapshot(), fileName, offset);
680     m_watcher.setFuture(result);
681 }
682
683 void FindReferences::displayResults(int first, int last)
684 {
685     // the first usage is always a dummy to indicate we now start searching
686     if (first == 0) {
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);
691
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()));
697
698         ++first;
699     }
700
701     for (int index = first; index != last; ++index) {
702         Usage result = m_watcher.future().resultAt(index);
703         _resultWindow->addResult(result.path,
704                                  result.line,
705                                  result.lineText,
706                                  result.col,
707                                  result.len);
708     }
709 }
710
711 void FindReferences::searchFinished()
712 {
713     _resultWindow->finishSearch();
714     emit changed();
715 }
716
717 void FindReferences::openEditor(const Find::SearchResultItem &item)
718 {
719     if (item.path.size() > 0) {
720         TextEditor::BaseTextEditorWidget::openEditorAt(item.path.first(), item.lineNumber, item.textMarkPos,
721                                                  QString(),
722                                                  Core::EditorManager::ModeSwitch);
723     } else {
724         Core::EditorManager::instance()->openEditor(item.text, QString(), Core::EditorManager::ModeSwitch);
725     }
726 }