Search for element declarations in discovered available files.

R=brianwilkerson@google.com

Change-Id: Idb8d4528aa1edffe60db63067e3d42fd89058b6b
Reviewed-on: https://dart-review.googlesource.com/57700
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/search/search_domain.dart b/pkg/analysis_server/lib/src/search/search_domain.dart
index d3ffa60..ab94d11 100644
--- a/pkg/analysis_server/lib/src/search/search_domain.dart
+++ b/pkg/analysis_server/lib/src/search/search_domain.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:collection';
 
 import 'package:analysis_server/protocol/protocol_constants.dart';
 import 'package:analysis_server/src/analysis_server.dart';
@@ -144,7 +145,7 @@
       }
     }
 
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     var declarations = <search.Declaration>[];
 
     protocol.ElementKind getElementKind(search.DeclarationKind kind) {
@@ -209,7 +210,7 @@
     }).toList();
 
     server.sendResponse(new protocol.SearchGetElementDeclarationsResult(
-            elementDeclarations, files)
+            elementDeclarations, files.toList())
         .toResponse(request.id));
   }
 
diff --git a/pkg/analysis_server/test/search/declarations_test.dart b/pkg/analysis_server/test/search/declarations_test.dart
index 71bf567..ab58b70 100644
--- a/pkg/analysis_server/test/search/declarations_test.dart
+++ b/pkg/analysis_server/test/search/declarations_test.dart
@@ -85,15 +85,15 @@
 ''').path;
 
     // Limit to exactly one file.
-    await _getDeclarations(pattern: r'[A-D]', maxResults: 2);
+    await _getDeclarations(pattern: r'^[A-D]$', maxResults: 2);
     expect(declarationsResult.declarations, hasLength(2));
 
     // Limit in the middle of the second file.
-    await _getDeclarations(pattern: r'[A-D]', maxResults: 3);
+    await _getDeclarations(pattern: r'^[A-D]$', maxResults: 3);
     expect(declarationsResult.declarations, hasLength(3));
 
     // No limit.
-    await _getDeclarations(pattern: r'[A-D]');
+    await _getDeclarations(pattern: r'^[A-D]$');
     expect(declarationsResult.declarations, hasLength(4));
   }
 
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 23139b8..e4640fb 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -599,6 +599,15 @@
     _createKernelDriver();
   }
 
+  /**
+   * Return a [Future] that completes when discovery of all files that are
+   * potentially available is done, so that they are included in [knownFiles].
+   */
+  Future<void> discoverAvailableFiles() {
+    _discoverAvailableFiles();
+    return _discoverAvailableFilesTask.completer.future;
+  }
+
   @override
   void dispose() {
     _scheduler.remove(this);
@@ -2259,6 +2268,7 @@
   static const int _MS_WORK_INTERVAL = 5;
 
   final AnalysisDriver driver;
+  final Completer<void> completer = new Completer<void>();
 
   bool isCompleted = false;
 
@@ -2309,6 +2319,7 @@
     isCompleted = true;
     folderIterator = null;
     files = null;
+    completer.complete();
   }
 
   void _appendFilesRecursively(Folder folder) {
diff --git a/pkg/analyzer/lib/src/dart/analysis/search.dart b/pkg/analyzer/lib/src/dart/analysis/search.dart
index a54df1f..1307034 100644
--- a/pkg/analyzer/lib/src/dart/analysis/search.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/search.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:collection';
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
@@ -113,11 +114,9 @@
    * declarations.
    *
    * The path of each file with at least one declaration is added to [files].
-   * The list is for searched, there might be duplicates, but this is OK,
-   * we just want reduce amount of data, not to make it absolute minimum.
    */
   Future<List<Declaration>> declarations(
-      RegExp regExp, int maxResults, List<String> files,
+      RegExp regExp, int maxResults, LinkedHashSet<String> files,
       {String onlyForFile}) async {
     List<Declaration> declarations = <Declaration>[];
 
@@ -139,11 +138,19 @@
       }
     }
 
+    for (String path in _driver.addedFiles) {
+      _driver.fsState.getFileForPath(path);
+    }
+    await _driver.discoverAvailableFiles();
+
     try {
-      for (String path in _driver.addedFiles) {
+      for (String path in _driver.knownFiles) {
         if (onlyForFile != null && path != onlyForFile) {
           continue;
         }
+        if (files.contains(path)) {
+          continue;
+        }
 
         FileState file = _driver.fsState.getFileForPath(path);
         int fileIndex;
@@ -166,6 +173,7 @@
             fileIndex = files.length;
             files.add(file.path);
           }
+
           var location = file.lineInfo.getLocation(offset);
           declarations.add(new Declaration(
               fileIndex,
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index cb583d2..d3cfbf9 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -1107,6 +1107,35 @@
     expect(session2, isNot(session1));
   }
 
+  test_discoverAvailableFiles() async {
+    var t = _p('/test/lib/test.dart');
+    var a1 = _p('/aaa/lib/a1.dart');
+    var a2 = _p('/aaa/lib/src/a2.dart');
+    var b = _p('/bbb/lib/b.dart');
+    var c = _p('/ccc/lib/c.dart');
+
+    provider.newFile(t, 'class T {}');
+    provider.newFile(a1, 'class A1 {}');
+    provider.newFile(a2, 'class A2 {}');
+    provider.newFile(b, 'class B {}');
+    provider.newFile(c, 'class C {}');
+
+    driver.addFile(t);
+    // Don't add a1.dart, a2.dart, or b.dart - they should be discovered.
+    // And c.dart is not in .packages, so should not be discovered.
+
+    await driver.discoverAvailableFiles();
+
+    expect(driver.knownFiles, contains(t));
+    expect(driver.knownFiles, contains(a1));
+    expect(driver.knownFiles, contains(a2));
+    expect(driver.knownFiles, contains(b));
+    expect(driver.knownFiles, isNot(contains(c)));
+
+    // We call wait for discovery more than once.
+    await driver.discoverAvailableFiles();
+  }
+
   test_errors_uriDoesNotExist_export() async {
     addTestFile(r'''
 export 'foo.dart';
diff --git a/pkg/analyzer/test/src/dart/analysis/search_test.dart b/pkg/analyzer/test/src/dart/analysis/search_test.dart
index 52cadae..45c3fbd 100644
--- a/pkg/analyzer/test/src/dart/analysis/search_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/search_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:collection';
 
 import 'package:analyzer/dart/ast/ast.dart' hide Declaration;
 import 'package:analyzer/dart/ast/standard_resolution_map.dart';
@@ -109,7 +110,7 @@
   void m() {}
 }
 ''');
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     List<Declaration> declarations =
         await driver.search.declarations(null, null, files);
     _assertHasDeclaration(declarations, 'C', DeclarationKind.CLASS,
@@ -128,13 +129,52 @@
         offset: 83, codeOffset: 78, codeLength: 11, className: 'C');
   }
 
+  test_declarations_discoverAvailable() async {
+    var t = _p('/test/lib/t.dart');
+    var a = _p('/aaa/lib/a.dart');
+    var b = _p('/bbb/lib/b.dart');
+    var c = _p('/ccc/lib/c.dart');
+
+    provider.newFile(t, 'class T {}');
+    provider.newFile(a, 'class A {}');
+    provider.newFile(b, 'class B {}');
+    provider.newFile(c, 'class C {}');
+
+    driver.addFile(t);
+
+    var files = new LinkedHashSet<String>();
+    var declarations = await driver.search.declarations(null, null, files);
+    _assertHasDeclaration(declarations, 'T', DeclarationKind.CLASS);
+    _assertHasDeclaration(declarations, 'A', DeclarationKind.CLASS);
+    _assertHasDeclaration(declarations, 'B', DeclarationKind.CLASS);
+    _assertNoDeclaration(declarations, 'C');
+  }
+
+  test_declarations_duplicateFile() async {
+    var a = _p('/test/lib/a.dart');
+    var b = _p('/test/lib/b.dart');
+
+    provider.newFile(a, 'class A {}');
+    provider.newFile(b, 'class B {}');
+
+    driver.addFile(a);
+    driver.addFile(b);
+
+    var files = new LinkedHashSet<String>();
+    files.add(b);
+
+    var declarations = await driver.search.declarations(null, null, files);
+    _assertHasDeclaration(declarations, 'A', DeclarationKind.CLASS);
+    _assertNoDeclaration(declarations, 'B');
+  }
+
   test_declarations_enum() async {
     await _resolveTestUnit('''
 enum E {
   a, bb, ccc
 }
 ''');
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     List<Declaration> declarations =
         await driver.search.declarations(null, null, files);
     _assertHasDeclaration(declarations, 'E', DeclarationKind.ENUM,
@@ -153,7 +193,7 @@
 class B {}
 class C {}
 ''');
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     List<Declaration> declarations =
         await driver.search.declarations(null, 2, files);
     expect(declarations, hasLength(2));
@@ -168,7 +208,7 @@
     driver.addFile(a);
     driver.addFile(b);
 
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     List<Declaration> declarations =
         await driver.search.declarations(null, null, files, onlyForFile: b);
 
@@ -188,7 +228,7 @@
 typedef F(int a);
 typedef T F2<T, U>(U a);
 ''');
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     List<Declaration> declarations =
         await driver.search.declarations(null, null, files);
 
@@ -228,7 +268,7 @@
 void f3(bool Function(int a, String b) c) {}
 void f4(bool Function(int, String) a) {}
 ''');
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     List<Declaration> declarations =
         await driver.search.declarations(null, null, files);
 
@@ -259,7 +299,7 @@
   void m3<U1, U2>(Map<Map<T2, U2>, Map<U1, T>> a) {}
 }
 ''');
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     List<Declaration> declarations =
         await driver.search.declarations(null, null, files);
 
@@ -288,7 +328,7 @@
 class C {}
 class D {}
 ''');
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     List<Declaration> declarations =
         await driver.search.declarations(new RegExp(r'[A-C]'), null, files);
     _assertHasDeclaration(declarations, 'A', DeclarationKind.CLASS);
@@ -306,7 +346,7 @@
 typedef void tf1();
 typedef tf2<T> = int Function<S>(T tp, S sp);
 ''');
-    var files = <String>[];
+    var files = new LinkedHashSet<String>();
     List<Declaration> declarations =
         await driver.search.declarations(null, null, files);
     _assertHasDeclaration(declarations, 'g', DeclarationKind.GETTER,