Add AnalysisDriver.getFile(), deprecate getFileSync()

Change-Id: I2881c03813740c421f1b4a4be96549e4a91be519
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/235741
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index d1aa079..aac32ba 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -2,6 +2,7 @@
 * Deprecated synchronous `getFile`, `getParsedLibrary`, `getParsedLibraryByElement`,
   `getParsedUnit` from `AnalysisSession`. Use corresponding `getXyz2` asynchronous
   methods instead. This change is necessary for the future work on macros.
+* Deprecated synchronous `getFileSync` from `AnalysisDriver`, use `getFile` instead.
 
 ## 3.3.1
 * Report HintCode.OVERRIDE_ON_NON_OVERRIDING_xyz on enum.
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 84644e4..7a3b593 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -588,6 +588,19 @@
     return getErrors(path);
   }
 
+  /// Return the [FileResult] for the Dart file with the given [path].
+  ///
+  /// The [path] must be absolute and normalized.
+  Future<SomeFileResult> getFile(String path) async {
+    if (!_isAbsolutePath(path)) {
+      return InvalidPathResult();
+    }
+
+    FileState file = _fileTracker.getFile(path);
+    return FileResultImpl(
+        currentSession, path, file.uri, file.lineInfo, file.isPart);
+  }
+
   /// Return a [Future] that completes with the list of added files that
   /// define a class member with the given [name].
   Future<List<String>> getFilesDefiningClassMemberName(String name) {
@@ -611,6 +624,7 @@
   /// Return the [FileResult] for the Dart file with the given [path].
   ///
   /// The [path] must be absolute and normalized.
+  @Deprecated('Use getFile() instead')
   SomeFileResult getFileSync(String path) {
     if (!_isAbsolutePath(path)) {
       return InvalidPathResult();
@@ -624,7 +638,7 @@
   /// Return the [FileResult] for the Dart file with the given [path].
   ///
   /// The [path] must be absolute and normalized.
-  @Deprecated('Use getFileSync() instead')
+  @Deprecated('Use getFile() instead')
   SomeFileResult getFileSync2(String path) {
     return getFileSync(path);
   }
diff --git a/pkg/analyzer/lib/src/dart/analysis/session.dart b/pkg/analyzer/lib/src/dart/analysis/session.dart
index 97c98ca..47df470 100644
--- a/pkg/analyzer/lib/src/dart/analysis/session.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/session.dart
@@ -70,7 +70,7 @@
   @override
   Future<SomeFileResult> getFile2(String path) async {
     _checkConsistency();
-    return _driver.getFileSync(path);
+    return _driver.getFile(path);
   }
 
   @override
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index afefd83..67951ae 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -1380,6 +1380,66 @@
     expect(result, isA<InvalidPathResult>());
   }
 
+  test_getFile_changedFile() async {
+    var a = convertPath('/test/lib/a.dart');
+    var b = convertPath('/test/lib/b.dart');
+
+    newFile(a, content: '');
+    newFile(b, content: r'''
+import 'a.dart';
+
+void f(A a) {}
+''');
+
+    // Ensure that [a.dart] library cycle is loaded.
+    // So, `a.dart` is in the library context.
+    await driver.getResultValid(a);
+
+    // Update the file, changing its API signature.
+    // Note that we don't call `changeFile`.
+    newFile(a, content: 'class A {}\n');
+
+    // Get the file.
+    // We have not called `changeFile(a)`, so we should not read the file.
+    // Moreover, doing this will create a new library cycle [a.dart].
+    // Library cycles are compared by their identity, so we would try to
+    // reload linked summary for [a.dart], and crash.
+    expect((await driver.getFileValid(a)).lineInfo.lineCount, 1);
+
+    // We have not read `a.dart`, so `A` is still not declared.
+    expect((await driver.getResultValid(b)).errors, isNotEmpty);
+
+    // Notify the driver that the file was changed.
+    driver.changeFile(a);
+
+    // So, `class A {}` is declared now.
+    expect((await driver.getFileValid(a)).lineInfo.lineCount, 2);
+    expect((await driver.getResultValid(b)).errors, isEmpty);
+  }
+
+  test_getFile_library() async {
+    var path = convertPath('/test/lib/a.dart');
+    newFile(path);
+    var file = await driver.getFileValid(path);
+    expect(file.path, path);
+    expect(file.uri.toString(), 'package:test/a.dart');
+    expect(file.isPart, isFalse);
+  }
+
+  test_getFile_notAbsolutePath() async {
+    var result = await driver.getFile('not_absolute.dart');
+    expect(result, isA<InvalidPathResult>());
+  }
+
+  test_getFile_part() async {
+    var path = convertPath('/test/lib/a.dart');
+    newFile(path, content: 'part of lib;');
+    var file = await driver.getFileValid(path);
+    expect(file.path, path);
+    expect(file.uri.toString(), 'package:test/a.dart');
+    expect(file.isPart, isTrue);
+  }
+
   test_getFilesDefiningClassMemberName_class() async {
     var a = convertPath('/test/bin/a.dart');
     var b = convertPath('/test/bin/b.dart');
@@ -1483,6 +1543,7 @@
     expect(files, isNot(contains(c)));
   }
 
+  @deprecated
   test_getFileSync_changedFile() async {
     var a = convertPath('/test/lib/a.dart');
     var b = convertPath('/test/lib/b.dart');
@@ -1520,6 +1581,7 @@
     expect((await driver.getResultValid(b)).errors, isEmpty);
   }
 
+  @deprecated
   test_getFileSync_library() async {
     var path = convertPath('/test/lib/a.dart');
     newFile(path);
@@ -1529,11 +1591,13 @@
     expect(file.isPart, isFalse);
   }
 
+  @deprecated
   test_getFileSync_notAbsolutePath() async {
     var result = driver.getFileSync('not_absolute.dart');
     expect(result, isA<InvalidPathResult>());
   }
 
+  @deprecated
   test_getFileSync_part() async {
     var path = convertPath('/test/lib/a.dart');
     newFile(path, content: 'part of lib;');
@@ -3451,10 +3515,15 @@
     }
   }
 
+  @deprecated
   FileResult getFileSyncValid(String path) {
     return getFileSync(path) as FileResult;
   }
 
+  Future<FileResult> getFileValid(String path) async {
+    return await getFile(path) as FileResult;
+  }
+
   Future<LibraryElementResult> getLibraryByUriValid(String uriStr) async {
     return await getLibraryByUri(uriStr) as LibraryElementResult;
   }
diff --git a/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart b/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart
index d570325..b1a26bd 100644
--- a/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart
@@ -461,7 +461,7 @@
     newFile(testFilePath, content: 'class A {}');
 
     // Read the file.
-    testDriver.getFileSync(testFilePathPlatform);
+    await testDriver.getFile(testFilePathPlatform);
 
     // Should call `changeFile()`, and the driver must re-read the file.
     var result = _resolveTestCode(r'''
diff --git a/pkg/analyzer_cli/lib/src/analyzer_impl.dart b/pkg/analyzer_cli/lib/src/analyzer_impl.dart
index fff1252..7e40fff 100644
--- a/pkg/analyzer_cli/lib/src/analyzer_impl.dart
+++ b/pkg/analyzer_cli/lib/src/analyzer_impl.dart
@@ -148,7 +148,7 @@
 
     // Print errors and performance numbers.
     if (printMode == 1) {
-      formatter.formatErrors(errorsResults);
+      await formatter.formatErrors(errorsResults);
     } else if (printMode == 2) {
       _printColdPerf();
     }
diff --git a/pkg/analyzer_cli/lib/src/driver.dart b/pkg/analyzer_cli/lib/src/driver.dart
index de1d3c0..6c75fd3 100644
--- a/pkg/analyzer_cli/lib/src/driver.dart
+++ b/pkg/analyzer_cli/lib/src/driver.dart
@@ -248,7 +248,7 @@
             analysisDriver.sourceFactory,
             analysisDriver.currentSession.analysisContext.contextRoot.root.path,
           );
-          formatter.formatErrors([
+          await formatter.formatErrors([
             ErrorsResultImpl(analysisDriver.currentSession, path,
                 pathContext.toUri(path), lineInfo, false, errors)
           ]);
@@ -304,7 +304,7 @@
                 allResult = allResult.max(severity);
               }
               var lineInfo = LineInfo.fromContent(content);
-              formatter.formatErrors([
+              await formatter.formatErrors([
                 ErrorsResultImpl(analysisDriver.currentSession, path,
                     pathContext.toUri(path), lineInfo, false, errors)
               ]);
@@ -320,7 +320,7 @@
             var lineInfo = LineInfo.fromContent(content);
             var errors = validator.validate(
                 content, analysisDriver.analysisOptions.chromeOsManifestChecks);
-            formatter.formatErrors([
+            await formatter.formatErrors([
               ErrorsResultImpl(analysisDriver.currentSession, path,
                   pathContext.toUri(path), lineInfo, false, errors)
             ]);
diff --git a/pkg/analyzer_cli/lib/src/error_formatter.dart b/pkg/analyzer_cli/lib/src/error_formatter.dart
index 51c7002..88147da 100644
--- a/pkg/analyzer_cli/lib/src/error_formatter.dart
+++ b/pkg/analyzer_cli/lib/src/error_formatter.dart
@@ -189,10 +189,10 @@
   /// Call to write any batched up errors from [formatErrors].
   void flush();
 
-  void formatError(
+  Future<void> formatError(
       Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error);
 
-  void formatErrors(List<ErrorsResult> results) {
+  Future<void> formatErrors(List<ErrorsResult> results) async {
     stats.unfilteredCount += results.length;
 
     var errors = <AnalysisError>[];
@@ -207,7 +207,7 @@
     }
 
     for (var error in errors) {
-      formatError(errorToLine, error);
+      await formatError(errorToLine, error);
     }
   }
 
@@ -276,8 +276,8 @@
   }
 
   @override
-  void formatError(
-      Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error) {
+  Future<void> formatError(
+      Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error) async {
     var source = error.source;
     var result = errorToLine[error]!;
     var location = result.lineInfo.getLocation(error.offset);
@@ -308,9 +308,10 @@
     }
     var contextMessages = <ContextMessage>[];
     for (var message in error.contextMessages) {
+      // TODO(scheglov) We should add `LineInfo` to `DiagnosticMessage`.
       var session = result.session.analysisContext;
       if (session is DriverBasedAnalysisContext) {
-        var fileResult = session.driver.getFileSync(message.filePath);
+        var fileResult = await session.driver.getFile(message.filePath);
         if (fileResult is FileResult) {
           var lineInfo = fileResult.lineInfo;
           var location = lineInfo.getLocation(message.offset);
@@ -348,13 +349,13 @@
   void flush() {}
 
   @override
-  void formatError(
-      Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error) {
+  Future<void> formatError(
+      Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error) async {
     throw UnsupportedError('Cannot format a single error');
   }
 
   @override
-  void formatErrors(List<ErrorsResult> results) {
+  Future<void> formatErrors(List<ErrorsResult> results) async {
     Map<String, dynamic> range(
             Map<String, dynamic> start, Map<String, dynamic> end) =>
         {
@@ -435,8 +436,8 @@
   void flush() {}
 
   @override
-  void formatError(
-      Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error) {
+  Future<void> formatError(
+      Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error) async {
     // Ensure we don't over-report (#36062).
     if (!_seenErrors.add(error)) {
       return;
diff --git a/pkg/analyzer_cli/test/reporter_test.dart b/pkg/analyzer_cli/test/reporter_test.dart
index 781d1e5..54a2964 100644
--- a/pkg/analyzer_cli/test/reporter_test.dart
+++ b/pkg/analyzer_cli/test/reporter_test.dart
@@ -44,27 +44,27 @@
         reporter = HumanErrorFormatter(out, options, stats);
       });
 
-      test('error', () {
+      test('error', () async {
         var error = mockResult(ErrorType.SYNTACTIC_ERROR, ErrorSeverity.ERROR);
-        reporter.formatErrors([error]);
+        await reporter.formatErrors([error]);
         reporter.flush();
 
         expect(out.toString().trim(),
             'error • MSG • /foo/bar/baz.dart:3:3 • mock_code');
       });
 
-      test('hint', () {
+      test('hint', () async {
         var error = mockResult(ErrorType.HINT, ErrorSeverity.INFO);
-        reporter.formatErrors([error]);
+        await reporter.formatErrors([error]);
         reporter.flush();
 
         expect(out.toString().trim(),
             'hint • MSG • /foo/bar/baz.dart:3:3 • mock_code');
       });
 
-      test('stats', () {
+      test('stats', () async {
         var error = mockResult(ErrorType.HINT, ErrorSeverity.INFO);
-        reporter.formatErrors([error]);
+        await reporter.formatErrors([error]);
         reporter.flush();
         stats.print(out);
         expect(
@@ -79,9 +79,9 @@
         reporter = JsonErrorFormatter(out, options, stats);
       });
 
-      test('error', () {
+      test('error', () async {
         var error = mockResult(ErrorType.SYNTACTIC_ERROR, ErrorSeverity.ERROR);
-        reporter.formatErrors([error]);
+        await reporter.formatErrors([error]);
         reporter.flush();
 
         expect(