Version 2.13.0-82.0.dev

Merge commit '8833cbed7d7021420e6a969eba48f006bb7050f4' into 'dev'
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 122bfc6..c7afc20 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -429,16 +429,6 @@
     });
   }
 
-  /// Returns `true` if errors should be reported for [file] with the given
-  /// absolute path.
-  bool shouldSendErrorsNotificationFor(String file) {
-    // Errors should not be reported for things that are explicitly skipped
-    // during normal analysis (for example dot folders are skipped over in
-    // _handleWatchEventImpl).
-    return contextManager.isInAnalysisRoot(file) &&
-        !contextManager.isContainedInDotFolder(file);
-  }
-
   Future<void> shutdown() {
     if (options.analytics != null) {
       options.analytics
@@ -674,7 +664,7 @@
     analysisDriver.results.listen((result) {
       var path = result.path;
       filesToFlush.add(path);
-      if (analysisServer.shouldSendErrorsNotificationFor(path)) {
+      if (analysisServer.isAnalyzed(path)) {
         _notificationManager.recordAnalysisErrors(NotificationManager.serverId,
             path, server.doAnalysisError_listFromEngine(result));
       }
diff --git a/pkg/analysis_server/lib/src/analysis_server_abstract.dart b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
index 5b32212..0853e07 100644
--- a/pkg/analysis_server/lib/src/analysis_server_abstract.dart
+++ b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
@@ -348,6 +348,12 @@
     });
   }
 
+  /// Return `true` if the file or directory with the given [path] will be
+  /// analyzed in one of the analysis contexts.
+  bool isAnalyzed(String path) {
+    return contextManager.isAnalyzed(path);
+  }
+
   void logExceptionResult(nd.ExceptionResult result) {
     var message = 'Analysis failed: ${result.filePath}';
     if (result.contextKey != null) {
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index 42ef0da..abacc10 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -87,15 +87,9 @@
   /// If no driver contains the given path, `null` is returned.
   AnalysisDriver getDriverFor(String path);
 
-  /// Determine whether the given [path], when interpreted relative to innermost
-  /// context root, contains a folder whose name starts with '.'.
-  ///
-  /// TODO(scheglov) Remove it, just [isInAnalysisRoot] should be enough.
-  bool isContainedInDotFolder(String path);
-
-  /// Return `true` if the given absolute [path] is in one of the current
-  /// root folders and is not excluded.
-  bool isInAnalysisRoot(String path);
+  /// Return `true` if the file or directory with the given [path] will be
+  /// analyzed in one of the analysis contexts.
+  bool isAnalyzed(String path);
 
   /// Rebuild the set of contexts from scratch based on the data last sent to
   /// [setRoots].
@@ -221,21 +215,8 @@
     return getContextFor(path)?.driver;
   }
 
-  /// Determine whether the given [path], when interpreted relative to innermost
-  /// context root, contains a folder whose name starts with '.'.
   @override
-  bool isContainedInDotFolder(String path) {
-    for (var analysisContext in _collection.contexts) {
-      var contextImpl = analysisContext as DriverBasedAnalysisContext;
-      if (_isContainedInDotFolder(contextImpl.contextRoot.root.path, path)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  @override
-  bool isInAnalysisRoot(String path) {
+  bool isAnalyzed(String path) {
     var collection = _collection;
     if (collection == null) {
       return false;
@@ -431,9 +412,6 @@
       _watchBazelFilesIfNeeded(rootFolder, driver);
 
       for (var file in contextImpl.contextRoot.analyzedFiles()) {
-        if (_isContainedInDotFolder(contextImpl.contextRoot.root.path, file)) {
-          continue;
-        }
         if (file_paths.isAndroidManifestXml(pathContext, file)) {
           _analyzeAndroidManifestXml(driver, file);
         } else if (file_paths.isDart(pathContext, file)) {
@@ -554,12 +532,11 @@
         var analysisContext = analysisContext_ as DriverBasedAnalysisContext;
         switch (type) {
           case ChangeType.ADD:
-            // TODO(scheglov) Why not `isInAnalysisRoot()`?
-            if (_isContainedInDotFolder(
-                analysisContext.contextRoot.root.path, path)) {
-              return;
+            if (analysisContext.contextRoot.isAnalyzed(path)) {
+              analysisContext.driver.addFile(path);
+            } else {
+              analysisContext.driver.changeFile(path);
             }
-            analysisContext.driver.addFile(path);
             break;
           case ChangeType.MODIFY:
             analysisContext.driver.changeFile(path);
@@ -592,25 +569,6 @@
     refresh();
   }
 
-  /// Determine whether the given [path], when interpreted relative to the
-  /// context root [root], contains a folder whose name starts with '.' but is
-  /// not included in [exclude].
-  bool _isContainedInDotFolder(String root, String path,
-      {Set<String> exclude}) {
-    var pathDir = pathContext.dirname(path);
-    var rootPrefix = root + pathContext.separator;
-    if (pathDir.startsWith(rootPrefix)) {
-      var suffixPath = pathDir.substring(rootPrefix.length);
-      for (var pathComponent in pathContext.split(suffixPath)) {
-        if (pathComponent.startsWith('.') &&
-            !(exclude?.contains(pathComponent) ?? false)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
   /// Read the contents of the file at the given [path], or throw an exception
   /// if the contents cannot be read.
   String _readFile(String path) {
diff --git a/pkg/analysis_server/lib/src/edit/edit_dartfix.dart b/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
index 574c964..c36f2bb 100644
--- a/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
@@ -175,7 +175,7 @@
       if (res is Folder) {
         for (var child in res.getChildren()) {
           if (!child.shortName.startsWith('.') &&
-              contextManager.isInAnalysisRoot(child.path)) {
+              server.isAnalyzed(child.path)) {
             resources.add(child);
           }
         }
diff --git a/pkg/analysis_server/lib/src/edit/edit_domain.dart b/pkg/analysis_server/lib/src/edit/edit_domain.dart
index 9c223e0..be204d6 100644
--- a/pkg/analysis_server/lib/src/edit/edit_domain.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_domain.dart
@@ -261,7 +261,7 @@
       return;
     }
 
-    if (!server.contextManager.isInAnalysisRoot(file)) {
+    if (!server.isAnalyzed(file)) {
       server.sendResponse(Response.getFixesInvalidFile(request));
       return;
     }
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
index f291e25..762a4f8 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
@@ -46,7 +46,7 @@
     }
 
     final path = pathOfDoc(params.textDocument);
-    if (!path.isError && !server.isAnalyzedFile(path.result)) {
+    if (!path.isError && !server.isAnalyzed(path.result)) {
       return success(const []);
     }
 
diff --git a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
index d243989..6a5bbc1 100644
--- a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
+++ b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
@@ -338,14 +338,6 @@
     }, socketError);
   }
 
-  /// Returns `true` if the [file] with the given absolute path is included
-  /// in an analysis root and not excluded.
-  bool isAnalyzedFile(String file) {
-    return contextManager.isInAnalysisRoot(file) &&
-        // Dot folders are not analyzed (skipped over in _handleWatchEventImpl)
-        !contextManager.isContainedInDotFolder(file);
-  }
-
   /// Logs the error on the client using window/logMessage.
   void logErrorToClient(String message) {
     channel.sendNotification(NotificationMessage(
@@ -584,13 +576,7 @@
     // workspace.
     return initializationOptions.closingLabels &&
         priorityFiles.contains(file) &&
-        contextManager.isInAnalysisRoot(file);
-  }
-
-  /// Returns `true` if errors should be reported for [file] with the given
-  /// absolute path.
-  bool shouldSendErrorsNotificationFor(String file) {
-    return isAnalyzedFile(file);
+        isAnalyzed(file);
   }
 
   /// Returns `true` if Flutter outlines should be sent for [file] with the
@@ -817,7 +803,7 @@
     analysisDriver.results.listen((result) {
       var path = result.path;
       filesToFlush.add(path);
-      if (analysisServer.shouldSendErrorsNotificationFor(path)) {
+      if (analysisServer.isAnalyzed(path)) {
         final serverErrors = protocol.mapEngineErrors(
             result,
             result.errors.where(_shouldSendDiagnostic).toList(),
diff --git a/pkg/analysis_server/test/domain_analysis_test.dart b/pkg/analysis_server/test/domain_analysis_test.dart
index 9266e21..2138863 100644
--- a/pkg/analysis_server/test/domain_analysis_test.dart
+++ b/pkg/analysis_server/test/domain_analysis_test.dart
@@ -454,6 +454,35 @@
     assertHasErrors(a_path);
   }
 
+  Future<void> test_fileSystem_addFile_dart_dotFolder() async {
+    var a_path = '$projectPath/lib/.foo/a.dart';
+    var b_path = '$projectPath/lib/b.dart';
+
+    newFile(b_path, content: r'''
+import '.foo/a.dart';
+void f(A a) {}
+''');
+
+    createProject();
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // We don't have a.dart, so the import cannot be resolved.
+    assertHasErrors(b_path);
+
+    newFile(a_path, content: r'''
+class A {}
+''');
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // 'a.dart' is in a dot-folder, so excluded from analysis.
+    assertNoErrorsNotification(a_path);
+
+    // We added a.dart with `A`, so no errors.
+    assertNoErrors(b_path);
+  }
+
   Future<void> test_fileSystem_addFile_dart_excluded() async {
     var a_path = '$projectPath/lib/a.dart';
     var b_path = '$projectPath/lib/b.dart';
@@ -747,6 +776,42 @@
     assertNoErrors(b_path);
   }
 
+  Future<void> test_fileSystem_changeFile_dart_dotFolder() async {
+    var a_path = '$testPackageLibPath/.foo/a.dart';
+    var b_path = '$testPackageLibPath/b.dart';
+
+    newFile(a_path, content: r'''
+class B {}
+''');
+
+    newFile(b_path, content: r'''
+import '.foo/a.dart';
+void f(A a) {}
+''');
+
+    setRoots(included: [workspaceRootPath], excluded: []);
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // 'a.dart' is in a dot-folder, so excluded from analysis.
+    assertNoErrorsNotification(a_path);
+
+    // We have `B`, not `A`, in a.dart, so has errors.
+    assertHasErrors(b_path);
+
+    newFile(a_path, content: r'''
+class A {}
+''');
+    await pumpEventQueue();
+    await server.onAnalysisComplete;
+
+    // 'a.dart' is in a dot-folder, so excluded from analysis.
+    assertNoErrorsNotification(a_path);
+
+    // We changed a.dart, to have `A`, so no errors.
+    assertNoErrors(b_path);
+  }
+
   Future<void> test_fileSystem_changeFile_dart_excluded() async {
     var a_path = '$testPackageLibPath/a.dart';
     var b_path = '$testPackageLibPath/b.dart';
diff --git a/pkg/analysis_server/test/src/cider/fixes_test.dart b/pkg/analysis_server/test/src/cider/fixes_test.dart
index 4f4b240..6eeaf47 100644
--- a/pkg/analysis_server/test/src/cider/fixes_test.dart
+++ b/pkg/analysis_server/test/src/cider/fixes_test.dart
@@ -37,6 +37,20 @@
     expect(resultContent, expected);
   }
 
+  Future<void> test_cachedResolvedFiles() async {
+    await _compute(r'''
+var a = 0^ var b = 1
+''');
+
+    // Only the first fix is applied.
+    assertHasFix(DartFixKind.INSERT_SEMICOLON, r'''
+var a = 0; var b = 1
+''');
+
+    // The file was resolved only once, even though we have 2 errors.
+    expect(fileResolver.testView.resolvedFiles, [testPath]);
+  }
+
   Future<void> test_createMethod() async {
     await _compute(r'''
 class A {
diff --git a/pkg/analyzer/lib/src/context/packages.dart b/pkg/analyzer/lib/src/context/packages.dart
index 94b1686..f3dad37 100644
--- a/pkg/analyzer/lib/src/context/packages.dart
+++ b/pkg/analyzer/lib/src/context/packages.dart
@@ -99,11 +99,6 @@
         jsonLanguageVersion.minor,
         0,
       );
-      // New features were added in `2.2.2` over `2.2.0`.
-      // But `2.2.2` is not representable, so we special case it.
-      if (languageVersion.major == 2 && languageVersion.minor == 2) {
-        languageVersion = Version(2, 2, 2);
-      }
     }
 
     map[name] = Package(
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_root.dart b/pkg/analyzer/lib/src/dart/analysis/context_root.dart
index 4eecb80..6ef4330 100644
--- a/pkg/analyzer/lib/src/dart/analysis/context_root.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/context_root.dart
@@ -107,10 +107,11 @@
   bool _isExcluded(String path) {
     Context context = resourceProvider.pathContext;
 
-    for (String pathComponent in context.split(path)) {
-      if (pathComponent.startsWith('.')) {
+    for (var current = path; current != root.path;) {
+      if (context.basename(current).startsWith('.')) {
         return true;
       }
+      current = context.dirname(current);
     }
 
     for (String excludedPath in excludedPaths) {
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index 79a9088..583f799 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -80,6 +80,13 @@
   /// to release the cache items and is then cleared.
   final Set<int> removedCacheIds = {};
 
+  /// The cache of file results, cleared on [changeFile].
+  ///
+  /// 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
+  /// are more than one error on a line.
+  final Map<String, ResolvedUnitResult> _cachedResults = {};
+
   FileResolver(
     PerformanceLog logger,
     ResourceProvider resourceProvider,
@@ -127,6 +134,9 @@
       return;
     }
 
+    // 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);
@@ -329,6 +339,11 @@
 
     performance ??= OperationPerformanceImpl('<default>');
 
+    var cachedResult = _cachedResults[path];
+    if (cachedResult != null) {
+      return cachedResult;
+    }
+
     return logger.run('Resolve $path', () {
       var fileContext = getFileContext(
         path: path,
@@ -403,7 +418,7 @@
       });
       UnitAnalysisResult fileResult = results[file]!;
 
-      return ResolvedUnitResultImpl(
+      var result = ResolvedUnitResultImpl(
         contextObjects!.analysisSession,
         path,
         file.uri,
@@ -414,6 +429,8 @@
         fileResult.unit,
         fileResult.errors,
       );
+      _cachedResults[path] = result;
+      return result;
     });
   }
 
diff --git a/pkg/analyzer/test/src/context/packages_test.dart b/pkg/analyzer/test/src/context/packages_test.dart
index 204ca42..792753b 100644
--- a/pkg/analyzer/test/src/context/packages_test.dart
+++ b/pkg/analyzer/test/src/context/packages_test.dart
@@ -209,32 +209,6 @@
     );
   }
 
-  /// New features were added in `2.2.2` over `2.2.0`.
-  /// But `2.2.2` is not representable, so we special case it.
-  test_parsePackageConfigJsonFile_version222() {
-    var file = newFile('/test/.dart_tool/package_config.json', content: '''
-{
-  "configVersion": 2,
-  "packages": [
-    {
-      "name": "test",
-      "rootUri": "../",
-      "packageUri": "lib/",
-      "languageVersion": "2.2"
-    }
-  ]
-}
-''');
-    var packages = parsePackageConfigJsonFile(resourceProvider, file);
-
-    _assertPackage(
-      packages,
-      name: 'test',
-      expectedLibPath: '/test/lib',
-      expectedVersion: Version(2, 2, 2),
-    );
-  }
-
   test_parsePackagesFile_dotPackages() {
     var path = convertPath('/test/.packages');
     newFile(path, content: '''
diff --git a/pkg/analyzer/test/src/dart/analysis/context_root_test.dart b/pkg/analyzer/test/src/dart/analysis/context_root_test.dart
index 22b64ae..39eaf04 100644
--- a/pkg/analyzer/test/src/dart/analysis/context_root_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/context_root_test.dart
@@ -2,11 +2,13 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:analyzer/dart/analysis/context_root.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/dart/analysis/context_root.dart';
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer/src/workspace/basic.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
+import 'package:path/path.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -78,6 +80,14 @@
     expect(contextRoot.isAnalyzed(filePath), isFalse);
   }
 
+  test_isAnalyzed_implicitlyExcluded_dotFolder_containsRoot() {
+    var contextRoot = _createContextRoot('/home/.foo/root');
+
+    expect(_isAnalyzed(contextRoot, ''), isTrue);
+    expect(_isAnalyzed(contextRoot, 'lib/a.dart'), isTrue);
+    expect(_isAnalyzed(contextRoot, 'lib/.bar/a.dart'), isFalse);
+  }
+
   test_isAnalyzed_implicitlyExcluded_dotFolder_directParent() {
     String filePath = convertPath('/test/root/lib/.aaa/a.dart');
     expect(contextRoot.isAnalyzed(filePath), isFalse);
@@ -88,6 +98,14 @@
     expect(contextRoot.isAnalyzed(filePath), isFalse);
   }
 
+  test_isAnalyzed_implicitlyExcluded_dotFolder_isRoot() {
+    var contextRoot = _createContextRoot('/home/.root');
+
+    expect(_isAnalyzed(contextRoot, ''), isTrue);
+    expect(_isAnalyzed(contextRoot, 'lib/a.dart'), isTrue);
+    expect(_isAnalyzed(contextRoot, 'lib/.bar/a.dart'), isFalse);
+  }
+
   test_isAnalyzed_included() {
     String filePath = convertPath('/test/root/lib/root.dart');
     expect(contextRoot.isAnalyzed(filePath), isTrue);
@@ -110,4 +128,24 @@
     newFolder(folderPath);
     expect(contextRoot.isAnalyzed(folderPath), isTrue);
   }
+
+  ContextRootImpl _createContextRoot(String posixPath) {
+    var rootPath = convertPath(posixPath);
+    var rootFolder = newFolder(rootPath);
+    var workspace = BasicWorkspace.find(resourceProvider, {}, rootPath);
+    var contextRoot = ContextRootImpl(resourceProvider, rootFolder, workspace);
+    contextRoot.included.add(rootFolder);
+    return contextRoot;
+  }
+
+  static bool _isAnalyzed(ContextRoot contextRoot, String relPosix) {
+    var pathContext = contextRoot.resourceProvider.pathContext;
+    var path = pathContext.join(
+      contextRoot.root.path,
+      pathContext.joinAll(
+        posix.split(relPosix),
+      ),
+    );
+    return contextRoot.isAnalyzed(path);
+  }
 }
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 b842d4b..8709a1f 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
@@ -528,6 +528,38 @@
 ''');
   }
 
+  test_resolveFile_cache() async {
+    var path = convertPath('/workspace/dart/test/lib/test.dart');
+    newFile(path, content: 'var a = 0;');
+
+    // No resolved files yet.
+    expect(fileResolver.testView!.resolvedFiles, isEmpty);
+
+    await resolveFile2(path);
+    var result1 = result;
+
+    // The file was resolved.
+    expect(fileResolver.testView!.resolvedFiles, [path]);
+
+    // Ask again, no changes, not resolved.
+    await resolveFile2(path);
+    expect(fileResolver.testView!.resolvedFiles, [path]);
+
+    // The same result was returned.
+    expect(result, same(result1));
+
+    // Change a file.
+    var a_path = convertPath('/workspace/dart/test/lib/a.dart');
+    fileResolver.changeFile(a_path);
+
+    // The was a change to a file, no matter which, resolve again.
+    await resolveFile2(path);
+    expect(fileResolver.testView!.resolvedFiles, [path, path]);
+
+    // Get should get a new result.
+    expect(result, isNot(same(result1)));
+  }
+
   test_reuse_compatibleOptions() async {
     newFile('/workspace/dart/aaa/BUILD', content: '');
     newFile('/workspace/dart/bbb/BUILD', content: '');
diff --git a/tools/VERSION b/tools/VERSION
index e4f0ca7..40d7e5d 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 81
+PRERELEASE 82
 PRERELEASE_PATCH 0
\ No newline at end of file