Discover available files before searching in known files.

Known files are only used for seaching top-level declarations.
But we already get something for user from it - we can give Quick Fix
for imports, even if the package to import is not used yet in the project.

R=brianwilkerson@google.com

Change-Id: Iaa6d7ad515325b1bad3e37e7c066c42df056c85c
Reviewed-on: https://dart-review.googlesource.com/56623
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 0d4084c..66005ce 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -201,6 +201,13 @@
   final _requestedFiles = <String, List<Completer<AnalysisResult>>>{};
 
   /**
+   * The task that discovers available files.  If this field is not `null`,
+   * and the task is not completed, it should be performed and completed
+   * before any name searching task.
+   */
+  _DiscoverAvailableFilesTask _discoverAvailableFilesTask;
+
+  /**
    * The list of tasks to compute files defining a class member name.
    */
   final _definingClassMemberNameTasks = <_FilesDefiningClassMemberNameTask>[];
@@ -478,6 +485,10 @@
     if (_requestedFiles.isNotEmpty) {
       return AnalysisDriverPriority.interactive;
     }
+    if (_discoverAvailableFilesTask != null &&
+        !_discoverAvailableFilesTask.isCompleted) {
+      return AnalysisDriverPriority.interactive;
+    }
     if (_definingClassMemberNameTasks.isNotEmpty ||
         _referencingNameTasks.isNotEmpty) {
       return AnalysisDriverPriority.interactive;
@@ -650,6 +661,7 @@
    * define a class member with the given [name].
    */
   Future<List<String>> getFilesDefiningClassMemberName(String name) {
+    _discoverAvailableFiles();
     var task = new _FilesDefiningClassMemberNameTask(this, name);
     _definingClassMemberNameTasks.add(task);
     _scheduler.notify(this);
@@ -661,6 +673,7 @@
    * reference the given external [name].
    */
   Future<List<String>> getFilesReferencingName(String name) {
+    _discoverAvailableFiles();
     var task = new _FilesReferencingNameTask(this, name);
     _referencingNameTasks.add(task);
     _scheduler.notify(this);
@@ -793,6 +806,7 @@
    */
   Future<List<TopLevelDeclarationInSource>> getTopLevelNameDeclarations(
       String name) {
+    _discoverAvailableFiles();
     var task = new _TopLevelNameDeclarationsTask(this, name);
     _topLevelNameDeclarationsTasks.add(task);
     _scheduler.notify(this);
@@ -967,6 +981,13 @@
       return;
     }
 
+    // Discover available files.
+    if (_discoverAvailableFilesTask != null &&
+        !_discoverAvailableFilesTask.isCompleted) {
+      _discoverAvailableFilesTask.perform();
+      return;
+    }
+
     // Compute files defining a name.
     if (_definingClassMemberNameTasks.isNotEmpty) {
       _FilesDefiningClassMemberNameTask task =
@@ -1417,6 +1438,14 @@
   }
 
   /**
+   * If this has not been done yet, schedule discovery of all files that are
+   * potentially available, so that they are included in [knownFiles].
+   */
+  void _discoverAvailableFiles() {
+    _discoverAvailableFilesTask ??= new _DiscoverAvailableFilesTask(this);
+  }
+
+  /**
    * Fill [_salt] with data.
    */
   void _fillSalt() {
@@ -2223,6 +2252,79 @@
 }
 
 /**
+ * Task that discovers all files that are available to the driver, and makes
+ * them known.
+ */
+class _DiscoverAvailableFilesTask {
+  static const int _MS_WORK_INTERVAL = 5;
+
+  final AnalysisDriver driver;
+
+  bool isCompleted = false;
+
+  Iterator<Folder> folderIterator;
+  List<String> files;
+  int fileIndex = 0;
+
+  _DiscoverAvailableFilesTask(this.driver);
+
+  /**
+   * Perform the next piece of work, and set [isCompleted] to `true` to
+   * indicate that the task is done, or keeps it `false` to indicate that the
+   * task should continue to be run.
+   */
+  void perform() {
+    // Prepare the iterator of package/lib folders.
+    if (folderIterator == null) {
+      var packageMap = driver._sourceFactory.packageMap;
+      if (packageMap != null) {
+        folderIterator = packageMap.values.expand((f) => f).iterator;
+        files = <String>[];
+      } else {
+        isCompleted = true;
+        return;
+      }
+    }
+
+    // List each package/lib folder recursively.
+    Stopwatch timer = new Stopwatch()..start();
+    while (folderIterator.moveNext()) {
+      if (timer.elapsedMilliseconds > _MS_WORK_INTERVAL) {
+        return;
+      }
+      var folder = folderIterator.current;
+      _appendFilesRecursively(folder);
+    }
+
+    // Get know files one by one.
+    while (fileIndex < files.length) {
+      if (timer.elapsedMilliseconds > _MS_WORK_INTERVAL) {
+        return;
+      }
+      var file = files[fileIndex++];
+      driver._fsState.getFileForPath(file);
+    }
+
+    // The task is done, clean up.
+    isCompleted = true;
+    folderIterator = null;
+    files = null;
+  }
+
+  void _appendFilesRecursively(Folder folder) {
+    try {
+      for (var child in folder.getChildren()) {
+        if (child is File) {
+          files.add(child.path);
+        } else if (child is Folder) {
+          _appendFilesRecursively(child);
+        }
+      }
+    } catch (_) {}
+  }
+}
+
+/**
  * Information about an exception and its context.
  */
 class _ExceptionState {
diff --git a/pkg/analyzer/test/src/dart/analysis/base.dart b/pkg/analyzer/test/src/dart/analysis/base.dart
index dacd732..373fd4b 100644
--- a/pkg/analyzer/test/src/dart/analysis/base.dart
+++ b/pkg/analyzer/test/src/dart/analysis/base.dart
@@ -94,7 +94,9 @@
           new DartUriResolver(sdk),
           generatedUriResolver,
           new PackageMapUriResolver(provider, <String, List<Folder>>{
-            'test': [provider.getFolder(testProject)]
+            'test': [provider.getFolder(testProject)],
+            'aaa': [provider.getFolder(_p('/aaa/lib'))],
+            'bbb': [provider.getFolder(_p('/bbb/lib'))],
           }),
           new ResourceUriResolver(provider)
         ], null, provider),
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index 99871a4..cb583d2 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -1987,6 +1987,39 @@
         await driver.getTopLevelNameDeclarations('X'), [], []);
   }
 
+  test_getTopLevelNameDeclarations_discoverAvailable() 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.
+
+    _assertTopLevelDeclarations(
+        await driver.getTopLevelNameDeclarations('T'), [t], [false]);
+
+    _assertTopLevelDeclarations(
+        await driver.getTopLevelNameDeclarations('A1'), [a1], [false]);
+
+    _assertTopLevelDeclarations(
+        await driver.getTopLevelNameDeclarations('A2'), [a2], [false]);
+
+    _assertTopLevelDeclarations(
+        await driver.getTopLevelNameDeclarations('B'), [b], [false]);
+
+    _assertTopLevelDeclarations(
+        await driver.getTopLevelNameDeclarations('C'), [], []);
+  }
+
   test_getTopLevelNameDeclarations_parts() async {
     var a = _p('/test/lib/a.dart');
     var b = _p('/test/lib/b.dart');