Add changeFiles(), use instead of changeFile().

I would like to remove releaseAndClearRemovedIds(), so that methods
of FileResolver call it when necessary, as linkLibraries2(),
dispose() do it.

Change-Id: I494abaffa7a5b8a8360c2750efb5b88b4b5535e9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/247926
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index 1f0ecb0..6e30935 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -116,11 +116,11 @@
   LibraryContext? libraryContext;
 
   /// List of keys for cache elements that are invalidated. Track elements that
-  /// are invalidated during [changeFile]. Used in [releaseAndClearRemovedIds]
+  /// are invalidated during [changeFiles]. Used in [releaseAndClearRemovedIds]
   /// to release the cache items and is then cleared.
   final Set<String> removedCacheKeys = {};
 
-  /// The cache of file results, cleared on [changeFile].
+  /// The cache of file results, cleared on [changeFiles].
   ///
   /// It is used to allow assists and fixes without resolving the same file
   /// multiple times, as we compute more than one assist, or fixes when there
@@ -160,7 +160,17 @@
   /// that directly or indirectly referenced it, is resolved, we used the new
   /// state of the file. Updates [removedCacheKeys] with the ids of the invalidated
   /// items, used in [releaseAndClearRemovedIds] to release the cache items.
+  /// TODO(scheglov) Remove [releaseKeys] when removing [changeFile].
+  @Deprecated('Use changeFiles() instead')
   void changeFile(String path) {
+    changeFiles([path], releaseKeys: false);
+  }
+
+  /// Update the resolver to reflect the fact that the files with the given
+  /// [paths] were changed. For each specified file we need to make sure that
+  /// when the file, of any file that directly or indirectly referenced it,
+  /// is resolved, we use the new state of the file.
+  void changeFiles(List<String> paths, {bool releaseKeys = true}) {
     if (fsState == null) {
       return;
     }
@@ -168,19 +178,23 @@
     // Forget all results, anything is potentially affected.
     cachedResults.clear();
 
-    // Remove this file and all files that transitively depend on it.
-    var removedFiles = <FileState>[];
-    fsState!.changeFile(path, removedFiles);
+    // Remove the specified files and files that transitively depend on it.
+    final removedFiles = <FileState>[];
+    for (final path in paths) {
+      fsState!.changeFile(path, removedFiles);
+    }
 
     // Schedule disposing references to cached unlinked data.
-    for (var removedFile in removedFiles) {
+    for (final removedFile in removedFiles) {
       removedCacheKeys.add(removedFile.unlinkedKey);
     }
 
     // Remove libraries represented by removed files.
     // If we need these libraries later, we will relink and reattach them.
-    if (libraryContext != null) {
-      libraryContext!.remove(removedFiles, removedCacheKeys);
+    libraryContext?.remove(removedFiles, removedCacheKeys);
+
+    if (releaseKeys) {
+      releaseAndClearRemovedIds();
     }
   }
 
diff --git a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
index 775ac45..1ad2d13 100644
--- a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
+++ b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
@@ -18,13 +18,13 @@
 
 main() {
   defineReflectiveSuite(() {
-    defineReflectiveTests(FileResolver_changeFile_Test);
+    defineReflectiveTests(FileResolver_changeFiles_Test);
     defineReflectiveTests(FileResolverTest);
   });
 }
 
 @reflectiveTest
-class FileResolver_changeFile_Test extends FileResolutionTest {
+class FileResolver_changeFiles_Test extends FileResolutionTest {
   test_changeFile_refreshedFiles() async {
     final a = newFile('$testPackageLibPath/a.dart', r'''
 class A {}
@@ -128,8 +128,7 @@
     assertStateString(state_1);
 
     // Change a.dart, discard data for a.dart and c.dart, but not b.dart
-    fileResolver.changeFile(a.path);
-    fileResolver.releaseAndClearRemovedIds();
+    fileResolver.changeFiles([a.path]);
     assertStateString(r'''
 files
   /sdk/lib/_internal/internal.dart
@@ -294,7 +293,7 @@
 class A {}
 class B {}
 ''');
-    fileResolver.changeFile(a.path);
+    fileResolver.changeFiles([a.path]);
 
     result = await resolveFile(b.path);
     assertErrorsInResolvedUnit(result, []);
@@ -325,7 +324,7 @@
   int foo = 0;
 }
 ''');
-    fileResolver.changeFile(a.path);
+    fileResolver.changeFiles([a.path]);
 
     result = await resolveFile(b.path);
     assertErrorsInResolvedUnit(result, []);
@@ -351,7 +350,7 @@
 
 var b = B(1);
 ''');
-    fileResolver.changeFile(a.path);
+    fileResolver.changeFiles([a.path]);
 
     // Update b.dart, but do not notify the resolver.
     // If we try to read it now, it will throw.
@@ -368,7 +367,7 @@
     }, throwsStateError);
 
     // Notify the resolver about b.dart, it is OK now.
-    fileResolver.changeFile(b.path);
+    fileResolver.changeFiles([b.path]);
     result = await resolveFile(a.path);
     assertErrorsInResolvedUnit(result, []);
   }
@@ -448,8 +447,7 @@
 ''');
 
     // Change b.dart, discard both b.dart and a.dart
-    fileResolver.changeFile(b.path);
-    fileResolver.releaseAndClearRemovedIds();
+    fileResolver.changeFiles([b.path]);
     assertStateString(r'''
 files
   /sdk/lib/_internal/internal.dart
@@ -653,8 +651,7 @@
 ''');
 
     // Should invalidate a.dart, b.dart, c.dart
-    fileResolver.changeFile(b.path);
-    fileResolver.releaseAndClearRemovedIds();
+    fileResolver.changeFiles([b.path]);
     assertStateString(r'''
 files
   /sdk/lib/_internal/internal.dart
@@ -1007,7 +1004,7 @@
     var dartCorePath = a_result.session.uriConverter.uriToPath(
       Uri.parse('dart:core'),
     )!;
-    fileResolver.changeFile(dartCorePath);
+    fileResolver.changeFiles([dartCorePath]);
 
     // Analyze, this will read the element model for `dart:core`.
     // There was a bug that `root::dart:core::dynamic` had no element set.
@@ -1360,7 +1357,7 @@
 
     // Change the file, will be resolved again.
     newFile(testFilePath, 'var a = c;');
-    fileResolver.changeFile(testFile.path);
+    fileResolver.changeFiles([testFile.path]);
     expect((await getTestErrors()).errors, hasLength(1));
     _assertResolvedFiles([testFile]);
   }
@@ -1392,7 +1389,7 @@
     newFile(a.path, r'''
 var a = 4.2;
 ''');
-    fileResolver.changeFile(a.path);
+    fileResolver.changeFiles([a.path]);
     expect((await getTestErrors()).errors, hasLength(1));
     _assertResolvedFiles([testFile]);
   }
@@ -2554,7 +2551,7 @@
 
     // Change a file.
     var a_path = convertPath('/workspace/dart/test/lib/a.dart');
-    fileResolver.changeFile(a_path);
+    fileResolver.changeFiles([a_path]);
 
     // The was a change to a file, no matter which, resolve again.
     await resolveFile2(testFile.path);