Version 3.15.0-145.0.dev

Merge ff8d9b658754ac09bec3ac7aba4f1afe27a6c09f into dev
diff --git a/DEPS b/DEPS
index c9ed3b0..f312545 100644
--- a/DEPS
+++ b/DEPS
@@ -133,22 +133,22 @@
   ### /third_party/pkg dependencies
   # 'tools/rev_sdk_deps.dart' will rev pkg dependencies to their latest; put an
   # EOL comment after a dependency to instead pin at the current revision.
-  "core_rev": "36f57a994965785d641e436b7dd3aa8a18a26f62",
-  "dartdoc_rev": "03ec6df6228ac8257a737261d17e746c665b1e13",
-  "ecosystem_rev": "54e16739d9c15ef0b77e050242c16226ad9235f9",
+  "core_rev": "fa0c6b77d67b6837eb2891e251c40cdfbeab1f4d",
+  "dartdoc_rev": "7e2508c61a41443c99b78f5ca26ddc456cf0c1c2",
+  "ecosystem_rev": "1d2b5ff862c6b93cd86dd06eadac4c9f4d5d6c20",
   "flute_rev": "b84119fba67016a80c3eb80765762bcc4d0d0b8d",
-  "http_rev": "4646fe66ca78423f28333ca7f46c543dc902daf8",
+  "http_rev": "2bac218a1115a58addd140d7a7eaabf803d57631",
   "i18n_rev": "7475ce643ed6210e5e787b310bb5bc24de9a2158",
   "leak_tracker_rev": "f5620600a5ce1c44f65ddaa02001e200b096e14c", # rolled manually
   "material_color_utilities_rev": "799b6ba2f3f1c28c67cc7e0b4f18e0c7d7f3c03e",
-  "native_rev": "6287745caa4c742a975a7958387fa07b909e9957",
+  "native_rev": "b21ef2da3c580377f9465eac7f8ce22a097c0de6",
   "protobuf_rev": "19191c8c5881d4a63db804bbed26fa1dcdd17ed0",
   "pub_rev": "9df389a76385f82c1165c7cf93b78807b5ea8890", # rolled manually
-  "shelf_rev": "9946de555f1eb3abd8ae5054848b9c3c2bd36e4d",
+  "shelf_rev": "b175e9f1407994c751c1c1c976c3c80e2971e85c",
   "sync_http_rev": "6666fff944221891182e1f80bf56569338164d72",
   "tar_rev": "201952a191be34a993f4ae182612c891ea4ef0a2",
-  "test_rev": "2a9e01756f42123102a34faf2964130ba7e0f77a",
-  "tools_rev": "727fd9c2fac0efe5eb60d23ef8a24ef90b1326d0",
+  "test_rev": "3533ae1e81fdf9aab93f1d579cddeb5c5b3a2839",
+  "tools_rev": "589d844c4e0c363ca6d2811eb328aede33f0cf30",
   "vector_math_rev": "cf3b5db7340d317dd3489e5a35434b408020a852",
   "web_rev": "4e60e61e55e1e2111f0fec9d19efff577c0dc671",
   "webdriver_rev": "8e5c611ea242efdc9f597a8a37c38a9c586120ee",
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart
index d56215f..1c7ae5f 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart
@@ -116,7 +116,7 @@
     var paramString = hasParams ? (parameters == '()' ? '()' : '(…)') : '';
 
     var name = declaration.name;
-    var containerName = declaration.className ?? declaration.mixinName;
+    var containerName = declaration.containerName;
     // Constructors are always prefixed with the class name, and we never show
     // 'new'.
     var fullName = switch ((isConstructor, name)) {
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index 92e44af..83c03ca 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -895,6 +895,14 @@
     });
   }
 
+  void setWorkspaceSymbolKinds(List<SymbolKind> kinds) {
+    workspaceCapabilities = extendWorkspaceCapabilities(workspaceCapabilities, {
+      'symbol': {
+        'symbolKind': {'valueSet': kinds.map((k) => k.toJson()).toList()},
+      },
+    });
+  }
+
   WorkspaceClientCapabilities _withResourceOperationKinds(
     WorkspaceClientCapabilities source,
     List<ResourceOperationKind> kinds,
diff --git a/pkg/analysis_server/test/lsp/workspace_symbols_test.dart b/pkg/analysis_server/test/lsp/workspace_symbols_test.dart
index 5dcc5a0..62e5455 100644
--- a/pkg/analysis_server/test/lsp/workspace_symbols_test.dart
+++ b/pkg/analysis_server/test/lsp/workspace_symbols_test.dart
@@ -310,6 +310,48 @@
     expect(await getWorkspaceSymbols('LocalClass12345'), isNotEmpty);
   }
 
+  Future<void> test_enum() async {
+    const content = '''
+enum NamedEnum { x }
+''';
+    var code = TestCode.parse(content);
+    newFile(mainFilePath, code.code);
+
+    failTestOnErrorDiagnostic = false;
+    await provideConfig(initialize, {
+      'includeDependenciesInWorkspaceSymbols': false,
+    });
+
+    var symbols = await getWorkspaceSymbols('Named');
+    expect(symbols, hasLength(1));
+    var symbol = symbols.single;
+    expect(symbol.containerName, isNull);
+    expect(symbol.name, 'NamedEnum');
+    expect(symbol.kind, SymbolKind.Enum);
+  }
+
+  Future<void> test_enum_member() async {
+    setWorkspaceSymbolKinds([SymbolKind.Enum, SymbolKind.EnumMember]);
+
+    const content = '''
+enum NamedEnum { namedEnumConstant }
+''';
+    var code = TestCode.parse(content);
+    newFile(mainFilePath, code.code);
+
+    failTestOnErrorDiagnostic = false;
+    await provideConfig(initialize, {
+      'includeDependenciesInWorkspaceSymbols': false,
+    });
+
+    var symbols = await getWorkspaceSymbols('namedEnumCon');
+    expect(symbols, hasLength(1));
+    var symbol = symbols.single;
+    expect(symbol.containerName, 'NamedEnum');
+    expect(symbol.name, 'namedEnumConstant');
+    expect(symbol.kind, SymbolKind.EnumMember);
+  }
+
   Future<void> test_enum_unnamed() async {
     const content = '''
 enum {
@@ -365,6 +407,28 @@
     expect(namedExtensions.containerName, isNull);
   }
 
+  Future<void> test_extensionType_member() async {
+    const content = '''
+extension NamedExtensionType on int {
+  void namedMethod() {}
+}
+''';
+    var code = TestCode.parse(content);
+    newFile(mainFilePath, code.code);
+
+    failTestOnErrorDiagnostic = false;
+    await provideConfig(initialize, {
+      'includeDependenciesInWorkspaceSymbols': false,
+    });
+
+    var symbols = await getWorkspaceSymbols('namedMet');
+    expect(symbols, hasLength(1));
+    var symbol = symbols.single;
+    expect(symbol.containerName, 'NamedExtensionType');
+    expect(symbol.name, 'namedMethod()');
+    expect(symbol.kind, SymbolKind.Method);
+  }
+
   Future<void> test_extensionType_method() async {
     const content = r'''
 extension type E(int it) {
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index c1a3755..63d1306 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -932,10 +932,11 @@
       required MetricsSuggestionListener listener,
       required CompletionMetrics metrics,
     }) async {
+      assert(expectedCompletion.syntacticEntity.length >= options.prefixLength);
       var stopwatch = Stopwatch()..start();
       var request = DartCompletionRequest.forResolvedUnit(
         resolvedUnit: resolvedUnitResult,
-        offset: expectedCompletion.offset,
+        offset: expectedCompletion.offset + options.prefixLength,
       );
 
       var opType = OpType.forCompletion(request.target, request.offset);
@@ -1038,10 +1039,16 @@
       var charsBeforeTop = _computeCharsBeforeTop(
         expectedCompletion,
         suggestions,
+        targetRank: rank,
       );
       metrics.charsBeforeTop.addValue(charsBeforeTop);
       metrics.charsBeforeTopFive.addValue(
-        _computeCharsBeforeTop(expectedCompletion, suggestions, minRank: 5),
+        _computeCharsBeforeTop(
+          expectedCompletion,
+          suggestions,
+          targetRank: rank,
+          minRank: 5,
+        ),
       );
       metrics.insertionLengthTheoretical.addValue(
         expectedCompletion.completion.length - charsBeforeTop,
@@ -1548,21 +1555,36 @@
   @override
   void setupForResolution(AnalysisContext context) {}
 
+  /// Computes the number of characters that need to be typed before [target]
+  /// with [targetRank] would be in the top [minRank] results.
   int _computeCharsBeforeTop(
     ExpectedCompletion target,
     List<CandidateSuggestion> suggestions, {
+    required int targetRank,
     int minRank = 1,
   }) {
-    var rank = placementInSuggestionList(suggestions, target).rank;
-    if (rank <= minRank) {
+    if (targetRank <= minRank) {
       return 0;
     }
+    // Ranks are 1-based, so `target` is at index `targetRank - 1`. Only
+    // suggestions before `targetIndex` can still rank ahead of `target` after
+    // filtering, so we only need to check those, not the entire list.
+    var targetIndex = targetRank - 1;
     var expected = target.completion;
     for (var i = 1; i < expected.length + 1; i++) {
       var prefix = expected.substring(0, i);
-      var filteredSuggestions = _filterSuggestions(prefix, suggestions);
-      rank = placementInSuggestionList(filteredSuggestions, target).rank;
-      if (rank <= minRank) {
+      var newRank = 1;
+      for (var j = 0; j < targetIndex; j++) {
+        // TODO(brianwilkerson): Replace `startsWith` with a more realistic
+        //  filtering algorithm.
+        if (suggestions[j].completion.startsWith(prefix)) {
+          newRank++;
+          if (newRank > minRank) {
+            break;
+          }
+        }
+      }
+      if (newRank <= minRank) {
         return i;
       }
     }
@@ -1591,17 +1613,6 @@
     return collector.suggestions;
   }
 
-  List<CandidateSuggestion> _filterSuggestions(
-    String prefix,
-    List<CandidateSuggestion> suggestions,
-  ) {
-    // TODO(brianwilkerson): Replace this with a more realistic filtering
-    //  algorithm.
-    return suggestions
-        .where((suggestion) => suggestion.completion.startsWith(prefix))
-        .toList();
-  }
-
   void _printWorstResults(String title, List<CompletionResult> worstResults) {
     List<String> suggestionRow(int rank, SuggestionData data) {
       var suggestion = data.suggestion;
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics_base.dart b/pkg/analysis_server/tool/code_completion/completion_metrics_base.dart
index c2aabec..bcb3d37 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics_base.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics_base.dart
@@ -171,12 +171,11 @@
     var length = expectedCompletion.syntacticEntity.length;
     assert(offset >= 0);
     assert(length > 0);
+    assert(length >= prefixLength);
     var tokenEndOffset = offset + length;
-    if (length >= prefixLength) {
-      // Rather than removing the whole token, remove the characters after
-      // the given prefix length.
-      offset += prefixLength;
-    }
+    // Rather than removing the whole token, remove the characters after
+    // the given prefix length.
+    offset += prefixLength;
     if (overlay == OverlayMode.removeToken) {
       return content.substring(0, offset) + content.substring(tokenEndOffset);
     } else if (overlay == OverlayMode.removeRestOfFile) {
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics_client.dart b/pkg/analysis_server/tool/code_completion/completion_metrics_client.dart
index 1e8a09c..251d2ba 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics_client.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics_client.dart
@@ -540,10 +540,11 @@
     ExpectedCompletion expectedCompletion,
     AnalysisContext context,
   ) async {
+    assert(expectedCompletion.syntacticEntity.length >= options.prefixLength);
     var stopwatch = Stopwatch()..start();
     var suggestionsData = await client.requestCompletion(
       expectedCompletion.filePath,
-      expectedCompletion.offset,
+      expectedCompletion.offset + options.prefixLength,
       1000,
     );
     stopwatch.stop();
diff --git a/pkg/analysis_server/tool/code_completion/visitors.dart b/pkg/analysis_server/tool/code_completion/visitors.dart
index dc57a7e..fca6cc3 100644
--- a/pkg/analysis_server/tool/code_completion/visitors.dart
+++ b/pkg/analysis_server/tool/code_completion/visitors.dart
@@ -150,6 +150,8 @@
   /// The offset from the location of each entity passed to
   /// [safelyRecordEntity], to be used as the column for each
   /// [ExpectedCompletion] created.
+  ///
+  /// Entities shorter than this offset are ignored.
   final int _caretOffset;
 
   /// The completions that are expected to be produced in the file being
@@ -182,14 +184,15 @@
     protocol.CompletionSuggestionKind? kind,
     protocol.ElementKind? elementKind,
   }) {
-    // Only record if this entity is not null, has a length, etc.
-    if (entity != null && entity.offset >= 0 && entity.length > 0) {
+    // Only record if this entity is not null, has offset/length, etc. and is at
+    // least at long as the caret offset.
+    if (entity != null &&
+        entity.offset >= 0 &&
+        entity.length > 0 &&
+        entity.length >= _caretOffset) {
       var location = lineInfo.getLocation(entity.offset);
       var lineNumber = location.lineNumber;
-      var columnNumber = location.columnNumber;
-      if (entity.length >= _caretOffset) {
-        columnNumber += _caretOffset;
-      }
+      var columnNumber = location.columnNumber + _caretOffset;
 
       bool isKeyword() => kind == protocol.CompletionSuggestionKind.KEYWORD;
 
diff --git a/pkg/analyzer/lib/src/dart/analysis/search.dart b/pkg/analyzer/lib/src/dart/analysis/search.dart
index 400e744..572381d 100644
--- a/pkg/analyzer/lib/src/dart/analysis/search.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/search.dart
@@ -144,10 +144,18 @@
   final int column;
   final int codeOffset;
   final int codeLength;
-  final String? className;
-  final String? mixinName;
   final String? parameters;
 
+  /// The name of the containing element, if it is an [InstanceElement].
+  final String? containerName;
+
+  /// The name of the containing element if it is an interface element but not
+  /// a mixin or enum.
+  final String? className;
+
+  /// The name of the containing element if it is a mixin.
+  final String? mixinName;
+
   new(
     this.fileIndex,
     this.lineInfo,
@@ -158,6 +166,7 @@
     this.column,
     this.codeOffset,
     this.codeLength,
+    this.containerName,
     this.className,
     this.mixinName,
     this.parameters,
@@ -1534,6 +1543,11 @@
 
     var enclosing = element.enclosingElement;
 
+    String? containerName = switch (enclosing) {
+      InstanceElement() => enclosing.name,
+      _ => null,
+    };
+
     String? className;
     String? mixinName;
     if (enclosing is EnumElement) {
@@ -1607,6 +1621,7 @@
         locationStart.columnNumber,
         fragmentImpl.codeOffset ?? 0,
         fragmentImpl.codeLength ?? 0,
+        containerName,
         className,
         mixinName,
         parameters,
diff --git a/pkg/analyzer/test/src/dart/analysis/search_test.dart b/pkg/analyzer/test/src/dart/analysis/search_test.dart
index 0b9ce49..11bb444 100644
--- a/pkg/analyzer/test/src/dart/analysis/search_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/search_test.dart
@@ -315,29 +315,35 @@
   FIELD f
     offset: 16 2:7
     codeOffset: 12 + 5
+    containerName: C
     className: C
   CONSTRUCTOR new
     offset: 21 3:3
     codeOffset: 21 + 4
+    containerName: C
     className: C
     parameters: ()
   CONSTRUCTOR named
     offset: 30 4:5
     codeOffset: 28 + 10
+    containerName: C
     className: C
     parameters: ()
   GETTER g
     offset: 49 5:11
     codeOffset: 41 + 15
+    containerName: C
     className: C
   SETTER s
     offset: 68 6:12
     codeOffset: 59 + 16
+    containerName: C
     className: C
     parameters: (dynamic _)
   METHOD m
     offset: 83 7:8
     codeOffset: 78 + 11
+    containerName: C
     className: C
     parameters: ()
 ''',
@@ -467,12 +473,15 @@
   ENUM_CONSTANT a
     offset: 11 2:3
     codeOffset: 11 + 1
+    containerName: E
   ENUM_CONSTANT bb
     offset: 14 2:6
     codeOffset: 14 + 2
+    containerName: E
   ENUM_CONSTANT ccc
     offset: 18 2:10
     codeOffset: 18 + 3
+    containerName: E
 ''',
     );
   }
@@ -529,16 +538,20 @@
   FIELD f
     offset: 27 2:7
     codeOffset: 23 + 5
+    containerName: E
   GETTER g
     offset: 40 3:11
     codeOffset: 32 + 15
+    containerName: E
   SETTER s
     offset: 59 4:12
     codeOffset: 50 + 16
+    containerName: E
     parameters: (dynamic _)
   METHOD m
     offset: 74 5:8
     codeOffset: 69 + 11
+    containerName: E
     parameters: ()
 ''',
     );
@@ -572,20 +585,24 @@
   CONSTRUCTOR new
     offset: 15 1:16
     codeOffset: 15 + 9
+    containerName: E
     className: E
     parameters: (int it)
   GETTER g
     offset: 37 2:11
     codeOffset: 29 + 15
+    containerName: E
     className: E
   SETTER s
     offset: 56 3:12
     codeOffset: 47 + 16
+    containerName: E
     className: E
     parameters: (dynamic _)
   METHOD m
     offset: 71 4:8
     codeOffset: 66 + 11
+    containerName: E
     className: E
     parameters: ()
 ''',
@@ -688,19 +705,23 @@
   FIELD f
     offset: 16 2:7
     codeOffset: 12 + 5
+    containerName: M
     mixinName: M
   GETTER g
     offset: 29 3:11
     codeOffset: 21 + 15
+    containerName: M
     mixinName: M
   SETTER s
     offset: 48 4:12
     codeOffset: 39 + 16
+    containerName: M
     mixinName: M
     parameters: (dynamic _)
   METHOD m
     offset: 63 5:8
     codeOffset: 58 + 11
+    containerName: M
     mixinName: M
     parameters: ()
 ''',
@@ -786,10 +807,12 @@
   GETTER g
     offset: 20 2:11
     codeOffset: 12 + 15
+    containerName: C
     className: C
   METHOD m
     offset: 35 3:8
     codeOffset: 30 + 26
+    containerName: C
     className: C
     parameters: (int a, double b)
   FUNCTION f
@@ -869,16 +892,19 @@
   METHOD m1
     offset: 24 2:8
     codeOffset: 19 + 30
+    containerName: A
     className: A
     parameters: (Map<int, String> a)
   METHOD m2
     offset: 57 3:8
     codeOffset: 52 + 26
+    containerName: A
     className: A
     parameters: (Map<T, U> a)
   METHOD m3
     offset: 86 4:8
     codeOffset: 81 + 50
+    containerName: A
     className: A
     parameters: (Map<Map<T2, U2>, Map<U1, T>> a)
 ''',
@@ -1351,11 +1377,13 @@
   CONSTRUCTOR bar
     offset: 18 2:7
     codeOffset: 14 + 26
+    containerName: Foo
     className: Foo
     parameters: ()
   METHOD bar
     offset: 48 5:8
     codeOffset: 43 + 24
+    containerName: Foo
     className: Foo
     parameters: ()
 ''',
@@ -11147,6 +11175,11 @@
           '${declaration.codeLength}',
         );
 
+        var containerName = declaration.containerName;
+        if (containerName != null) {
+          buffer.writeln('    containerName: $containerName');
+        }
+
         var className = declaration.className;
         if (className != null) {
           buffer.writeln('    className: $className');
diff --git a/tools/VERSION b/tools/VERSION
index cd4b230..b5f2339 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 3
 MINOR 15
 PATCH 0
-PRERELEASE 144
+PRERELEASE 145
 PRERELEASE_PATCH 0