Version 2.13.0-77.0.dev

Merge commit 'd54c201932f71fcc1935be440e0e642e2d666068' into 'dev'
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index c0f9ae0..122bfc6 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -249,7 +249,7 @@
       }
       channel.sendResponse(Response.unknownRequest(request));
     }, (exception, stackTrace) {
-      AnalysisEngine.instance.instrumentationService.logException(
+      instrumentationService.logException(
         FatalException(
           'Failed to handle request: ${request.method}',
           exception,
diff --git a/pkg/analysis_server/lib/src/analysis_server_abstract.dart b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
index 6ce204b..5589872 100644
--- a/pkg/analysis_server/lib/src/analysis_server_abstract.dart
+++ b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
@@ -39,7 +39,6 @@
 import 'package:analyzer/src/dart/ast/element_locator.dart';
 import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/services/available_declarations.dart';
 import 'package:analyzer/src/util/file_paths.dart' as file_paths;
@@ -336,7 +335,7 @@
     return driver
         .getResult(path, sendCachedToStream: sendCachedToStream)
         .catchError((e, st) {
-      AnalysisEngine.instance.instrumentationService.logException(e, st);
+      instrumentationService.logException(e, st);
       return null;
     });
   }
@@ -351,7 +350,7 @@
         crashReportingAttachmentsBuilder.forExceptionResult(result);
 
     // TODO(39284): should this exception be silent?
-    AnalysisEngine.instance.instrumentationService.logException(
+    instrumentationService.logException(
       SilentException.wrapInMessage(message, result.exception),
       null,
       attachments,
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index 94f48ef..ce8ab76 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -606,7 +606,7 @@
   /// On windows, the directory watcher may overflow, and we must recover.
   void _handleWatchInterruption(dynamic error, StackTrace stackTrace) {
     // We've handled the error, so we only have to log it.
-    AnalysisEngine.instance.instrumentationService
+    _instrumentationService
         .logError('Watcher error; refreshing contexts.\n$error\n$stackTrace');
     // TODO(mfairhurst): Optimize this, or perhaps be less complete.
     refresh();
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index 6c26a54..c77e04a 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -153,17 +153,16 @@
   /// given [offset].
   YamlCompletionResults computeYamlSuggestions(String file, int offset) {
     var provider = server.resourceProvider;
-    if (file_paths.isAnalysisOptionsYaml(provider.pathContext, file)) {
+    var pathContext = provider.pathContext;
+    if (file_paths.isAnalysisOptionsYaml(pathContext, file)) {
       var generator = AnalysisOptionsGenerator(provider);
       return generator.getSuggestions(file, offset);
-    }
-    var fileName = provider.pathContext.basename(file);
-    if (fileName == AnalysisEngine.PUBSPEC_YAML_FILE) {
-      var generator = PubspecGenerator(provider);
-      return generator.getSuggestions(file, offset);
-    } else if (fileName == AnalysisEngine.FIX_DATA_FILE) {
+    } else if (file_paths.isFixDataYaml(pathContext, file)) {
       var generator = FixDataGenerator(provider);
       return generator.getSuggestions(file, offset);
+    } else if (file_paths.isPubspecYaml(pathContext, file)) {
+      var generator = PubspecGenerator(provider);
+      return generator.getSuggestions(file, offset);
     }
     return const YamlCompletionResults.empty();
   }
diff --git a/pkg/analysis_server/lib/src/flutter/flutter_notifications.dart b/pkg/analysis_server/lib/src/flutter/flutter_notifications.dart
index a5d894f..74085faa 100644
--- a/pkg/analysis_server/lib/src/flutter/flutter_notifications.dart
+++ b/pkg/analysis_server/lib/src/flutter/flutter_notifications.dart
@@ -7,7 +7,6 @@
 import 'package:analysis_server/src/protocol_server.dart' as protocol;
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/exception/exception.dart';
-import 'package:analyzer/src/generated/engine.dart';
 
 void sendFlutterNotificationOutline(
     AnalysisServer server, ResolvedUnitResult resolvedUnit) {
@@ -28,8 +27,7 @@
   try {
     f();
   } catch (exception, stackTrace) {
-    AnalysisEngine.instance.instrumentationService.logException(
-        CaughtException.withMessage(
-            'Failed to send notification', exception, stackTrace));
+    server.instrumentationService.logException(CaughtException.withMessage(
+        'Failed to send notification', exception, stackTrace));
   }
 }
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index b84ae47..ea6f4c2 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -23,8 +23,8 @@
 import 'package:analysis_server/src/services/completion/yaml/yaml_completion_generator.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/source/line_info.dart';
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/services/available_declarations.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
 
@@ -97,7 +97,6 @@
     return offset.mapResult((offset) async {
       Future<ErrorOr<List<CompletionItem>>> serverResultsFuture;
       final pathContext = server.resourceProvider.pathContext;
-      final filename = pathContext.basename(path.result);
       final fileExtension = pathContext.extension(path.result);
 
       if (fileExtension == '.dart' && !unit.isError) {
@@ -111,16 +110,12 @@
         );
       } else if (fileExtension == '.yaml') {
         YamlCompletionGenerator generator;
-        switch (filename) {
-          case AnalysisEngine.PUBSPEC_YAML_FILE:
-            generator = PubspecGenerator(server.resourceProvider);
-            break;
-          case AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE:
-            generator = AnalysisOptionsGenerator(server.resourceProvider);
-            break;
-          case AnalysisEngine.FIX_DATA_FILE:
-            generator = FixDataGenerator(server.resourceProvider);
-            break;
+        if (file_paths.isAnalysisOptionsYaml(pathContext, path.result)) {
+          generator = AnalysisOptionsGenerator(server.resourceProvider);
+        } else if (file_paths.isFixDataYaml(pathContext, path.result)) {
+          generator = FixDataGenerator(server.resourceProvider);
+        } else if (file_paths.isPubspecYaml(pathContext, path.result)) {
+          generator = PubspecGenerator(server.resourceProvider);
         }
         if (generator != null) {
           serverResultsFuture = _getServerYamlItems(
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 2f04089..d243989 100644
--- a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
+++ b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
@@ -42,7 +42,6 @@
 import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart' as nd;
 import 'package:analyzer/src/dart/analysis/status.dart' as nd;
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
 import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
@@ -382,7 +381,7 @@
       false,
     ));
 
-    AnalysisEngine.instance.instrumentationService.logException(
+    instrumentationService.logException(
       FatalException(
         message,
         exception,
diff --git a/pkg/analysis_server/lib/src/operation/operation_analysis.dart b/pkg/analysis_server/lib/src/operation/operation_analysis.dart
index e6672f4..aa6443e 100644
--- a/pkg/analysis_server/lib/src/operation/operation_analysis.dart
+++ b/pkg/analysis_server/lib/src/operation/operation_analysis.dart
@@ -12,7 +12,6 @@
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/exception/exception.dart';
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/source.dart';
 
 Future<void> scheduleImplementedNotification(
@@ -32,11 +31,10 @@
             file, computer.classes, computer.members);
         server.sendNotification(params.toNotification());
       } catch (exception, stackTrace) {
-        AnalysisEngine.instance.instrumentationService.logException(
-            CaughtException.withMessage(
-                'Failed to send analysis.implemented notification.',
-                exception,
-                stackTrace));
+        server.instrumentationService.logException(CaughtException.withMessage(
+            'Failed to send analysis.implemented notification.',
+            exception,
+            stackTrace));
       }
     }
   }
@@ -150,8 +148,7 @@
   try {
     f();
   } catch (exception, stackTrace) {
-    AnalysisEngine.instance.instrumentationService.logException(
-        CaughtException.withMessage(
-            'Failed to send notification', exception, stackTrace));
+    server.instrumentationService.logException(CaughtException.withMessage(
+        'Failed to send notification', exception, stackTrace));
   }
 }
diff --git a/pkg/analysis_server/test/edit/bulk_fixes_test.dart b/pkg/analysis_server/test/edit/bulk_fixes_test.dart
index f22d513..6709373 100644
--- a/pkg/analysis_server/test/edit/bulk_fixes_test.dart
+++ b/pkg/analysis_server/test/edit/bulk_fixes_test.dart
@@ -7,7 +7,6 @@
 import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/edit/edit_domain.dart';
 import 'package:analysis_server/src/services/linter/lint_names.dart';
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:linter/src/rules.dart';
 import 'package:meta/meta.dart';
@@ -93,14 +92,13 @@
 
     // Sub-project.
     var subprojectRoot = '$projectPath/test/data/subproject';
-    newFile('$subprojectRoot/${AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE}',
-        content: '''
+    newOptionsFile(subprojectRoot, content: '''
 linter:
   rules:
     - annotate_overrides
 ''');
 
-    newFile('$subprojectRoot/${AnalysisEngine.PUBSPEC_YAML_FILE}', content: '''
+    newPubspecYamlFile(subprojectRoot, '''
 name: subproject
 ''');
 
@@ -118,14 +116,13 @@
 
   Future<void> test_annotateOverrides_subProject() async {
     var subprojectRoot = '$projectPath/test/data/subproject';
-    newFile('$subprojectRoot/${AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE}',
-        content: '''
+    newOptionsFile(subprojectRoot, content: '''
 linter:
   rules:
     - annotate_overrides
 ''');
 
-    newFile('$subprojectRoot/${AnalysisEngine.PUBSPEC_YAML_FILE}', content: '''
+    newPubspecYamlFile(subprojectRoot, '''
 name: subproject
 ''');
 
diff --git a/pkg/analysis_server/test/integration/analysis/analysis_options_test.dart b/pkg/analysis_server/test/integration/analysis/analysis_options_test.dart
index fc5ae08..0324041 100644
--- a/pkg/analysis_server/test/integration/analysis/analysis_options_test.dart
+++ b/pkg/analysis_server/test/integration/analysis/analysis_options_test.dart
@@ -2,7 +2,7 @@
 // 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/src/generated/engine.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -24,7 +24,7 @@
   }
 
   Future<void> test_option_warning_optionFile() async {
-    var options = sourcePath(AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
+    var options = sourcePath(file_paths.analysisOptionsYaml);
     writeFile(options, '''
 linter:
   rules:
diff --git a/pkg/analysis_server/test/integration/analysis/lint_test.dart b/pkg/analysis_server/test/integration/analysis/lint_test.dart
index a7d5164..ffc3d8b 100644
--- a/pkg/analysis_server/test/integration/analysis/lint_test.dart
+++ b/pkg/analysis_server/test/integration/analysis/lint_test.dart
@@ -2,7 +2,7 @@
 // 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/src/generated/engine.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -32,7 +32,7 @@
   }
 
   Future<void> test_simple_lint_optionsFile() async {
-    writeFile(sourcePath(AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE), '''
+    writeFile(sourcePath(file_paths.analysisOptionsYaml), '''
 linter:
   rules:
     - camel_case_types
diff --git a/pkg/analysis_server/test/integration/edit/bulk_fixes_test.dart b/pkg/analysis_server/test/integration/edit/bulk_fixes_test.dart
index 9b42331..4c4d82d 100644
--- a/pkg/analysis_server/test/integration/edit/bulk_fixes_test.dart
+++ b/pkg/analysis_server/test/integration/edit/bulk_fixes_test.dart
@@ -2,7 +2,7 @@
 // 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/src/generated/engine.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -29,7 +29,7 @@
   }
 
   Future<void> test_bulk_fix_override() async {
-    writeFile(sourcePath(AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE), '''
+    writeFile(sourcePath(file_paths.analysisOptionsYaml), '''
 linter:
   rules:
     - annotate_overrides
diff --git a/pkg/analyzer/lib/src/analysis_options/analysis_options_provider.dart b/pkg/analyzer/lib/src/analysis_options/analysis_options_provider.dart
index 9e9cb06..6193114 100644
--- a/pkg/analyzer/lib/src/analysis_options/analysis_options_provider.dart
+++ b/pkg/analyzer/lib/src/analysis_options/analysis_options_provider.dart
@@ -5,10 +5,10 @@
 import 'dart:core';
 
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/source/source_resource.dart';
 import 'package:analyzer/src/task/options.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer/src/util/yaml.dart';
 import 'package:source_span/source_span.dart';
 import 'package:yaml/yaml.dart';
@@ -22,7 +22,7 @@
   AnalysisOptionsProvider([this.sourceFactory]);
 
   /// Provide the options found in
-  /// [root]/[AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE].
+  /// [root]/[file_paths.analysisOptionsYaml].
   /// Recursively merge options referenced by an include directive
   /// and remove the include directive from the resulting options map.
   /// Return an empty options map if the file does not exist.
@@ -41,8 +41,7 @@
   /// then enclosing directories will be searched.
   File? getOptionsFile(Folder root) {
     for (var current in root.withAncestors) {
-      var name = AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
-      var file = current.getChildAssumingFile(name);
+      var file = current.getChildAssumingFile(file_paths.analysisOptionsYaml);
       if (file.exists) {
         return file;
       }
diff --git a/pkg/analyzer/lib/src/context/builder.dart b/pkg/analyzer/lib/src/context/builder.dart
index b5e8a0a..0c6052a 100644
--- a/pkg/analyzer/lib/src/context/builder.dart
+++ b/pkg/analyzer/lib/src/context/builder.dart
@@ -28,6 +28,7 @@
 import 'package:analyzer/src/summary/package_bundle_reader.dart';
 import 'package:analyzer/src/summary/summary_sdk.dart';
 import 'package:analyzer/src/task/options.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer/src/workspace/basic.dart';
 import 'package:analyzer/src/workspace/bazel.dart';
 import 'package:analyzer/src/workspace/gn.dart';
@@ -356,8 +357,7 @@
 
     var folder = resourceProvider.getFolder(path);
     for (var current in folder.withAncestors) {
-      var name = AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
-      var file = current.getChildAssumingFile(name);
+      var file = current.getChildAssumingFile(file_paths.analysisOptionsYaml);
       if (file.exists) {
         return file;
       }
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_locator.dart b/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
index b05e502..f4749c5 100644
--- a/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
@@ -15,6 +15,7 @@
 import 'package:analyzer/src/context/packages.dart';
 import 'package:analyzer/src/dart/analysis/context_root.dart';
 import 'package:analyzer/src/task/options.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer/src/util/yaml.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:glob/glob.dart';
@@ -23,18 +24,6 @@
 
 /// An implementation of a context locator.
 class ContextLocatorImpl implements ContextLocator {
-  /// The name of the analysis options file.
-  static const String ANALYSIS_OPTIONS_NAME = 'analysis_options.yaml';
-
-  /// The name of the `.dart_tool` directory.
-  static const String DOT_DART_TOOL_NAME = '.dart_tool';
-
-  /// The name of the packages file.
-  static const String PACKAGE_CONFIG_JSON_NAME = 'package_config.json';
-
-  /// The name of the packages file.
-  static const String DOT_PACKAGES_NAME = '.packages';
-
   /// The resource provider used to access the file system.
   final ResourceProvider resourceProvider;
 
@@ -112,7 +101,7 @@
       root.included.add(folder);
       root.excludedGlobs = _getExcludedGlobs(root);
       roots.add(root);
-      _createContextRootsIn(roots, folder, excludedFolders, root,
+      _createContextRootsIn(roots, {}, folder, excludedFolders, root,
           root.excludedGlobs, defaultOptionsFile, defaultPackagesFile);
     }
     Map<Folder, ContextRoot> rootMap = <Folder, ContextRoot>{};
@@ -151,6 +140,7 @@
   /// search for nested context roots.
   void _createContextRoots(
       List<ContextRoot> roots,
+      Set<String> visited,
       Folder folder,
       List<Folder> excludedFolders,
       ContextRoot containingRoot,
@@ -192,8 +182,8 @@
       excludedGlobs = _getExcludedGlobs(root);
       root.excludedGlobs = excludedGlobs;
     }
-    _createContextRootsIn(roots, folder, excludedFolders, containingRoot,
-        excludedGlobs, optionsFile, packagesFile);
+    _createContextRootsIn(roots, visited, folder, excludedFolders,
+        containingRoot, excludedGlobs, optionsFile, packagesFile);
   }
 
   /// For each directory within the given [folder] that is neither in the list
@@ -204,6 +194,7 @@
   /// file will be used even if there is a local version of the file.
   void _createContextRootsIn(
       List<ContextRoot> roots,
+      Set<String> visited,
       Folder folder,
       List<Folder> excludedFolders,
       ContextRoot containingRoot,
@@ -223,6 +214,12 @@
       return false;
     }
 
+    // Stop infinite recursion via links.
+    var canonicalFolderPath = folder.resolveSymbolicLinksSync().path;
+    if (!visited.add(canonicalFolderPath)) {
+      return;
+    }
+
     //
     // Check each of the subdirectories to see whether a context root needs to
     // be added for it.
@@ -233,8 +230,8 @@
           if (isExcluded(child)) {
             containingRoot.excluded.add(child);
           } else {
-            _createContextRoots(roots, child, excludedFolders, containingRoot,
-                excludedGlobs, optionsFile, packagesFile);
+            _createContextRoots(roots, visited, child, excludedFolders,
+                containingRoot, excludedGlobs, optionsFile, packagesFile);
           }
         }
       }
@@ -348,19 +345,19 @@
   /// Return the analysis options file in the given [folder], or `null` if the
   /// folder does not contain an analysis options file.
   File? _getOptionsFile(Folder folder) =>
-      _getFile(folder, ANALYSIS_OPTIONS_NAME);
+      _getFile(folder, file_paths.analysisOptionsYaml);
 
   /// Return the packages file in the given [folder], or `null` if the folder
   /// does not contain a packages file.
   File? _getPackagesFile(Folder folder) {
     var file = folder
-        .getChildAssumingFolder(DOT_DART_TOOL_NAME)
-        .getChildAssumingFile(PACKAGE_CONFIG_JSON_NAME);
+        .getChildAssumingFolder(file_paths.dotDartTool)
+        .getChildAssumingFile(file_paths.packageConfigJson);
     if (file.exists) {
       return file;
     }
 
-    return _getFile(folder, DOT_PACKAGES_NAME);
+    return _getFile(folder, file_paths.dotPackages);
   }
 
   /// Add to the given lists of [folders] and [files] all of the resources in
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_root.dart b/pkg/analyzer/lib/src/dart/analysis/context_root.dart
index 583fd5b..830e013 100644
--- a/pkg/analyzer/lib/src/dart/analysis/context_root.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/context_root.dart
@@ -56,13 +56,14 @@
 
   @override
   Iterable<String> analyzedFiles() sync* {
+    var visitedCanonicalPaths = <String>{};
     for (String path in includedPaths) {
       if (!_isExcluded(path)) {
         Resource resource = resourceProvider.getResource(path);
         if (resource is File) {
           yield path;
         } else if (resource is Folder) {
-          yield* _includedFilesInFolder(resource);
+          yield* _includedFilesInFolder(visitedCanonicalPaths, resource);
         } else {
           Type type = resource.runtimeType;
           throw StateError('Unknown resource at path "$path" ($type)');
@@ -78,14 +79,21 @@
 
   /// Return the absolute paths of all of the files that are included in the
   /// given [folder].
-  Iterable<String> _includedFilesInFolder(Folder folder) sync* {
+  Iterable<String> _includedFilesInFolder(
+    Set<String> visited,
+    Folder folder,
+  ) sync* {
     for (Resource resource in folder.getChildren()) {
       String path = resource.path;
       if (!_isExcluded(path)) {
         if (resource is File) {
           yield path;
         } else if (resource is Folder) {
-          yield* _includedFilesInFolder(resource);
+          var canonicalPath = resource.resolveSymbolicLinksSync().path;
+          if (visited.add(canonicalPath)) {
+            yield* _includedFilesInFolder(visited, resource);
+            visited.remove(canonicalPath);
+          }
         } else {
           Type type = resource.runtimeType;
           throw StateError('Unknown resource at path "$path" ($type)');
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index ef912afc..7fa0579 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -86,7 +86,7 @@
 /// TODO(scheglov) Clean up the list of implicitly analyzed files.
 class AnalysisDriver implements AnalysisDriverGeneric {
   /// The version of data format, should be incremented on every format change.
-  static const int DATA_VERSION = 127;
+  static const int DATA_VERSION = 128;
 
   /// The length of the list returned by [_computeDeclaredVariablesSignature].
   static const int _declaredVariablesSignatureLength = 4;
diff --git a/pkg/analyzer/lib/src/dart/analysis/unlinked_api_signature.dart b/pkg/analyzer/lib/src/dart/analysis/unlinked_api_signature.dart
index 7f59be6..b9658e1 100644
--- a/pkg/analyzer/lib/src/dart/analysis/unlinked_api_signature.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/unlinked_api_signature.dart
@@ -17,6 +17,10 @@
 }
 
 class _UnitApiSignatureComputer {
+  static const int _kindConstructorDeclaration = 1;
+  static const int _kindFieldDeclaration = 2;
+  static const int _kindMethodDeclaration = 3;
+
   final ApiSignature signature = ApiSignature();
 
   void addClassOrMixin(ClassOrMixinDeclaration node) {
@@ -28,12 +32,14 @@
     signature.addInt(node.members.length);
     for (var member in node.members) {
       if (member is ConstructorDeclaration) {
+        signature.addInt(_kindConstructorDeclaration);
         addTokens(member.beginToken, member.parameters.endToken);
         if (member.constKeyword != null) {
           addNodeList(member.initializers);
         }
         addNode(member.redirectedConstructor);
       } else if (member is FieldDeclaration) {
+        signature.addInt(_kindFieldDeclaration);
         var variableList = member.fields;
         addVariables(
           member,
@@ -41,6 +47,7 @@
           !member.isStatic && variableList.isFinal && hasConstConstructor,
         );
       } else if (member is MethodDeclaration) {
+        signature.addInt(_kindMethodDeclaration);
         addTokens(
           member.beginToken,
           (member.parameters ?? member.name).endToken,
@@ -48,7 +55,7 @@
         signature.addBool(member.body is EmptyFunctionBody);
         addFunctionBodyModifiers(member.body);
       } else {
-        addNode(member);
+        throw UnimplementedError('(${member.runtimeType}) $member');
       }
     }
 
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index 9e68e5b..79a9088 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -22,8 +22,7 @@
 import 'package:analyzer/src/dart/micro/library_analyzer.dart';
 import 'package:analyzer/src/dart/micro/library_graph.dart';
 import 'package:analyzer/src/exception/exception.dart';
-import 'package:analyzer/src/generated/engine.dart'
-    show AnalysisEngine, AnalysisOptionsImpl;
+import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/summary/api_signature.dart';
 import 'package:analyzer/src/summary/format.dart';
@@ -33,6 +32,7 @@
 import 'package:analyzer/src/summary2/linked_element_factory.dart';
 import 'package:analyzer/src/summary2/reference.dart';
 import 'package:analyzer/src/task/options.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer/src/util/performance/operation_performance.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:yaml/yaml.dart';
@@ -487,8 +487,7 @@
 
   File? _findOptionsFile(Folder folder) {
     for (var current in folder.withAncestors) {
-      var name = AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
-      var file = _getFile(current, name);
+      var file = _getFile(current, file_paths.analysisOptionsYaml);
       if (file != null) {
         return file;
       }
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index 3848bf9..9a4d376 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -87,19 +87,6 @@
 /// The entry point for the functionality provided by the analysis engine. There
 /// is a single instance of this class.
 class AnalysisEngine {
-  /// The file name used for analysis options files.
-  static const String ANALYSIS_OPTIONS_YAML_FILE = 'analysis_options.yaml';
-
-  /// The file name used for the files containing the data for the data-driven
-  /// fixes.
-  static const String FIX_DATA_FILE = 'fix_data.yaml';
-
-  /// The file name used for pubspec files.
-  static const String PUBSPEC_YAML_FILE = 'pubspec.yaml';
-
-  /// The file name used for Android manifest files.
-  static const String ANDROID_MANIFEST_FILE = 'AndroidManifest.xml';
-
   /// The unique instance of this class.
   static final AnalysisEngine instance = AnalysisEngine._();
 
diff --git a/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart b/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart
index ecdfffd..36d2133 100644
--- a/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart
+++ b/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/pubspec/pubspec_warning_code.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer/src/util/yaml.dart';
 import 'package:path/path.dart' as path;
 import 'package:source_span/src/span.dart';
@@ -266,9 +267,7 @@
           _reportErrorForNode(reporter, pathValue(),
               PubspecWarningCode.PATH_DOES_NOT_EXIST, [pathEntry]);
         } else {
-          if (!packageFolder
-              .getChild(AnalysisEngine.PUBSPEC_YAML_FILE)
-              .exists) {
+          if (!packageFolder.getChild(file_paths.pubspecYaml).exists) {
             _reportErrorForNode(reporter, pathValue(),
                 PubspecWarningCode.PATH_PUBSPEC_DOES_NOT_EXIST, [pathEntry]);
           }
diff --git a/pkg/analyzer/lib/src/services/available_declarations.dart b/pkg/analyzer/lib/src/services/available_declarations.dart
index 06acfc4..6fba2c3 100644
--- a/pkg/analyzer/lib/src/services/available_declarations.dart
+++ b/pkg/analyzer/lib/src/services/available_declarations.dart
@@ -338,8 +338,15 @@
   void _findPackages() {
     var pathContext = _tracker._resourceProvider.pathContext;
     var pubPathPrefixToPathList = <String, List<String>>{};
+    var visitedFolderPaths = <String>{};
 
+    /// TODO(scheglov) Use analyzedFiles() ?
     void visitFolder(Folder folder) {
+      var canonicalFolderPath = folder.resolveSymbolicLinksSync().path;
+      if (!visitedFolderPaths.add(canonicalFolderPath)) {
+        return;
+      }
+
       var buildFile = folder.getChildAssumingFile('BUILD');
       var pubspecFile = folder.getChildAssumingFile('pubspec.yaml');
       if (buildFile.exists) {
diff --git a/pkg/analyzer/lib/src/test_utilities/resource_provider_mixin.dart b/pkg/analyzer/lib/src/test_utilities/resource_provider_mixin.dart
index bf332b0..ea5e130 100644
--- a/pkg/analyzer/lib/src/test_utilities/resource_provider_mixin.dart
+++ b/pkg/analyzer/lib/src/test_utilities/resource_provider_mixin.dart
@@ -4,7 +4,6 @@
 
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
-import 'package:analyzer/src/dart/analysis/context_locator.dart';
 import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 
 /// A mixin for test classes that adds a [ResourceProvider] and utility methods
@@ -62,12 +61,12 @@
   }
 
   File newOptionsFile(String directoryPath, {String content = ''}) {
-    String path = join(directoryPath, ContextLocatorImpl.ANALYSIS_OPTIONS_NAME);
+    String path = join(directoryPath, file_paths.analysisOptionsYaml);
     return newFile(path, content: content);
   }
 
   File newPackagesFile(String directoryPath) {
-    String path = join(directoryPath, ContextLocatorImpl.DOT_PACKAGES_NAME);
+    String path = join(directoryPath, file_paths.dotPackages);
     return newFile(path);
   }
 
diff --git a/pkg/analyzer/lib/src/util/file_paths.dart b/pkg/analyzer/lib/src/util/file_paths.dart
index 46e450f..2a6318c 100644
--- a/pkg/analyzer/lib/src/util/file_paths.dart
+++ b/pkg/analyzer/lib/src/util/file_paths.dart
@@ -13,12 +13,18 @@
 /// File name of Android manifest files.
 const String androidManifestXml = 'AndroidManifest.xml';
 
+/// The name of the `.dart_tool` directory.
+const String dotDartTool = '.dart_tool';
+
 /// File name of package spec files.
 const String dotPackages = '.packages';
 
 /// The name of the data file used to specify data-driven fixes.
 const String fixDataYaml = 'fix_data.yaml';
 
+/// The name of the package config files.
+const String packageConfigJson = 'package_config.json';
+
 /// File name of pubspec files.
 const String pubspecYaml = 'pubspec.yaml';
 
@@ -52,8 +58,8 @@
 bool isPackageConfigJson(p.Context pathContext, String path) {
   var components = pathContext.split(path);
   return components.length > 2 &&
-      components[components.length - 1] == 'package_config.json' &&
-      components[components.length - 2] == '.dart_tool';
+      components[components.length - 1] == packageConfigJson &&
+      components[components.length - 2] == dotDartTool;
 }
 
 /// Return `true` if [path] is a `pubspec.yaml` file.
diff --git a/pkg/analyzer/test/source/analysis_options_provider_test.dart b/pkg/analyzer/test/source/analysis_options_provider_test.dart
index f139239..ac4ea13 100644
--- a/pkg/analyzer/test/source/analysis_options_provider_test.dart
+++ b/pkg/analyzer/test/source/analysis_options_provider_test.dart
@@ -7,8 +7,8 @@
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer/src/util/yaml.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -105,7 +105,7 @@
 
   late final AnalysisOptionsProvider provider;
 
-  String get optionsFileName => AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
+  String get analysisOptionsYaml => file_paths.analysisOptionsYaml;
 
   void setUp() {
     var rawProvider = MemoryResourceProvider();
@@ -118,12 +118,12 @@
 
   void test_getOptions_crawlUp_hasInFolder() {
     pathTranslator.newFolder('/foo/bar');
-    pathTranslator.newFile('/foo/$optionsFileName', r'''
+    pathTranslator.newFile('/foo/$analysisOptionsYaml', r'''
 analyzer:
   ignore:
     - foo
 ''');
-    pathTranslator.newFile('/foo/bar/$optionsFileName', r'''
+    pathTranslator.newFile('/foo/bar/$analysisOptionsYaml', r'''
 analyzer:
   ignore:
     - bar
@@ -139,12 +139,12 @@
 
   void test_getOptions_crawlUp_hasInParent() {
     pathTranslator.newFolder('/foo/bar/baz');
-    pathTranslator.newFile('/foo/$optionsFileName', r'''
+    pathTranslator.newFile('/foo/$analysisOptionsYaml', r'''
 analyzer:
   ignore:
     - foo
 ''');
-    pathTranslator.newFile('/foo/bar/$optionsFileName', r'''
+    pathTranslator.newFile('/foo/bar/$analysisOptionsYaml', r'''
 analyzer:
   ignore:
     - bar
@@ -165,7 +165,7 @@
   }
 
   void test_getOptions_empty() {
-    pathTranslator.newFile('/$optionsFileName', r'''#empty''');
+    pathTranslator.newFile('/$analysisOptionsYaml', r'''#empty''');
     YamlMap options = _getOptions('/');
     expect(options, isNotNull);
     expect(options, isEmpty);
@@ -178,7 +178,7 @@
     - ignoreme.dart
     - 'sdk_ext/**'
 ''');
-    pathTranslator.newFile('/$optionsFileName', r'''
+    pathTranslator.newFile('/$analysisOptionsYaml', r'''
 include: foo.include
 ''');
     YamlMap options = _getOptions('/');
@@ -196,7 +196,7 @@
   }
 
   void test_getOptions_include_missing() {
-    pathTranslator.newFile('/$optionsFileName', r'''
+    pathTranslator.newFile('/$analysisOptionsYaml', r'''
 include: /foo.include
 ''');
     YamlMap options = _getOptions('/');
@@ -204,13 +204,13 @@
   }
 
   void test_getOptions_invalid() {
-    pathTranslator.newFile('/$optionsFileName', r''':''');
+    pathTranslator.newFile('/$analysisOptionsYaml', r''':''');
     YamlMap options = _getOptions('/');
     expect(options, hasLength(1));
   }
 
   void test_getOptions_simple() {
-    pathTranslator.newFile('/$optionsFileName', r'''
+    pathTranslator.newFile('/$analysisOptionsYaml', r'''
 analyzer:
   ignore:
     - ignoreme.dart
diff --git a/pkg/analyzer/test/src/context/builder_test.dart b/pkg/analyzer/test/src/context/builder_test.dart
index 7b4e4fa..d650f9c 100644
--- a/pkg/analyzer/test/src/context/builder_test.dart
+++ b/pkg/analyzer/test/src/context/builder_test.dart
@@ -103,8 +103,7 @@
     ];
 
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    newOptionsFile(path, content: '''
 linter:
   rules:
     - mock_lint_rule
@@ -128,8 +127,7 @@
     ];
 
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    newOptionsFile(path, content: '''
 linter:
   rules:
     - mock_lint_rule
@@ -153,8 +151,7 @@
     ];
 
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    newOptionsFile(path, content: '''
 linter:
   rules:
     - mock_lint_rule
@@ -176,8 +173,7 @@
     expected.lintRules = <LintRule>[];
 
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    newOptionsFile(path, content: '''
 ''');
 
     var options = _getAnalysisOptions(builder, path);
@@ -536,8 +532,7 @@
     builderOptions.defaultOptions = defaultOptions;
     AnalysisOptionsImpl expected = AnalysisOptionsImpl();
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    newOptionsFile(path, content: '''
 linter:
   rules:
     - non_existent_lint_rule
@@ -554,8 +549,7 @@
     AnalysisOptionsImpl expected = AnalysisOptionsImpl();
     expected.implicitDynamic = false;
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    newOptionsFile(path, content: '''
 analyzer:
   strong-mode:
     implicit-dynamic: false
@@ -609,8 +603,7 @@
   rules:
     - mock_lint_rule2
 ''');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    newOptionsFile(path, content: '''
 include: bar.yaml
 linter:
   rules:
@@ -623,8 +616,7 @@
 
   void test_getAnalysisOptions_invalid() {
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: ';');
+    newOptionsFile(path, content: ';');
 
     AnalysisOptions options = _getAnalysisOptions(builder, path);
     expect(options, isNotNull);
@@ -632,8 +624,7 @@
 
   void test_getAnalysisOptions_noDefault_noOverrides() {
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    newOptionsFile(path, content: '''
 linter:
   rules:
     - non_existent_lint_rule
@@ -647,8 +638,7 @@
     AnalysisOptionsImpl expected = AnalysisOptionsImpl();
     expected.implicitDynamic = false;
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    newOptionsFile(path, content: '''
 analyzer:
   strong-mode:
     implicit-dynamic: false
@@ -660,22 +650,21 @@
 
   void test_getAnalysisOptions_optionsPath() {
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath, content: '''
+    String filePath = newOptionsFile(path, content: '''
 linter:
   rules:
     - empty_constructor_bodies
-''');
+''').path;
 
     ContextRoot root =
         ContextRoot(path, [], pathContext: resourceProvider.pathContext);
     _getAnalysisOptions(builder, path, contextRoot: root);
-    expect(root.optionsFilePath, equals(filePath));
+    expect(root.optionsFilePath, filePath);
   }
 
   void test_getAnalysisOptions_sdkVersionConstraint() {
     var projectPath = convertPath('/test');
-    newFile(join(projectPath, AnalysisEngine.PUBSPEC_YAML_FILE), content: '''
+    newPubspecYamlFile(projectPath, '''
 environment:
   sdk: ^2.1.0
 ''');
@@ -704,9 +693,7 @@
   void test_getOptionsFile_inParentOfRoot_new() {
     String parentPath = convertPath('/some/directory');
     String path = join(parentPath, 'path');
-    String filePath =
-        join(parentPath, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath);
+    String filePath = newOptionsFile(path).path;
 
     var result = builder.getOptionsFile(path)!;
     expect(result, isNotNull);
@@ -715,8 +702,7 @@
 
   void test_getOptionsFile_inRoot_new() {
     String path = convertPath('/some/directory/path');
-    String filePath = join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-    newFile(filePath);
+    String filePath = newOptionsFile(path).path;
 
     var result = builder.getOptionsFile(path)!;
     expect(result, isNotNull);
diff --git a/pkg/analyzer/test/src/dart/analysis/context_locator_test.dart b/pkg/analyzer/test/src/dart/analysis/context_locator_test.dart
index 116c6f1..469a182 100644
--- a/pkg/analyzer/test/src/dart/analysis/context_locator_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/context_locator_test.dart
@@ -6,6 +6,7 @@
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/dart/analysis/context_locator.dart';
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -41,6 +42,136 @@
     contextLocator = ContextLocatorImpl(resourceProvider: resourceProvider);
   }
 
+  void test_locateRoots_link_file_toOutOfRoot() {
+    Folder rootFolder = newFolder('/home/test');
+    newFile('/home/test/lib/a.dart');
+    newFile('/home/b.dart');
+    resourceProvider.newLink(
+      convertPath('/home/test/lib/c.dart'),
+      convertPath('/home/b.dart'),
+    );
+
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [rootFolder.path]);
+    expect(roots, hasLength(1));
+
+    ContextRoot root = findRoot(roots, rootFolder);
+    expect(root.includedPaths, unorderedEquals([rootFolder.path]));
+    expect(root.excludedPaths, isEmpty);
+    expect(root.optionsFile, isNull);
+    expect(root.packagesFile, isNull);
+
+    _assertAnalyzedFiles(root, [
+      '/home/test/lib/a.dart',
+      '/home/test/lib/c.dart',
+    ]);
+  }
+
+  void test_locateRoots_link_file_toSiblingInRoot() {
+    Folder rootFolder = newFolder('/test');
+    newFile('/test/lib/a.dart');
+    resourceProvider.newLink(
+      convertPath('/test/lib/b.dart'),
+      convertPath('/test/lib/a.dart'),
+    );
+
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [rootFolder.path]);
+    expect(roots, hasLength(1));
+
+    ContextRoot root = findRoot(roots, rootFolder);
+    expect(root.includedPaths, unorderedEquals([rootFolder.path]));
+    expect(root.excludedPaths, isEmpty);
+    expect(root.optionsFile, isNull);
+    expect(root.packagesFile, isNull);
+
+    _assertAnalyzedFiles(root, [
+      '/test/lib/a.dart',
+      '/test/lib/b.dart',
+    ]);
+  }
+
+  void test_locateRoots_link_folder_toParentInRoot() {
+    Folder rootFolder = newFolder('/test');
+    newFile('/test/lib/a.dart');
+    resourceProvider.newLink(
+      convertPath('/test/lib/foo'),
+      convertPath('/test/lib'),
+    );
+
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [rootFolder.path]);
+    expect(roots, hasLength(1));
+
+    ContextRoot root = findRoot(roots, rootFolder);
+    expect(root.includedPaths, unorderedEquals([rootFolder.path]));
+    expect(root.excludedPaths, isEmpty);
+    expect(root.optionsFile, isNull);
+    expect(root.packagesFile, isNull);
+
+    _assertAnalyzedFiles(root, ['/test/lib/a.dart']);
+
+    _assertAnalyzed(root, [
+      '/test/lib/a.dart',
+      '/test/lib/foo/b.dart',
+    ]);
+  }
+
+  void test_locateRoots_link_folder_toParentOfRoot() {
+    Folder rootFolder = newFolder('/home/test');
+    newFile('/home/test/lib/a.dart');
+    newFile('/home/b.dart');
+    newFile('/home/other/c.dart');
+    resourceProvider.newLink(
+      convertPath('/home/test/lib/foo'),
+      convertPath('/home'),
+    );
+
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [rootFolder.path]);
+    expect(roots, hasLength(1));
+
+    ContextRoot root = findRoot(roots, rootFolder);
+    expect(root.includedPaths, unorderedEquals([rootFolder.path]));
+    expect(root.excludedPaths, isEmpty);
+    expect(root.optionsFile, isNull);
+    expect(root.packagesFile, isNull);
+
+    // The set of analyzed files includes everything in `/home`,
+    // but does not repeat `/home/test/lib/a.dart` and does not cycle.
+    _assertAnalyzedFiles(root, [
+      '/home/test/lib/a.dart',
+      '/home/test/lib/foo/b.dart',
+      '/home/test/lib/foo/other/c.dart',
+    ]);
+  }
+
+  void test_locateRoots_link_folder_toSiblingInRoot() {
+    Folder rootFolder = newFolder('/test');
+    newFile('/test/lib/a.dart');
+    newFile('/test/lib/foo/b.dart');
+    resourceProvider.newLink(
+      convertPath('/test/lib/bar'),
+      convertPath('/test/lib/foo'),
+    );
+
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [rootFolder.path]);
+    expect(roots, hasLength(1));
+
+    ContextRoot root = findRoot(roots, rootFolder);
+    expect(root.includedPaths, unorderedEquals([rootFolder.path]));
+    expect(root.excludedPaths, isEmpty);
+    expect(root.optionsFile, isNull);
+    expect(root.packagesFile, isNull);
+
+    _assertAnalyzedFiles(root, [
+      '/test/lib/a.dart',
+      '/test/lib/foo/b.dart',
+      '/test/lib/bar/b.dart',
+    ]);
+  }
+
   void test_locateRoots_multiple_dirAndNestedDir() {
     Folder outerRootFolder = newFolder('/test/outer');
     File outerOptionsFile = newOptionsFile('/test/outer');
@@ -573,9 +704,7 @@
 
   void test_locateRoots_options_withExclude_wholeFolder_includedOptions() {
     Folder rootFolder = newFolder('/test/root');
-    File optionsFile = newFile(
-        '/test/root/${ContextLocatorImpl.ANALYSIS_OPTIONS_NAME}',
-        content: '''
+    File optionsFile = newOptionsFile('/test/root', content: '''
 include: has_excludes.yaml
 ''');
     newFile('/test/root/has_excludes.yaml', content: '''
@@ -609,9 +738,7 @@
 
   void test_locateRoots_options_withExclude_wholeFolder_includedOptionsMerge() {
     Folder rootFolder = newFolder('/test/root');
-    File optionsFile = newFile(
-        '/test/root/${ContextLocatorImpl.ANALYSIS_OPTIONS_NAME}',
-        content: '''
+    File optionsFile = newOptionsFile('/test/root', content: '''
 include: has_excludes.yaml
 analyzer:
   exclude:
@@ -792,6 +919,12 @@
     }
   }
 
+  void _assertAnalyzedFiles(ContextRoot root, List<String> posixPathList) {
+    var analyzedFiles = root.analyzedFiles().toList();
+    var pathList = posixPathList.map(convertPath).toList();
+    expect(analyzedFiles, unorderedEquals(pathList));
+  }
+
   void _assertNotAnalyzed(ContextRoot root, List<String> posixPathList) {
     for (var posixPath in posixPathList) {
       var path = convertPath(posixPath);
@@ -802,8 +935,8 @@
   File _newPackageConfigFile(String directoryPath) {
     String path = join(
       directoryPath,
-      ContextLocatorImpl.DOT_DART_TOOL_NAME,
-      ContextLocatorImpl.PACKAGE_CONFIG_JSON_NAME,
+      file_paths.dotDartTool,
+      file_paths.packageConfigJson,
     );
     return newFile(path);
   }
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
index 72108e0..932abb8 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
@@ -22,6 +22,23 @@
     return driver.test.libraryContext.linkedCycles;
   }
 
+  test_change_factoryConstructor_moveStaticToken() async {
+    await resolveTestCode(r'''
+class A {
+  factory A();
+  static void foo<U>() {}
+}
+''');
+
+    driverFor(testFilePath).changeFile(testFilePath);
+    await resolveTestCode(r'''
+class A {
+  factory A() =
+  static void foo<U>() {}
+}
+''');
+  }
+
   test_change_field_staticFinal_hasConstConstructor_changeInitializer() async {
     useEmptyByteStore();
 
diff --git a/pkg/analyzer/test/src/dart/analysis/unlinked_api_signature_test.dart b/pkg/analyzer/test/src/dart/analysis/unlinked_api_signature_test.dart
index 6fdda84..cb996f9 100644
--- a/pkg/analyzer/test/src/dart/analysis/unlinked_api_signature_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/unlinked_api_signature_test.dart
@@ -242,6 +242,25 @@
 ''');
   }
 
+  /// The token `static` is moving from the field declaration to the factory
+  /// constructor (its redirected constructor), so semantically its meaning
+  /// changes. But we had a bug that we put `static` into the signature
+  /// at the same position, without any separator, so failed to see the
+  /// difference.
+  test_class_factoryConstructor_empty_to_eq() {
+    assertNotSameSignature(r'''
+class A {
+  factory A();
+  static void foo<U>() {}
+}
+''', r'''
+class A {
+  factory A() =
+  static void foo<U>() {}
+}
+''');
+  }
+
   test_class_field_final_add() {
     assertNotSameSignature(r'''
 class C {
diff --git a/pkg/analyzer/test/src/services/available_declarations_test.dart b/pkg/analyzer/test/src/services/available_declarations_test.dart
index 019fe7b..6ba066f 100644
--- a/pkg/analyzer/test/src/services/available_declarations_test.dart
+++ b/pkg/analyzer/test/src/services/available_declarations_test.dart
@@ -211,10 +211,6 @@
     expect(library.id, id);
   }
 
-  @SkippedTest(
-    issue: 'https://github.com/dart-lang/sdk/issues/44501',
-    reason: 'Actually, with fixed ResourceProvider, this test cycles',
-  )
   test_getLibrary_exportViaRecursiveLink() async {
     resourceProvider.newLink(
       convertPath('/home/test/lib/foo'),
diff --git a/pkg/analyzer_cli/lib/src/driver.dart b/pkg/analyzer_cli/lib/src/driver.dart
index 7452a90..1e65979 100644
--- a/pkg/analyzer_cli/lib/src/driver.dart
+++ b/pkg/analyzer_cli/lib/src/driver.dart
@@ -267,9 +267,9 @@
       }
 
       // Analyze the libraries.
+      var pathContext = resourceProvider.pathContext;
       for (var path in filesToAnalyze) {
-        var shortName = resourceProvider.pathContext.basename(path);
-        if (shortName == AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE) {
+        if (file_paths.isAnalysisOptionsYaml(pathContext, path)) {
           var file = resourceProvider.getFile(path);
           var content = file.readAsStringSync();
           var lineInfo = LineInfo.fromContent(content);
@@ -286,7 +286,7 @@
               allResult = allResult.max(severity);
             }
           }
-        } else if (shortName == AnalysisEngine.PUBSPEC_YAML_FILE) {
+        } else if (file_paths.isPubspecYaml(pathContext, path)) {
           var errors = <AnalysisError>[];
           try {
             var file = resourceProvider.getFile(path);
@@ -309,7 +309,7 @@
                 }
               }
               if (visitors.isNotEmpty) {
-                var sourceUri = resourceProvider.pathContext.toUri(path);
+                var sourceUri = pathContext.toUri(path);
                 var pubspecAst = Pubspec.parse(content,
                     sourceUrl: sourceUri, resourceProvider: resourceProvider);
                 var listener = RecordingErrorListener();
@@ -339,7 +339,7 @@
           } catch (exception) {
             // If the file cannot be analyzed, ignore it.
           }
-        } else if (shortName == AnalysisEngine.ANDROID_MANIFEST_FILE) {
+        } else if (file_paths.isAndroidManifestXml(pathContext, path)) {
           try {
             var file = resourceProvider.getFile(path);
             var content = file.readAsStringSync();
diff --git a/pkg/analyzer_cli/test/driver_test.dart b/pkg/analyzer_cli/test/driver_test.dart
index 61ea617..249b959 100644
--- a/pkg/analyzer_cli/test/driver_test.dart
+++ b/pkg/analyzer_cli/test/driver_test.dart
@@ -11,6 +11,7 @@
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/summary2/package_bundle_format.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer/src/util/sdk.dart';
 import 'package:analyzer_cli/src/ansi.dart' as ansi;
 import 'package:analyzer_cli/src/driver.dart';
@@ -59,9 +60,9 @@
   }) async {
     filePath = _posixToPlatformPath(filePath);
 
-    var optionsFileName = AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
-    var options =
-        _posixToPlatformPath('data/options_tests_project/' + optionsFileName);
+    var options = _posixToPlatformPath(
+      'data/options_tests_project/' + file_paths.analysisOptionsYaml,
+    );
 
     var args = <String>[];
     args.add('--build-mode');
@@ -804,7 +805,7 @@
 
 @reflectiveTest
 class LinterTest extends BaseTest {
-  String get optionsFileName => AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
+  String get analysisOptionsYaml => file_paths.analysisOptionsYaml;
 
   Future<void> test_containsLintRuleEntry() async {
     var options = _parseOptions('''
@@ -882,7 +883,7 @@
 
   Future<void> test_pubspec_lintsInOptions_generatedLints() async {
     await drive('data/linter_project/pubspec.yaml',
-        options: 'data/linter_project/$optionsFileName');
+        options: 'data/linter_project/$analysisOptionsYaml');
     expect(bulletToDash(outSink), contains('lint - Sort pub dependencies'));
   }
 
@@ -891,17 +892,17 @@
 
   Future<void> _runLinter_defaultLints() async {
     await drive('data/linter_project/test_file.dart',
-        options: 'data/linter_project/$optionsFileName', args: ['--lints']);
+        options: 'data/linter_project/$analysisOptionsYaml', args: ['--lints']);
   }
 
   Future<void> _runLinter_lintsInOptions() async {
     await drive('data/linter_project/test_file.dart',
-        options: 'data/linter_project/$optionsFileName', args: ['--lints']);
+        options: 'data/linter_project/$analysisOptionsYaml', args: ['--lints']);
   }
 
   Future<void> _runLinter_noLintsFlag() async {
     await drive('data/no_lints_project/test_file.dart',
-        options: 'data/no_lints_project/$optionsFileName');
+        options: 'data/no_lints_project/$analysisOptionsYaml');
   }
 }
 
@@ -909,8 +910,7 @@
 class NonDartFilesTest extends BaseTest {
   Future<void> test_analysisOptionsYaml() async {
     await withTempDirAsync((tempDir) async {
-      var filePath =
-          path.join(tempDir, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
+      var filePath = path.join(tempDir, file_paths.analysisOptionsYaml);
       File(filePath).writeAsStringSync('''
 analyzer:
   string-mode: true
@@ -926,15 +926,13 @@
 
   Future<void> test_manifestFileChecks() async {
     await withTempDirAsync((tempDir) async {
-      var filePath =
-          path.join(tempDir, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
+      var filePath = path.join(tempDir, file_paths.analysisOptionsYaml);
       File(filePath).writeAsStringSync('''
 analyzer:
   optional-checks:
     chrome-os-manifest-checks: true
 ''');
-      var manifestPath =
-          path.join(tempDir, AnalysisEngine.ANDROID_MANIFEST_FILE);
+      var manifestPath = path.join(tempDir, file_paths.androidManifestXml);
       File(manifestPath).writeAsStringSync('''
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android">
@@ -953,7 +951,7 @@
 
   Future<void> test_pubspecYaml() async {
     await withTempDirAsync((tempDir) async {
-      var filePath = path.join(tempDir, AnalysisEngine.PUBSPEC_YAML_FILE);
+      var filePath = path.join(tempDir, file_paths.pubspecYaml);
       File(filePath).writeAsStringSync('''
 name: foo
 flutter:
@@ -972,7 +970,7 @@
 
 @reflectiveTest
 class OptionsTest extends BaseTest {
-  String get optionsFileName => AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE;
+  String get analysisOptionsYaml => file_paths.analysisOptionsYaml;
 
   List<ErrorProcessor> get processors => analysisOptions.errorProcessors;
 
@@ -981,7 +979,7 @@
 
   Future<void> test_analysisOptions_excludes() async {
     await drive('data/exclude_test_project',
-        options: 'data/exclude_test_project/$optionsFileName');
+        options: 'data/exclude_test_project/$analysisOptionsYaml');
     _expectUndefinedClassErrorsWithoutExclusions();
   }
 
@@ -990,7 +988,7 @@
     // The exclude is relative to the project, not/ the analyzed path, and it
     // has to then understand that.
     await drive('data/exclude_test_project',
-        options: 'data/exclude_test_project/$optionsFileName');
+        options: 'data/exclude_test_project/$analysisOptionsYaml');
     _expectUndefinedClassErrorsWithoutExclusions();
   }
 
@@ -1066,7 +1064,7 @@
   Future<void> test_withFlags_overrideFatalWarning() async {
     await drive('data/options_tests_project/test_file.dart',
         args: ['--fatal-warnings'],
-        options: 'data/options_tests_project/$optionsFileName');
+        options: 'data/options_tests_project/$analysisOptionsYaml');
 
     // missing_return: error
     var undefined_function = AnalysisError(
@@ -1082,7 +1080,7 @@
 
   Future<void> _driveBasic() async {
     await drive('data/options_tests_project/test_file.dart',
-        options: 'data/options_tests_project/$optionsFileName');
+        options: 'data/options_tests_project/$analysisOptionsYaml');
   }
 
   void _expectUndefinedClassErrorsWithoutExclusions() {
diff --git a/tools/VERSION b/tools/VERSION
index 147ceb0..846cd9c 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 76
+PRERELEASE 77
 PRERELEASE_PATCH 0
\ No newline at end of file