Add isLibraryByUri() to AnalysisDriver.

The package:build develpers told us that they need it.

R=brianwilkerson@google.com, paulberry@google.com

Change-Id: Ia48851faac353d5f83e7d6af7d579c2c1d8be1bd
Reviewed-on: https://dart-review.googlesource.com/c/84502
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 2e2bd5a..0ae475b 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -859,6 +859,27 @@
   }
 
   /**
+   * Return `true` is the file with the given absolute [uri] is a library,
+   * or `false` if it is a part. More specifically, return `true` if the file
+   * is not known to be a part.
+   *
+   * Correspondingly, return `true` if the [uri] does not correspond to a file,
+   * for any reason, e.g. the file does not exist, or the [uri] cannot be
+   * resolved to a file path, or the [uri] is invalid, e.g. a `package:` URI
+   * without a package name. In these cases we cannot prove that the file is
+   * not a part, so it must be a library.
+   */
+  bool isLibraryByUri(Uri uri) {
+    if (_externalSummaries != null) {
+      var uriStr = uri.toString();
+      if (_externalSummaries.unlinkedMap[uriStr] != null) {
+        return _externalSummaries.linkedMap.containsKey(uriStr);
+      }
+    }
+    return !_fsState.getFileForUri(uri).isPart;
+  }
+
+  /**
    * Return a [Future] that completes with a [ParseResult] for the file
    * with the given [path].
    *
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index c1aad81..c308e2e 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -220,6 +220,12 @@
   bool get isPart => _unlinked.libraryNameOffset == 0 && _unlinked.isPartOf;
 
   /**
+   * Return `true` if the file is the "unresolved" file, which does not have
+   * neither a valid URI, nor a path.
+   */
+  bool get isUnresolved => uri == null;
+
+  /**
    * If the file [isPart], return a currently know library the file is a part
    * of. Return `null` if a library is not known, for example because we have
    * not processed a library file yet.
@@ -834,9 +840,10 @@
   }
 
   /**
-   * Return the [FileState] for the given absolute [uri]. May return `null` if
-   * the [uri] is invalid, e.g. a `package:` URI without a package name. The
-   * returned file has the last known state since if was last refreshed.
+   * Return the [FileState] for the given absolute [uri]. May return the
+   * "unresolved" file if the [uri] is invalid, e.g. a `package:` URI without
+   * a package name. The returned file has the last known state since if was
+   * last refreshed.
    */
   FileState getFileForUri(Uri uri) {
     FileState file = _uriToFile[uri];
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index b0adc84..1afe213 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -10,8 +10,10 @@
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
+import 'package:analyzer/src/dart/analysis/byte_store.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart';
 import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/dart/analysis/performance_logger.dart';
 import 'package:analyzer/src/dart/analysis/status.dart';
 import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
 import 'package:analyzer/src/dart/constant/evaluation.dart';
@@ -24,8 +26,6 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/summary/idl.dart';
 import 'package:analyzer/src/summary/package_bundle_reader.dart';
-import 'package:analyzer/src/dart/analysis/byte_store.dart';
-import 'package:analyzer/src/dart/analysis/performance_logger.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -2322,6 +2322,50 @@
     await waitForIdleWithoutExceptions();
   }
 
+  test_isLibraryByUri() async {
+    var a1 = _p('/aaa/lib/a1.dart');
+    var a2 = _p('/aaa/lib/a2.dart');
+    var b1 = _p('/bbb/lib/b1.dart');
+    var b2 = _p('/bbb/lib/b2.dart');
+
+    String a1UriStr = 'package:aaa/a1.dart';
+    String a2UriStr = 'package:aaa/a2.dart';
+    String b1UriStr = 'package:bbb/b1.dart';
+    String b2UriStr = 'package:bbb/b2.dart';
+
+    provider.newFile(a1, "part 'a2.dart';");
+    provider.newFile(a2, "part of 'a1.dart';");
+    provider.newFile(b1, "part 'b2.dart';");
+    provider.newFile(b2, "part of 'b1.dart';");
+
+    // Build the store with the library.
+    var store = await createAnalysisDriver().test.getSummaryStore(a1);
+    expect(store.unlinkedMap.keys, contains(a1UriStr));
+    expect(store.unlinkedMap.keys, contains(a2UriStr));
+    expect(store.linkedMap.keys, contains(a1UriStr));
+
+    // Remove the stored files from the file system.
+    provider.deleteFile(a1);
+    provider.deleteFile(a2);
+
+    // We can ask isLibraryByUri() for both external and local units.
+    AnalysisDriver driver = createAnalysisDriver(externalSummaries: store);
+    expect(driver.isLibraryByUri(Uri.parse(a1UriStr)), isTrue);
+    expect(driver.isLibraryByUri(Uri.parse(a2UriStr)), isFalse);
+    expect(driver.isLibraryByUri(Uri.parse(b1UriStr)), isTrue);
+    expect(driver.isLibraryByUri(Uri.parse(b2UriStr)), isFalse);
+  }
+
+  test_isLibraryByUri_doesNotExist() async {
+    var uri = Uri.parse('file:///test.dart');
+    expect(driver.isLibraryByUri(uri), isTrue);
+  }
+
+  test_isLibraryByUri_invalidUri() async {
+    var uri = Uri.parse('package:aaa');
+    expect(driver.isLibraryByUri(uri), isTrue);
+  }
+
   test_issue34619() async {
     var a = _p('/test/lib/a.dart');
     provider.newFile(a, r'''
diff --git a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
index 091ec5c..aeb0e75 100644
--- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
@@ -7,7 +7,9 @@
 
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
+import 'package:analyzer/src/dart/analysis/byte_store.dart';
 import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/dart/analysis/performance_logger.dart';
 import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
 import 'package:analyzer/src/file_system/file_system.dart';
 import 'package:analyzer/src/generated/engine.dart'
@@ -16,8 +18,6 @@
 import 'package:analyzer/src/source/package_map_resolver.dart';
 import 'package:convert/convert.dart';
 import 'package:crypto/crypto.dart';
-import 'package:analyzer/src/dart/analysis/byte_store.dart';
-import 'package:analyzer/src/dart/analysis/performance_logger.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -567,6 +567,15 @@
     expect(file2, same(file1));
   }
 
+  test_getFileForUri_invalidUri() {
+    var uri = Uri.parse('package:x');
+    var file = fileSystemState.getFileForUri(uri);
+    expect(file.isUnresolved, isTrue);
+    expect(file.uri, isNull);
+    expect(file.path, isNull);
+    expect(file.isPart, isFalse);
+  }
+
   test_getFileForUri_packageVsFileUri() {
     String path = _p('/aaa/lib/a.dart');
     var packageUri = Uri.parse('package:aaa/a.dart');