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');