Version 2.18.0-284.0.dev

Merge commit 'df2b0426dfd8385088114c1b80a5936135a2ee90' into 'dev'
diff --git a/pkg/analysis_server/lib/src/computer/computer_call_hierarchy.dart b/pkg/analysis_server/lib/src/computer/computer_call_hierarchy.dart
index b6f563d..82a4176 100644
--- a/pkg/analysis_server/lib/src/computer/computer_call_hierarchy.dart
+++ b/pkg/analysis_server/lib/src/computer/computer_call_hierarchy.dart
@@ -78,6 +78,15 @@
   /// The range of the code for the declaration of this item.
   final SourceRange codeRange;
 
+  CallHierarchyItem({
+    required this.displayName,
+    required this.containerName,
+    required this.kind,
+    required this.file,
+    required this.nameRange,
+    required this.codeRange,
+  });
+
   CallHierarchyItem.forElement(Element element)
       : displayName = _getDisplayName(element),
         nameRange = _nameRangeForElement(element),
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart
new file mode 100644
index 0000000..725ea58
--- /dev/null
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart
@@ -0,0 +1,426 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// 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 'dart:async';
+
+import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
+import 'package:analysis_server/lsp_protocol/protocol_special.dart';
+import 'package:analysis_server/src/computer/computer_call_hierarchy.dart'
+    as call_hierarchy;
+import 'package:analysis_server/src/lsp/handlers/handlers.dart';
+import 'package:analysis_server/src/lsp/mapping.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/analysis/session.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/source/source_range.dart';
+
+/// A handler for `callHierarchy/incoming` that returns the incoming calls for
+/// the target supplied by the client.
+class IncomingCallHierarchyHandler extends _AbstractCallHierarchyCallsHandler<
+    CallHierarchyIncomingCallsParams,
+    CallHierarchyIncomingCallsResult,
+    CallHierarchyIncomingCall> with _CallHierarchyUtils {
+  IncomingCallHierarchyHandler(super.server);
+  @override
+  Method get handlesMessage => Method.callHierarchy_incomingCalls;
+
+  @override
+  LspJsonHandler<CallHierarchyIncomingCallsParams> get jsonHandler =>
+      CallHierarchyIncomingCallsParams.jsonHandler;
+
+  /// Fetches incoming calls from a [call_hierarchy.DartCallHierarchyComputer].
+  ///
+  /// This method is invoked by the superclass which handles similar logic for
+  /// both incoming and outgoing calls.
+  @override
+  Future<List<call_hierarchy.CallHierarchyCalls>> getCalls(
+    call_hierarchy.DartCallHierarchyComputer computer,
+    call_hierarchy.CallHierarchyItem target,
+  ) =>
+      computer.findIncomingCalls(target, server.searchEngine);
+
+  /// Handles the request by passing the target item to a shared implementation
+  /// in the superclass.
+  @override
+  Future<ErrorOr<CallHierarchyIncomingCallsResult>> handle(
+          CallHierarchyIncomingCallsParams params,
+          MessageInfo message,
+          CancellationToken token) =>
+      handleCalls(params.item);
+
+  /// Converts a server [call_hierarchy.CallHierarchyCalls] into the correct LSP
+  /// type for incoming calls.
+  @override
+  CallHierarchyIncomingCall toCall(
+    call_hierarchy.CallHierarchyCalls calls, {
+    required LineInfo localLineInfo,
+    required LineInfo itemLineInfo,
+    required Set<SymbolKind> supportedSymbolKinds,
+  }) {
+    return CallHierarchyIncomingCall(
+      from: toLspItem(
+        calls.item,
+        itemLineInfo,
+        supportedSymbolKinds: supportedSymbolKinds,
+      ),
+      fromRanges: calls.ranges
+          // For incoming calls, ranges are in the referenced item so we use
+          // itemLineInfo and not localLineInfo (which is for the original
+          // target we're collecting calls to).
+          .map((call) => sourceRangeToRange(itemLineInfo, call))
+          .toList(),
+    );
+  }
+}
+
+/// A handler for `callHierarchy/outgoing` that returns the outgoing calls for
+/// the target supplied by the client.
+class OutgoingCallHierarchyHandler extends _AbstractCallHierarchyCallsHandler<
+    CallHierarchyOutgoingCallsParams,
+    CallHierarchyOutgoingCallsResult,
+    CallHierarchyOutgoingCall> with _CallHierarchyUtils {
+  OutgoingCallHierarchyHandler(super.server);
+  @override
+  Method get handlesMessage => Method.callHierarchy_outgoingCalls;
+
+  @override
+  LspJsonHandler<CallHierarchyOutgoingCallsParams> get jsonHandler =>
+      CallHierarchyOutgoingCallsParams.jsonHandler;
+
+  /// Fetches outgoing calls from a [call_hierarchy.DartCallHierarchyComputer].
+  ///
+  /// This method is invoked by the superclass which handles similar logic for
+  /// both incoming and outgoing calls.
+  @override
+  Future<List<call_hierarchy.CallHierarchyCalls>> getCalls(
+    call_hierarchy.DartCallHierarchyComputer computer,
+    call_hierarchy.CallHierarchyItem target,
+  ) =>
+      computer.findOutgoingCalls(target);
+
+  /// Handles the request by passing the target item to a shared implementation
+  /// in the superclass.
+  @override
+  Future<ErrorOr<CallHierarchyOutgoingCallsResult>> handle(
+          CallHierarchyOutgoingCallsParams params,
+          MessageInfo message,
+          CancellationToken token) =>
+      handleCalls(params.item);
+
+  /// Converts a server [call_hierarchy.CallHierarchyCalls] into the correct LSP
+  /// type for outgoing calls.
+  @override
+  CallHierarchyOutgoingCall toCall(
+    call_hierarchy.CallHierarchyCalls calls, {
+    required LineInfo localLineInfo,
+    required LineInfo itemLineInfo,
+    required Set<SymbolKind> supportedSymbolKinds,
+  }) {
+    return CallHierarchyOutgoingCall(
+      to: toLspItem(
+        calls.item,
+        itemLineInfo,
+        supportedSymbolKinds: supportedSymbolKinds,
+      ),
+      fromRanges: calls.ranges
+          // For incoming calls, ranges are in original target so we use
+          // localLineInfo and not itemLineInfo (which is for call target
+          // the outbound call points to).
+          .map((call) => sourceRangeToRange(localLineInfo, call))
+          .toList(),
+    );
+  }
+}
+
+/// A handler for the initial "prepare" request for starting navigation with
+/// Call Hierarchy.
+///
+/// This handler returns the initial target based on the offset where the
+/// feature is invoked. Invocations at call sites will resolve to the respective
+/// declarations.
+///
+/// The target returned by this handler will be sent back to the server for
+/// incoming/outgoing calls as the user navigates the call hierarchy in the
+/// client.
+class PrepareCallHierarchyHandler extends MessageHandler<
+    CallHierarchyPrepareParams,
+    TextDocumentPrepareCallHierarchyResult> with _CallHierarchyUtils {
+  PrepareCallHierarchyHandler(super.server);
+  @override
+  Method get handlesMessage => Method.textDocument_prepareCallHierarchy;
+
+  @override
+  LspJsonHandler<CallHierarchyPrepareParams> get jsonHandler =>
+      CallHierarchyPrepareParams.jsonHandler;
+
+  @override
+  Future<ErrorOr<TextDocumentPrepareCallHierarchyResult>> handle(
+      CallHierarchyPrepareParams params,
+      MessageInfo message,
+      CancellationToken token) async {
+    if (!isDartDocument(params.textDocument)) {
+      return success(const []);
+    }
+
+    final clientCapabilities = server.clientCapabilities;
+    if (clientCapabilities == null) {
+      // This should not happen unless a client misbehaves.
+      return serverNotInitializedError;
+    }
+
+    final pos = params.position;
+    final path = pathOfDoc(params.textDocument);
+    final unit = await path.mapResult(requireResolvedUnit);
+    final offset = await unit.mapResult((unit) => toOffset(unit.lineInfo, pos));
+    return offset.mapResult((offset) {
+      final supportedSymbolKinds = clientCapabilities.documentSymbolKinds;
+      final computer = call_hierarchy.DartCallHierarchyComputer(unit.result);
+      final target = computer.findTarget(offset);
+      if (target == null) {
+        return success(null);
+      }
+
+      return _convertTarget(
+        unit.result.session,
+        target,
+        supportedSymbolKinds,
+      );
+    });
+  }
+
+  /// Converts a server [call_hierarchy.CallHierarchyItem] to the LSP protocol
+  /// equivalent.
+  Future<ErrorOr<TextDocumentPrepareCallHierarchyResult>> _convertTarget(
+    AnalysisSession session,
+    call_hierarchy.CallHierarchyItem target,
+    Set<SymbolKind> supportedSymbolKinds,
+  ) async {
+    // Since the target might be in a different file (for example if invoked at
+    // a call site), we need to get a consistent LineInfo for the target file
+    // for this session.
+    final targetFile = session.getFile(target.file);
+    if (targetFile is! FileResult) {
+      return error(
+        ErrorCodes.InternalError,
+        'Call Hierarchy target was in an unavailable file: '
+        '${target.displayName} in ${target.file}',
+      );
+    }
+    final targetLineInfo = targetFile.lineInfo;
+
+    final item = toLspItem(
+      target,
+      targetLineInfo,
+      supportedSymbolKinds: supportedSymbolKinds,
+    );
+    return success([item]);
+  }
+}
+
+/// An abstract base class for incoming and outgoing CallHierarchy handlers
+/// which perform largely the same task using different LSP classes.
+abstract class _AbstractCallHierarchyCallsHandler<P, R, C>
+    extends MessageHandler<P, R> with _CallHierarchyUtils {
+  _AbstractCallHierarchyCallsHandler(super.server);
+
+  /// Gets the appropriate types of calls for this handler.
+  Future<List<call_hierarchy.CallHierarchyCalls>> getCalls(
+      call_hierarchy.DartCallHierarchyComputer computer,
+      call_hierarchy.CallHierarchyItem target);
+
+  /// Handles a request for incoming or outgoing calls (handled by the concrete
+  /// implementation) by delegating fetching and converting calls to the
+  /// subclass.
+  Future<ErrorOr<List<C>?>> handleCalls(CallHierarchyItem item) async {
+    if (!isDartUri(item.uri)) {
+      return success(const []);
+    }
+
+    final clientCapabilities = server.clientCapabilities;
+    if (clientCapabilities == null) {
+      // This should not happen unless a client misbehaves.
+      return failure(serverNotInitializedError);
+    }
+
+    final pos = item.selectionRange.start;
+    final path = pathOfUri(Uri.parse(item.uri));
+    final unit = await path.mapResult(requireResolvedUnit);
+    final offset = await unit.mapResult((unit) => toOffset(unit.lineInfo, pos));
+    return offset.mapResult((offset) async {
+      final supportedSymbolKinds = clientCapabilities.documentSymbolKinds;
+      final computer = call_hierarchy.DartCallHierarchyComputer(unit.result);
+
+      // Convert the clients item back to one in the servers format so that we
+      // can use it to get incoming/outgoing calls.
+      final target = toServerItem(
+        item,
+        unit.result.lineInfo,
+        supportedSymbolKinds: supportedSymbolKinds,
+      );
+
+      if (target == null) {
+        return error(
+          ErrorCodes.ContentModified,
+          'Content was modified since Call Hierarchy node was produced',
+        );
+      }
+
+      final calls = await getCalls(computer, target);
+      final results = _convertCalls(
+        unit.result,
+        calls,
+        supportedSymbolKinds,
+      );
+      return success(results);
+    });
+  }
+
+  /// Converts a server [call_hierarchy.CallHierarchyCalls] to the appropriate
+  /// LSP type [C].
+  C toCall(
+    call_hierarchy.CallHierarchyCalls calls, {
+    required LineInfo localLineInfo,
+    required LineInfo itemLineInfo,
+    required Set<SymbolKind> supportedSymbolKinds,
+  });
+
+  C? _convertCall(
+    AnalysisSession session,
+    LineInfo localLineInfo,
+    Map<String, LineInfo?> lineInfoCache,
+    call_hierarchy.CallHierarchyCalls calls,
+    Set<SymbolKind> supportedSymbolKinds,
+  ) {
+    final filePath = calls.item.file;
+    final itemLineInfo = lineInfoCache.putIfAbsent(filePath, () {
+      final file = session.getFile(filePath);
+      return file is FileResult ? file.lineInfo : null;
+    });
+    if (itemLineInfo == null) {
+      return null;
+    }
+
+    return toCall(
+      calls,
+      localLineInfo: localLineInfo,
+      itemLineInfo: itemLineInfo,
+      supportedSymbolKinds: supportedSymbolKinds,
+    );
+  }
+
+  List<C> _convertCalls(
+    ResolvedUnitResult unit,
+    List<call_hierarchy.CallHierarchyCalls> calls,
+    Set<SymbolKind> supportedSymbolKinds,
+  ) {
+    final session = unit.session;
+    final lineInfoCache = <String, LineInfo?>{};
+    final results = convert(
+      calls,
+      (call_hierarchy.CallHierarchyCalls call) => _convertCall(
+        session,
+        unit.lineInfo,
+        lineInfoCache,
+        call,
+        supportedSymbolKinds,
+      ),
+    );
+    return results.toList();
+  }
+}
+
+/// Utility methods used by all Call Hierarchy handlers.
+mixin _CallHierarchyUtils {
+  /// A mapping from server kinds to LSP [SymbolKind]s.
+  static const toSymbolKindMapping = {
+    call_hierarchy.CallHierarchyKind.class_: SymbolKind.Class,
+    call_hierarchy.CallHierarchyKind.constructor: SymbolKind.Constructor,
+    call_hierarchy.CallHierarchyKind.extension: SymbolKind.Class,
+    call_hierarchy.CallHierarchyKind.file: SymbolKind.File,
+    call_hierarchy.CallHierarchyKind.function: SymbolKind.Function,
+    call_hierarchy.CallHierarchyKind.method: SymbolKind.Method,
+    call_hierarchy.CallHierarchyKind.mixin: SymbolKind.Class,
+    call_hierarchy.CallHierarchyKind.property: SymbolKind.Property,
+  };
+
+  /// A mapping from LSP [SymbolKind]s to server kinds.
+  static final fromSymbolKindMapping = {
+    for (final entry in toSymbolKindMapping.entries) entry.value: entry.key,
+  };
+
+  /// Converts a [SymbolKind] passed back from the client over LSP to a server
+  /// [call_hierarchy.CallHierarchyKind].
+  call_hierarchy.CallHierarchyKind fromSymbolKind(SymbolKind kind) {
+    var result = fromSymbolKindMapping[kind];
+    assert(result != null);
+
+    return result ?? call_hierarchy.CallHierarchyKind.unknown;
+  }
+
+  /// Converts a server [SourceRange] to an LSP [Range].
+  Range sourceRangeToRange(LineInfo lineInfo, SourceRange range) =>
+      toRange(lineInfo, range.offset, range.length);
+
+  /// Converts a server [call_hierarchy.CallHierarchyItem] to an LSP
+  /// [CallHierarchyItem].
+  CallHierarchyItem toLspItem(
+    call_hierarchy.CallHierarchyItem item,
+    LineInfo lineInfo, {
+    required Set<SymbolKind> supportedSymbolKinds,
+  }) {
+    return CallHierarchyItem(
+      name: item.displayName,
+      detail: item.containerName,
+      kind: toSymbolKind(supportedSymbolKinds, item.kind),
+      uri: Uri.file(item.file).toString(),
+      range: sourceRangeToRange(lineInfo, item.codeRange),
+      selectionRange: sourceRangeToRange(lineInfo, item.nameRange),
+    );
+  }
+
+  /// Converts an LSP [CallHierarchyItem] supplied by the client back to a
+  /// server [call_hierarchy.CallHierarchyItem] to use to look up calls.
+  ///
+  /// Returns `null` if the supplied item is no longer valid (for example its
+  /// ranges are no longer valid in the current state of the document).
+  call_hierarchy.CallHierarchyItem? toServerItem(
+    CallHierarchyItem item,
+    LineInfo lineInfo, {
+    required Set<SymbolKind> supportedSymbolKinds,
+  }) {
+    final nameRange = toSourceRange(lineInfo, item.selectionRange);
+    final codeRange = toSourceRange(lineInfo, item.range);
+    if (nameRange.isError || codeRange.isError) {
+      return null;
+    }
+
+    return call_hierarchy.CallHierarchyItem(
+      displayName: item.name,
+      containerName: item.detail,
+      kind: fromSymbolKind(item.kind),
+      file: Uri.parse(item.uri).toFilePath(),
+      nameRange: nameRange.result,
+      codeRange: codeRange.result,
+    );
+  }
+
+  /// Converts a server [call_hierarchy.CallHierarchyKind] to a [SymbolKind]
+  /// used in the LSP Protocol.
+  SymbolKind toSymbolKind(Set<SymbolKind> supportedSymbolKinds,
+      call_hierarchy.CallHierarchyKind kind) {
+    var result = toSymbolKindMapping[kind];
+    assert(result != null);
+
+    // Handle fallbacks and not-supported kinds.
+    if (!supportedSymbolKinds.contains(result)) {
+      if (result == SymbolKind.File) {
+        result = SymbolKind.Module;
+      } else {
+        result = null;
+      }
+    }
+
+    return result ?? SymbolKind.Obj;
+  }
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
index d9a9cfa..d19f007 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
@@ -9,6 +9,7 @@
 import 'package:analysis_server/src/lsp/handlers/custom/handler_diagnostic_server.dart';
 import 'package:analysis_server/src/lsp/handlers/custom/handler_reanalyze.dart';
 import 'package:analysis_server/src/lsp/handlers/custom/handler_super.dart';
+import 'package:analysis_server/src/lsp/handlers/handler_call_hierarchy.dart';
 import 'package:analysis_server/src/lsp/handlers/handler_change_workspace_folders.dart';
 import 'package:analysis_server/src/lsp/handlers/handler_code_actions.dart';
 import 'package:analysis_server/src/lsp/handlers/handler_completion.dart';
@@ -97,6 +98,9 @@
         server, !options.onlyAnalyzeProjectsWithOpenFiles));
     registerHandler(PrepareRenameHandler(server));
     registerHandler(RenameHandler(server));
+    registerHandler(PrepareCallHierarchyHandler(server));
+    registerHandler(IncomingCallHierarchyHandler(server));
+    registerHandler(OutgoingCallHierarchyHandler(server));
     registerHandler(FoldingHandler(server));
     registerHandler(DiagnosticServerHandler(server));
     registerHandler(WorkspaceSymbolHandler(server));
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handlers.dart b/pkg/analysis_server/lib/src/lsp/handlers/handlers.dart
index 6f2bea3..24366d6 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handlers.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handlers.dart
@@ -24,10 +24,10 @@
 
 /// Converts an iterable using the provided function and skipping over any
 /// null values.
-Iterable<T> convert<T, E>(Iterable<E> items, T Function(E) converter) {
+Iterable<T> convert<T, E>(Iterable<E> items, T? Function(E) converter) {
   // TODO(dantup): Now this is used outside of handlers, is there somewhere
   // better to put it, and/or a better name for it?
-  return items.map(converter).where((item) => item != null);
+  return items.map(converter).where((item) => item != null).cast<T>();
 }
 
 abstract class CommandHandler<P, R> with Handler<P, R> {
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index 7e2c591..284c4ad 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -400,7 +400,10 @@
       case server.ElementKind.CLASS_TYPE_ALIAS:
         return const [lsp.CompletionItemKind.Class];
       case server.ElementKind.COMPILATION_UNIT:
-        return const [lsp.CompletionItemKind.Module];
+        return const [
+          lsp.CompletionItemKind.File,
+          lsp.CompletionItemKind.Module,
+        ];
       case server.ElementKind.CONSTRUCTOR:
       case server.ElementKind.CONSTRUCTOR_INVOCATION:
         return const [lsp.CompletionItemKind.Constructor];
@@ -468,7 +471,7 @@
       case server.ElementKind.CLASS_TYPE_ALIAS:
         return const [lsp.SymbolKind.Class];
       case server.ElementKind.COMPILATION_UNIT:
-        return const [lsp.SymbolKind.Module];
+        return const [lsp.SymbolKind.File];
       case server.ElementKind.CONSTRUCTOR:
       case server.ElementKind.CONSTRUCTOR_INVOCATION:
         return const [lsp.SymbolKind.Constructor];
@@ -655,8 +658,9 @@
   return tags != null && tags.isNotEmpty ? tags : null;
 }
 
-bool isDartDocument(lsp.TextDocumentIdentifier? doc) =>
-    doc?.uri.endsWith('.dart') ?? false;
+bool isDartDocument(lsp.TextDocumentIdentifier doc) => isDartUri(doc.uri);
+
+bool isDartUri(String uri) => uri.endsWith('.dart');
 
 /// Converts a [server.Location] to an [lsp.Range] by translating the
 /// offset/length using a `LineInfo`.
diff --git a/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart b/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart
index ca9e238..b25addb 100644
--- a/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart
+++ b/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart
@@ -39,6 +39,7 @@
     Method.textDocument_foldingRange,
     Method.textDocument_selectionRange,
     Method.textDocument_typeDefinition,
+    Method.textDocument_prepareCallHierarchy,
     // workspace.fileOperations covers all file operation methods but we only
     // support this one.
     Method.workspace_willRenameFiles,
@@ -50,6 +51,9 @@
 
   ClientDynamicRegistrations(this._capabilities);
 
+  bool get callHierarchy =>
+      _capabilities.textDocument?.callHierarchy?.dynamicRegistration ?? false;
+
   bool get codeActions =>
       _capabilities.textDocument?.foldingRange?.dynamicRegistration ?? false;
 
@@ -186,6 +190,10 @@
               willSaveWaitUntil: false,
               save: null,
             )),
+      callHierarchyProvider: dynamicRegistrations.callHierarchy
+          ? null
+          : Either3<bool, CallHierarchyOptions,
+              CallHierarchyRegistrationOptions>.t1(true),
       completionProvider: dynamicRegistrations.completion
           ? null
           : CompletionOptions(
@@ -511,6 +519,13 @@
       ),
     );
     register(
+      dynamicRegistrations.callHierarchy,
+      Method.textDocument_prepareCallHierarchy,
+      CallHierarchyRegistrationOptions(
+        documentSelector: [dartFiles],
+      ),
+    );
+    register(
       dynamicRegistrations.semanticTokens,
       CustomMethods.semanticTokenDynamicRegistration,
       SemanticTokensRegistrationOptions(
diff --git a/pkg/analysis_server/test/lsp/call_hierarchy_test.dart b/pkg/analysis_server/test/lsp/call_hierarchy_test.dart
new file mode 100644
index 0000000..9e90f33
--- /dev/null
+++ b/pkg/analysis_server/test/lsp/call_hierarchy_test.dart
@@ -0,0 +1,750 @@
+// Copyright (c) 2022, the Dart project authors. Please see the FooUTHORS file
+// for details. Fooll rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'server_abstract.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PrepareCallHierarchyTest);
+    defineReflectiveTests(IncomingCallHierarchyTest);
+    defineReflectiveTests(OutgoingCallHierarchyTest);
+  });
+}
+
+@reflectiveTest
+class IncomingCallHierarchyTest extends AbstractLspAnalysisServerTest {
+  late final Uri otherFileUri;
+
+  /// Calls textDocument/prepareCallHierarchy at the location of `^` in
+  /// [mainContents] and uses the single result to call
+  /// `callHierarchy/incomingCalls` and ensures the results match
+  /// [expectedResults].
+  Future<void> expectResults({
+    required String mainContents,
+    String? otherContents,
+    required List<CallHierarchyIncomingCall> expectedResults,
+  }) async {
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(mainContents));
+
+    if (otherContents != null) {
+      await openFile(otherFileUri, withoutMarkers(otherContents));
+    }
+
+    final prepareResult = await prepareCallHierarchy(
+      mainFileUri,
+      positionFromMarker(mainContents),
+    );
+    final result = await callHierarchyIncoming(prepareResult!.single);
+
+    expect(result!, unorderedEquals(expectedResults));
+  }
+
+  @override
+  void setUp() {
+    super.setUp();
+    otherFileUri = Uri.file(join(projectFolderPath, 'lib', 'other.dart'));
+  }
+
+  Future<void> test_constructor() async {
+    final contents = '''
+    class Foo {
+      Fo^o();
+    }
+    ''';
+
+    final otherContents = '''
+    import 'main.dart';
+
+    class Bar {
+      final foo = Foo();
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyIncomingCall(
+          // Container of the call
+          from: CallHierarchyItem(
+            name: 'Bar',
+            detail: 'other.dart',
+            kind: SymbolKind.Class,
+            uri: otherFileUri.toString(),
+            range: rangeOfPattern(
+                otherContents, RegExp(r'class Bar \{.*\}', dotAll: true)),
+            selectionRange: rangeOfString(otherContents, 'Bar'),
+          ),
+          // Ranges of calls within this container
+          fromRanges: [
+            rangeOfString(otherContents, 'Foo'),
+          ],
+        ),
+      ],
+    );
+  }
+
+  Future<void> test_function() async {
+    final contents = '''
+    String fo^o() {}
+    ''';
+
+    final otherContents = '''
+    import 'main.dart';
+
+    final x = foo();
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyIncomingCall(
+          // Container of the call
+          from: CallHierarchyItem(
+            name: 'other.dart',
+            detail: null,
+            kind: SymbolKind.File,
+            uri: otherFileUri.toString(),
+            range: entireRange(otherContents),
+            selectionRange: startOfDocRange,
+          ),
+          // Ranges of calls within this container
+          fromRanges: [
+            rangeOfString(otherContents, 'foo'),
+          ],
+        ),
+      ],
+    );
+  }
+
+  Future<void> test_implicitConstructor() async {
+    final contents = '''
+    import 'other.dart';
+
+    void main() {
+      final foo = Fo^o();
+    }
+    ''';
+
+    final otherContents = '''
+    class Foo {}
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyIncomingCall(
+          // Container of the call
+          from: CallHierarchyItem(
+            name: 'main',
+            detail: 'main.dart',
+            kind: SymbolKind.Function,
+            uri: mainFileUri.toString(),
+            range: rangeOfPattern(
+                contents, RegExp(r'void main\(\) \{.*\}', dotAll: true)),
+            selectionRange: rangeOfString(contents, 'main'),
+          ),
+          // Ranges of calls within this container
+          fromRanges: [
+            rangeOfString(contents, 'Foo'),
+          ],
+        ),
+      ],
+    );
+  }
+
+  Future<void> test_method() async {
+    final contents = '''
+    class A {
+      String fo^o() {}
+    }
+    ''';
+
+    final otherContents = '''
+    import 'main.dart';
+
+    class B {
+      String bar() {
+        A().foo();
+      }
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyIncomingCall(
+          // Container of the call
+          from: CallHierarchyItem(
+            name: 'bar',
+            detail: 'B',
+            kind: SymbolKind.Method,
+            uri: otherFileUri.toString(),
+            range: rangeOfPattern(otherContents,
+                RegExp(r'String bar\(\) \{.*\      }', dotAll: true)),
+            selectionRange: rangeOfString(otherContents, 'bar'),
+          ),
+          // Ranges of calls within this container
+          fromRanges: [
+            rangeOfString(otherContents, 'foo'),
+          ],
+        ),
+      ],
+    );
+  }
+
+  Future<void> test_namedConstructor() async {
+    final contents = '''
+    class Foo {
+      Foo.nam^ed();
+    }
+    ''';
+
+    final otherContents = '''
+    import 'main.dart';
+
+    class Bar {
+      final foo = Foo.named();
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyIncomingCall(
+          // Container of the call
+          from: CallHierarchyItem(
+            name: 'Bar',
+            detail: 'other.dart',
+            kind: SymbolKind.Class,
+            uri: otherFileUri.toString(),
+            range: rangeOfPattern(
+                otherContents, RegExp(r'class Bar \{.*\}', dotAll: true)),
+            selectionRange: rangeOfString(otherContents, 'Bar'),
+          ),
+          // Ranges of calls within this container
+          fromRanges: [
+            rangeOfString(otherContents, 'named'),
+          ],
+        ),
+      ],
+    );
+  }
+}
+
+@reflectiveTest
+class OutgoingCallHierarchyTest extends AbstractLspAnalysisServerTest {
+  late final Uri otherFileUri;
+
+  /// Calls textDocument/prepareCallHierarchy at the location of `^` in
+  /// [mainContents] and uses the single result to call
+  /// `callHierarchy/outgoingCalls` and ensures the results match
+  /// [expectedResults].
+  Future<void> expectResults({
+    required String mainContents,
+    String? otherContents,
+    required List<CallHierarchyOutgoingCall> expectedResults,
+  }) async {
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(mainContents));
+
+    if (otherContents != null) {
+      await openFile(otherFileUri, withoutMarkers(otherContents));
+    }
+
+    final prepareResult = await prepareCallHierarchy(
+      mainFileUri,
+      positionFromMarker(mainContents),
+    );
+    final result = await callHierarchyOutgoing(prepareResult!.single);
+
+    expect(result!, unorderedEquals(expectedResults));
+  }
+
+  @override
+  void setUp() {
+    super.setUp();
+    otherFileUri = Uri.file(join(projectFolderPath, 'lib', 'other.dart'));
+  }
+
+  Future<void> test_constructor() async {
+    final contents = '''
+    import 'other.dart';
+
+    class Foo {
+      Fo^o() {
+        final b = Bar();
+      }
+    }
+    ''';
+
+    final otherContents = '''
+    class Bar {
+      Bar();
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyOutgoingCall(
+          // Target of the call.
+          to: CallHierarchyItem(
+            name: 'Bar',
+            detail: 'Bar',
+            kind: SymbolKind.Constructor,
+            uri: otherFileUri.toString(),
+            range: rangeOfString(otherContents, 'Bar();'),
+            selectionRange:
+                rangeStartingAtString(otherContents, 'Bar();', 'Bar'),
+          ),
+          // Ranges of the outbound call.
+          fromRanges: [
+            rangeOfString(contents, 'Bar'),
+          ],
+        ),
+      ],
+    );
+  }
+
+  Future<void> test_function() async {
+    final contents = '''
+    import 'other.dart';
+
+    void fo^o() {
+      bar();
+    }
+    ''';
+
+    final otherContents = '''
+    void bar() {}
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyOutgoingCall(
+          // Target of the call.
+          to: CallHierarchyItem(
+            name: 'bar',
+            detail: 'other.dart',
+            kind: SymbolKind.Function,
+            uri: otherFileUri.toString(),
+            range: rangeOfString(otherContents, 'void bar() {}'),
+            selectionRange: rangeOfString(otherContents, 'bar'),
+          ),
+          // Ranges of the outbound call.
+          fromRanges: [
+            rangeOfString(contents, 'bar'),
+          ],
+        ),
+      ],
+    );
+  }
+
+  Future<void> test_implicitConstructor() async {
+    final contents = '''
+    import 'other.dart';
+
+    class Foo {
+      Fo^o() {
+        final b = Bar();
+      }
+    }
+    ''';
+
+    final otherContents = '''
+    class Bar {}
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyOutgoingCall(
+          // Target of the call.
+          to: CallHierarchyItem(
+            name: 'Bar',
+            detail: 'Bar',
+            kind: SymbolKind.Constructor,
+            uri: otherFileUri.toString(),
+            range: rangeOfString(otherContents, 'class Bar {}'),
+            selectionRange: rangeOfString(otherContents, 'Bar'),
+          ),
+          // Ranges of the outbound call.
+          fromRanges: [
+            rangeOfString(contents, 'Bar'),
+          ],
+        ),
+      ],
+    );
+  }
+
+  Future<void> test_method() async {
+    final contents = '''
+    import 'other.dart';
+
+    class Foo {
+      final b = Bar();
+      void f^oo() {
+        b.bar();
+      }
+    }
+    ''';
+
+    final otherContents = '''
+    class Bar {
+      void bar() {}
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyOutgoingCall(
+          // Target of the call.
+          to: CallHierarchyItem(
+            name: 'bar',
+            detail: 'Bar',
+            kind: SymbolKind.Method,
+            uri: otherFileUri.toString(),
+            range: rangeOfString(otherContents, 'void bar() {}'),
+            selectionRange: rangeOfString(otherContents, 'bar'),
+          ),
+          // Ranges of the outbound call.
+          fromRanges: [
+            rangeOfString(contents, 'bar'),
+          ],
+        ),
+      ],
+    );
+  }
+
+  Future<void> test_namedConstructor() async {
+    final contents = '''
+    import 'other.dart';
+
+    class Foo {
+      Foo.nam^ed() {
+        final b = Bar.named();
+      }
+    }
+    ''';
+
+    final otherContents = '''
+    class Bar {
+      Bar.named();
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResults: [
+        CallHierarchyOutgoingCall(
+          // Target of the call.
+          to: CallHierarchyItem(
+            name: 'Bar.named',
+            detail: 'Bar',
+            kind: SymbolKind.Constructor,
+            uri: otherFileUri.toString(),
+            range: rangeOfString(otherContents, 'Bar.named();'),
+            selectionRange: rangeOfString(otherContents, 'named'),
+          ),
+          // Ranges of the outbound call.
+          fromRanges: [
+            rangeStartingAtString(contents, 'named();', 'named'),
+          ],
+        ),
+      ],
+    );
+  }
+}
+
+@reflectiveTest
+class PrepareCallHierarchyTest extends AbstractLspAnalysisServerTest {
+  late final Uri otherFileUri;
+
+  /// Calls textDocument/prepareCallHierarchy at the location of `^` in
+  /// [mainContents] and expects a null result.
+  Future<void> expectNullResults(String mainContents) async {
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(mainContents));
+    final result = await prepareCallHierarchy(
+      mainFileUri,
+      positionFromMarker(mainContents),
+    );
+    expect(result, isNull);
+  }
+
+  /// Calls textDocument/prepareCallHierarchy at the location of `^` in
+  /// [mainContents] and ensures the results match [expectedResults].
+  Future<void> expectResults({
+    required String mainContents,
+    String? otherContents,
+    required CallHierarchyItem expectedResult,
+  }) async {
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(mainContents));
+
+    if (otherContents != null) {
+      await openFile(otherFileUri, withoutMarkers(otherContents));
+    }
+
+    final results = await prepareCallHierarchy(
+      mainFileUri,
+      positionFromMarker(mainContents),
+    );
+
+    expect(results, isNotNull);
+    expect(results!, hasLength(1));
+    expect(results.single, expectedResult);
+  }
+
+  @override
+  void setUp() {
+    super.setUp();
+    otherFileUri = Uri.file(join(projectFolderPath, 'lib', 'other.dart'));
+  }
+
+  Future<void> test_args() async {
+    await expectNullResults('main(int ^a) {}');
+  }
+
+  Future<void> test_block() async {
+    await expectNullResults('main() {^}');
+  }
+
+  Future<void> test_comment() async {
+    await expectNullResults('main() {} // this is a ^comment');
+  }
+
+  Future<void> test_constructor() async {
+    final contents = '''
+    class Foo {
+      [[Fo^o]](String a) {}
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      expectedResult: CallHierarchyItem(
+          name: 'Foo',
+          detail: 'Foo', // Containing class name
+          kind: SymbolKind.Constructor,
+          uri: mainFileUri.toString(),
+          range: rangeOfString(contents, 'Foo(String a) {}'),
+          selectionRange: rangeFromMarkers(contents)),
+    );
+  }
+
+  Future<void> test_constructorCall() async {
+    final contents = '''
+    import 'other.dart';
+
+    main() {
+      final foo = Fo^o();
+    }
+    ''';
+
+    final otherContents = '''
+    class Foo {
+      [[Foo]]();
+    }
+    ''';
+
+    await expectResults(
+        mainContents: contents,
+        otherContents: otherContents,
+        expectedResult: CallHierarchyItem(
+            name: 'Foo',
+            detail: 'Foo', // Containing class name
+            kind: SymbolKind.Constructor,
+            uri: otherFileUri.toString(),
+            range: rangeOfString(otherContents, 'Foo();'),
+            selectionRange: rangeFromMarkers(otherContents)));
+  }
+
+  Future<void> test_function() async {
+    final contents = '''
+    void myFun^ction() {}
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      expectedResult: CallHierarchyItem(
+          name: 'myFunction',
+          detail: 'main.dart', // Containing file name
+          kind: SymbolKind.Function,
+          uri: mainFileUri.toString(),
+          range: rangeOfString(contents, 'void myFunction() {}'),
+          selectionRange: rangeOfString(contents, 'myFunction')),
+    );
+  }
+
+  Future<void> test_functionCall() async {
+    final contents = '''
+    import 'other.dart' as f;
+
+    main() {
+      f.myFun^ction();
+    }
+    ''';
+
+    final otherContents = '''
+    void myFunction() {}
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResult: CallHierarchyItem(
+          name: 'myFunction',
+          detail: 'other.dart', // Containing file name
+          kind: SymbolKind.Function,
+          uri: otherFileUri.toString(),
+          range: rangeOfString(otherContents, 'void myFunction() {}'),
+          selectionRange: rangeOfString(otherContents, 'myFunction')),
+    );
+  }
+
+  Future<void> test_implicitConstructorCall() async {
+    // Even if a constructor is implicit, we might want to be able to get the
+    // incoming calls, so invoking it here should still return an element
+    // (the class).
+    final contents = '''
+    import 'other.dart';
+
+    main() {
+      final foo = Fo^o();
+    }
+    ''';
+
+    final otherContents = '''
+    class Foo {}
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResult: CallHierarchyItem(
+          name: 'Foo',
+          detail: 'Foo', // Containing class name
+          kind: SymbolKind.Constructor,
+          uri: otherFileUri.toString(),
+          range: rangeOfString(otherContents, 'class Foo {}'),
+          selectionRange: rangeOfString(otherContents, 'Foo')),
+    );
+  }
+
+  Future<void> test_method() async {
+    final contents = '''
+    class Foo {
+      void myMet^hod() {}
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      expectedResult: CallHierarchyItem(
+          name: 'myMethod',
+          detail: 'Foo', // Containing class name
+          kind: SymbolKind.Method,
+          uri: mainFileUri.toString(),
+          range: rangeOfString(contents, 'void myMethod() {}'),
+          selectionRange: rangeOfString(contents, 'myMethod')),
+    );
+  }
+
+  Future<void> test_methodCall() async {
+    final contents = '''
+    import 'other.dart';
+
+    main() {
+      Foo().myMet^hod();
+    }
+    ''';
+
+    final otherContents = '''
+    class Foo {
+      void myMethod() {}
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResult: CallHierarchyItem(
+          name: 'myMethod',
+          detail: 'Foo', // Containing class name
+          kind: SymbolKind.Method,
+          uri: otherFileUri.toString(),
+          range: rangeOfString(otherContents, 'void myMethod() {}'),
+          selectionRange: rangeOfString(otherContents, 'myMethod')),
+    );
+  }
+
+  Future<void> test_namedConstructor() async {
+    final contents = '''
+    class Foo {
+      Foo.Ba^r(String a) {}
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      expectedResult: CallHierarchyItem(
+          name: 'Foo.Bar',
+          detail: 'Foo', // Containing class name
+          kind: SymbolKind.Constructor,
+          uri: mainFileUri.toString(),
+          range: rangeOfString(contents, 'Foo.Bar(String a) {}'),
+          selectionRange: rangeOfString(contents, 'Bar')),
+    );
+  }
+
+  Future<void> test_namedConstructorCall() async {
+    final contents = '''
+    import 'other.dart';
+
+    main() {
+      final foo = Foo.Ba^r();
+    }
+    ''';
+
+    final otherContents = '''
+    class Foo {
+      Foo.Bar();
+    }
+    ''';
+
+    await expectResults(
+      mainContents: contents,
+      otherContents: otherContents,
+      expectedResult: CallHierarchyItem(
+          name: 'Foo.Bar',
+          detail: 'Foo', // Containing class name
+          kind: SymbolKind.Constructor,
+          uri: otherFileUri.toString(),
+          range: rangeOfString(otherContents, 'Foo.Bar();'),
+          selectionRange: rangeOfString(otherContents, 'Bar')),
+    );
+  }
+
+  Future<void> test_whitespace() async {
+    await expectNullResults(' ^  main() {}');
+  }
+}
diff --git a/pkg/analysis_server/test/lsp/initialization_test.dart b/pkg/analysis_server/test/lsp/initialization_test.dart
index d304655..28bd71b 100644
--- a/pkg/analysis_server/test/lsp/initialization_test.dart
+++ b/pkg/analysis_server/test/lsp/initialization_test.dart
@@ -327,6 +327,7 @@
         expect(options.change, equals(TextDocumentSyncKind.Incremental));
       },
     );
+    expect(initResult.capabilities.callHierarchyProvider, isNotNull);
     expect(initResult.capabilities.completionProvider, isNotNull);
     expect(initResult.capabilities.hoverProvider, isNotNull);
     expect(initResult.capabilities.signatureHelpProvider, isNotNull);
@@ -387,6 +388,7 @@
     // Ensure no static registrations. This list should include all server equivalents
     // of the dynamic registrations listed in `ClientDynamicRegistrations.supported`.
     expect(initResult.capabilities.textDocumentSync, isNull);
+    expect(initResult.capabilities.callHierarchyProvider, isNull);
     expect(initResult.capabilities.completionProvider, isNull);
     expect(initResult.capabilities.hoverProvider, isNull);
     expect(initResult.capabilities.signatureHelpProvider, isNull);
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index 9d9c88e..d9acebe 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -299,6 +299,7 @@
     // the fields listed in `ClientDynamicRegistrations.supported`.
     return extendTextDocumentCapabilities(source, {
       'synchronization': {'dynamicRegistration': true},
+      'callHierarchy': {'dynamicRegistration': true},
       'completion': {'dynamicRegistration': true},
       'hover': {'dynamicRegistration': true},
       'signatureHelp': {'dynamicRegistration': true},
@@ -840,6 +841,26 @@
     return newContent;
   }
 
+  Future<List<CallHierarchyIncomingCall>?> callHierarchyIncoming(
+      CallHierarchyItem item) {
+    final request = makeRequest(
+      Method.callHierarchy_incomingCalls,
+      CallHierarchyIncomingCallsParams(item: item),
+    );
+    return expectSuccessfulResponseTo(
+        request, _fromJsonList(CallHierarchyIncomingCall.fromJson));
+  }
+
+  Future<List<CallHierarchyOutgoingCall>?> callHierarchyOutgoing(
+      CallHierarchyItem item) {
+    final request = makeRequest(
+      Method.callHierarchy_outgoingCalls,
+      CallHierarchyOutgoingCallsParams(item: item),
+    );
+    return expectSuccessfulResponseTo(
+        request, _fromJsonList(CallHierarchyOutgoingCall.fromJson));
+  }
+
   Future changeFile(
     int newVersion,
     Uri uri,
@@ -878,6 +899,12 @@
     await sendNotificationToServer(notification);
   }
 
+  /// Gets the entire range for [code].
+  Range entireRange(String code) => Range(
+        start: startOfDocPos,
+        end: positionFromOffset(code.length, code),
+      );
+
   Future<Object?> executeCodeAction(
       Either2<Command, CodeAction> codeAction) async {
     final command = codeAction.map(
@@ -1636,6 +1663,18 @@
     return toPosition(lineInfo.getLocation(offset));
   }
 
+  Future<List<CallHierarchyItem>?> prepareCallHierarchy(Uri uri, Position pos) {
+    final request = makeRequest(
+      Method.textDocument_prepareCallHierarchy,
+      CallHierarchyPrepareParams(
+        textDocument: TextDocumentIdentifier(uri: uri.toString()),
+        position: pos,
+      ),
+    );
+    return expectSuccessfulResponseTo(
+        request, _fromJsonList(CallHierarchyItem.fromJson));
+  }
+
   Future<PlaceholderAndRange?> prepareRename(Uri uri, Position pos) {
     final request = makeRequest(
       Method.textDocument_prepareRename,
@@ -1694,18 +1733,20 @@
     }
   }
 
-  /// Returns the range of [searchText] in [content].
-  Range? rangeOfString(String content, String searchText) {
+  /// Returns the range of [pattern] in [content].
+  Range rangeOfPattern(String content, Pattern pattern) {
     content = withoutMarkers(content);
-    final startOffset = content.indexOf(searchText);
-    return startOffset == -1
-        ? null
-        : Range(
-            start: positionFromOffset(startOffset, content),
-            end: positionFromOffset(startOffset + searchText.length, content),
-          );
+    final match = pattern.allMatches(content).first;
+    return Range(
+      start: positionFromOffset(match.start, content),
+      end: positionFromOffset(match.end, content),
+    );
   }
 
+  /// Returns the range of [searchText] in [content].
+  Range rangeOfString(String content, String searchText) =>
+      rangeOfPattern(content, searchText);
+
   /// Returns all ranges surrounded by `[[markers]]` in the provided string,
   /// excluding the markers themselves (as well as position markers `^` from
   /// the offsets).
@@ -1743,6 +1784,18 @@
     return rangesFromMarkersImpl(content).toList();
   }
 
+  /// Gets the range in [content] that beings with the string [prefix] and
+  /// has a length matching [text].
+  Range rangeStartingAtString(String content, String prefix, String text) {
+    content = withoutMarkers(content);
+    final offset = content.indexOf(prefix);
+    final end = offset + text.length;
+    return Range(
+      start: positionFromOffset(offset, content),
+      end: positionFromOffset(end, content),
+    );
+  }
+
   Future<WorkspaceEdit?> rename(
     Uri uri,
     int? version,
diff --git a/pkg/analysis_server/test/lsp/test_all.dart b/pkg/analysis_server/test/lsp/test_all.dart
index c563746..9bf0972 100644
--- a/pkg/analysis_server/test/lsp/test_all.dart
+++ b/pkg/analysis_server/test/lsp/test_all.dart
@@ -6,6 +6,7 @@
 
 import '../src/lsp/lsp_packet_transformer_test.dart' as lsp_packet_transformer;
 import 'analyzer_status_test.dart' as analyzer_status;
+import 'call_hierarchy_test.dart' as call_hierarchy;
 import 'cancel_request_test.dart' as cancel_request;
 import 'change_workspace_folders_test.dart' as change_workspace_folders;
 import 'client_configuration_test.dart' as client_configuration;
@@ -50,6 +51,7 @@
 void main() {
   defineReflectiveSuite(() {
     analyzer_status.main();
+    call_hierarchy.main();
     cancel_request.main();
     change_workspace_folders.main();
     client_configuration.main();
diff --git a/pkg/analysis_server/tool/lsp_spec/README.md b/pkg/analysis_server/tool/lsp_spec/README.md
index 5776154..186d133 100644
--- a/pkg/analysis_server/tool/lsp_spec/README.md
+++ b/pkg/analysis_server/tool/lsp_spec/README.md
@@ -102,9 +102,9 @@
 | textDocument/inlineValue | | | |
 | textDocument/linkedEditingRange | | | |
 | textDocument/moniker | | | |
-| textDocument/prepareCallHierarchy | | | |
-|   callHierarchy/incomingCalls | | | |
-|   callHierarchy/outgoingCalls | | | |
+| textDocument/prepareCallHierarchy | ✅ | | |
+|   callHierarchy/incomingCalls | ✅ | | |
+|   callHierarchy/outgoingCalls | ✅ | | |
 | textDocument/prepareRename | ✅ | | |
 |   textDocument/rename | ✅ | | |
 | textDocument/prepareTypeHierarchy | | | |
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 5de2a68..727e2cd 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 9.1.0
+- Remove `required` keyword from most of the named parameters in the
+  constructors of the Dart API objects.
+
 ## 9.0.0
 - Update to version `3.58` of the spec.
 - Added optional `local` parameter to `lookupResolvedPackageUris` RPC.
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index 7d79815..1533b40 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -2845,8 +2845,8 @@
   int? dateLastServiceGC;
 
   AllocationProfile({
-    required this.members,
-    required this.memoryUsage,
+    this.members,
+    this.memoryUsage,
     this.dateLastAccumulatorReset,
     this.dateLastServiceGC,
   });
@@ -2906,8 +2906,8 @@
   dynamic value;
 
   BoundField({
-    required this.decl,
-    required this.value,
+    this.decl,
+    this.value,
   });
 
   BoundField._fromJson(Map<String, dynamic> json) {
@@ -2959,11 +2959,11 @@
   int? scopeEndTokenPos;
 
   BoundVariable({
-    required this.name,
-    required this.value,
-    required this.declarationTokenPos,
-    required this.scopeStartTokenPos,
-    required this.scopeEndTokenPos,
+    this.name,
+    this.value,
+    this.declarationTokenPos,
+    this.scopeStartTokenPos,
+    this.scopeEndTokenPos,
   });
 
   BoundVariable._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -3028,10 +3028,10 @@
   dynamic location;
 
   Breakpoint({
-    required this.breakpointNumber,
-    required this.enabled,
-    required this.resolved,
-    required this.location,
+    this.breakpointNumber,
+    this.enabled,
+    this.resolved,
+    this.location,
     required String id,
     this.isSyntheticAsyncContinuation,
   }) : super(
@@ -3096,8 +3096,8 @@
   List<InstanceRef>? typeParameters;
 
   ClassRef({
-    required this.name,
-    required this.library,
+    this.name,
+    this.library,
     required String id,
     this.location,
     this.typeParameters,
@@ -3209,15 +3209,15 @@
   List<ClassRef>? subclasses;
 
   Class({
-    required this.name,
-    required this.library,
-    required this.isAbstract,
-    required this.isConst,
-    required this.traceAllocations,
-    required this.interfaces,
-    required this.fields,
-    required this.functions,
-    required this.subclasses,
+    this.name,
+    this.library,
+    this.isAbstract,
+    this.isConst,
+    this.traceAllocations,
+    this.interfaces,
+    this.fields,
+    this.functions,
+    this.subclasses,
     required String id,
     this.location,
     this.typeParameters,
@@ -3321,11 +3321,11 @@
   int? instancesCurrent;
 
   ClassHeapStats({
-    required this.classRef,
-    required this.accumulatedSize,
-    required this.bytesCurrent,
-    required this.instancesAccumulated,
-    required this.instancesCurrent,
+    this.classRef,
+    this.accumulatedSize,
+    this.bytesCurrent,
+    this.instancesAccumulated,
+    this.instancesCurrent,
   });
 
   ClassHeapStats._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -3366,7 +3366,7 @@
   List<ClassRef>? classes;
 
   ClassList({
-    required this.classes,
+    this.classes,
   });
 
   ClassList._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -3403,8 +3403,8 @@
   /*CodeKind*/ String? kind;
 
   CodeRef({
-    required this.name,
-    required this.kind,
+    this.name,
+    this.kind,
     required String id,
   }) : super(
           id: id,
@@ -3448,8 +3448,8 @@
   /*CodeKind*/ String? kind;
 
   Code({
-    required this.name,
-    required this.kind,
+    this.name,
+    this.kind,
     required String id,
   }) : super(
           id: id,
@@ -3489,7 +3489,7 @@
   int? length;
 
   ContextRef({
-    required this.length,
+    this.length,
     required String id,
   }) : super(
           id: id,
@@ -3536,8 +3536,8 @@
   List<ContextElement>? variables;
 
   Context({
-    required this.length,
-    required this.variables,
+    this.length,
+    this.variables,
     required String id,
     this.parent,
   }) : super(
@@ -3585,7 +3585,7 @@
   dynamic value;
 
   ContextElement({
-    required this.value,
+    this.value,
   });
 
   ContextElement._fromJson(Map<String, dynamic> json) {
@@ -3637,8 +3637,8 @@
   int? pid;
 
   /// A list of functions seen in the relevant samples. These references can be
-  /// looked up using the indicies provided in a `CpuSample` `stack` to
-  /// determine which function was on the stack.
+  /// looked up using the indices provided in a `CpuSample` `stack` to determine
+  /// which function was on the stack.
   List<ProfileFunction>? functions;
 
   /// A list of samples collected in the range `[timeOriginMicros,
@@ -3646,15 +3646,15 @@
   List<CpuSample>? samples;
 
   CpuSamples({
-    required this.samplePeriod,
-    required this.maxStackDepth,
-    required this.sampleCount,
-    required this.timeSpan,
-    required this.timeOriginMicros,
-    required this.timeExtentMicros,
-    required this.pid,
-    required this.functions,
-    required this.samples,
+    this.samplePeriod,
+    this.maxStackDepth,
+    this.sampleCount,
+    this.timeSpan,
+    this.timeOriginMicros,
+    this.timeExtentMicros,
+    this.pid,
+    this.functions,
+    this.samples,
   });
 
   CpuSamples._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -3738,15 +3738,15 @@
   List<CpuSample>? samples;
 
   CpuSamplesEvent({
-    required this.samplePeriod,
-    required this.maxStackDepth,
-    required this.sampleCount,
-    required this.timeSpan,
-    required this.timeOriginMicros,
-    required this.timeExtentMicros,
-    required this.pid,
-    required this.functions,
-    required this.samples,
+    this.samplePeriod,
+    this.maxStackDepth,
+    this.sampleCount,
+    this.timeSpan,
+    this.timeOriginMicros,
+    this.timeExtentMicros,
+    this.pid,
+    this.functions,
+    this.samples,
   });
 
   CpuSamplesEvent._fromJson(Map<String, dynamic> json) {
@@ -3833,9 +3833,9 @@
   int? classId;
 
   CpuSample({
-    required this.tid,
-    required this.timestamp,
-    required this.stack,
+    this.tid,
+    this.timestamp,
+    this.stack,
     this.vmTag,
     this.userTag,
     this.truncated,
@@ -3885,8 +3885,8 @@
   String? message;
 
   ErrorRef({
-    required this.kind,
-    required this.message,
+    this.kind,
+    this.message,
     required String id,
   }) : super(
           id: id,
@@ -3942,8 +3942,8 @@
   InstanceRef? stacktrace;
 
   Error({
-    required this.kind,
-    required this.message,
+    this.kind,
+    this.message,
     required String id,
     this.exception,
     this.stacktrace,
@@ -4187,8 +4187,8 @@
   ByteData? data;
 
   Event({
-    required this.kind,
-    required this.timestamp,
+    this.kind,
+    this.timestamp,
     this.isolate,
     this.vm,
     this.breakpoint,
@@ -4347,12 +4347,12 @@
   SourceLocation? location;
 
   FieldRef({
-    required this.name,
-    required this.owner,
-    required this.declaredType,
-    required this.isConst,
-    required this.isFinal,
-    required this.isStatic,
+    this.name,
+    this.owner,
+    this.declaredType,
+    this.isConst,
+    this.isFinal,
+    this.isStatic,
     required String id,
     this.location,
   }) : super(
@@ -4444,12 +4444,12 @@
   dynamic staticValue;
 
   Field({
-    required this.name,
-    required this.owner,
-    required this.declaredType,
-    required this.isConst,
-    required this.isFinal,
-    required this.isStatic,
+    this.name,
+    this.owner,
+    this.declaredType,
+    this.isConst,
+    this.isFinal,
+    this.isStatic,
     required String id,
     this.location,
     this.staticValue,
@@ -4522,9 +4522,9 @@
   String? valueAsString;
 
   Flag({
-    required this.name,
-    required this.comment,
-    required this.modified,
+    this.name,
+    this.comment,
+    this.modified,
     this.valueAsString,
   });
 
@@ -4559,7 +4559,7 @@
   List<Flag>? flags;
 
   FlagList({
-    required this.flags,
+    this.flags,
   });
 
   FlagList._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -4605,7 +4605,7 @@
   /*FrameKind*/ String? kind;
 
   Frame({
-    required this.index,
+    this.index,
     this.function,
     this.code,
     this.location,
@@ -4684,11 +4684,11 @@
   SourceLocation? location;
 
   FuncRef({
-    required this.name,
-    required this.owner,
-    required this.isStatic,
-    required this.isConst,
-    required this.implicit,
+    this.name,
+    this.owner,
+    this.isStatic,
+    this.isConst,
+    this.implicit,
     required String id,
     this.location,
   }) : super(
@@ -4775,12 +4775,12 @@
   CodeRef? code;
 
   Func({
-    required this.name,
-    required this.owner,
-    required this.isStatic,
-    required this.isConst,
-    required this.implicit,
-    required this.signature,
+    this.name,
+    this.owner,
+    this.isStatic,
+    this.isConst,
+    this.implicit,
+    this.signature,
     required String id,
     this.location,
     this.code,
@@ -4980,9 +4980,9 @@
   String? debugName;
 
   InstanceRef({
-    required this.kind,
-    required this.identityHashCode,
-    required this.classRef,
+    this.kind,
+    this.identityHashCode,
+    this.classRef,
     required String id,
     this.valueAsString,
     this.valueAsStringIsTruncated,
@@ -5389,9 +5389,9 @@
   String? debugName;
 
   Instance({
-    required this.kind,
-    required this.identityHashCode,
-    required this.classRef,
+    this.kind,
+    this.identityHashCode,
+    this.classRef,
     required String id,
     this.valueAsString,
     this.valueAsStringIsTruncated,
@@ -5579,10 +5579,10 @@
   bool? isSystemIsolate;
 
   IsolateRef({
-    required this.id,
-    required this.number,
-    required this.name,
-    required this.isSystemIsolate,
+    this.id,
+    this.number,
+    this.name,
+    this.isSystemIsolate,
   });
 
   IsolateRef._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -5683,19 +5683,19 @@
   List<String>? extensionRPCs;
 
   Isolate({
-    required this.id,
-    required this.number,
-    required this.name,
-    required this.isSystemIsolate,
-    required this.isolateFlags,
-    required this.startTime,
-    required this.runnable,
-    required this.livePorts,
-    required this.pauseOnExit,
-    required this.pauseEvent,
-    required this.libraries,
-    required this.breakpoints,
-    required this.exceptionPauseMode,
+    this.id,
+    this.number,
+    this.name,
+    this.isSystemIsolate,
+    this.isolateFlags,
+    this.startTime,
+    this.runnable,
+    this.livePorts,
+    this.pauseOnExit,
+    this.pauseEvent,
+    this.libraries,
+    this.breakpoints,
+    this.exceptionPauseMode,
     this.rootLib,
     this.error,
     this.extensionRPCs,
@@ -5779,8 +5779,8 @@
   String? valueAsString;
 
   IsolateFlag({
-    required this.name,
-    required this.valueAsString,
+    this.name,
+    this.valueAsString,
   });
 
   IsolateFlag._fromJson(Map<String, dynamic> json) {
@@ -5821,10 +5821,10 @@
   bool? isSystemIsolateGroup;
 
   IsolateGroupRef({
-    required this.id,
-    required this.number,
-    required this.name,
-    required this.isSystemIsolateGroup,
+    this.id,
+    this.number,
+    this.name,
+    this.isSystemIsolateGroup,
   });
 
   IsolateGroupRef._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -5881,11 +5881,11 @@
   List<IsolateRef>? isolates;
 
   IsolateGroup({
-    required this.id,
-    required this.number,
-    required this.name,
-    required this.isSystemIsolateGroup,
-    required this.isolates,
+    this.id,
+    this.number,
+    this.name,
+    this.isSystemIsolateGroup,
+    this.isolates,
   });
 
   IsolateGroup._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -5933,7 +5933,7 @@
   List<InboundReference>? references;
 
   InboundReferences({
-    required this.references,
+    this.references,
   });
 
   InboundReferences._fromJson(Map<String, dynamic> json)
@@ -5979,7 +5979,7 @@
   FieldRef? parentField;
 
   InboundReference({
-    required this.source,
+    this.source,
     this.parentListIndex,
     this.parentField,
   });
@@ -6016,8 +6016,8 @@
   List<ObjRef>? instances;
 
   InstanceSet({
-    required this.totalCount,
-    required this.instances,
+    this.totalCount,
+    this.instances,
   });
 
   InstanceSet._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -6057,8 +6057,8 @@
   String? uri;
 
   LibraryRef({
-    required this.name,
-    required this.uri,
+    this.name,
+    this.uri,
     required String id,
   }) : super(
           id: id,
@@ -6122,14 +6122,14 @@
   List<ClassRef>? classes;
 
   Library({
-    required this.name,
-    required this.uri,
-    required this.debuggable,
-    required this.dependencies,
-    required this.scripts,
-    required this.variables,
-    required this.functions,
-    required this.classes,
+    this.name,
+    this.uri,
+    this.debuggable,
+    this.dependencies,
+    this.scripts,
+    this.variables,
+    this.functions,
+    this.classes,
     required String id,
   }) : super(
           id: id,
@@ -6208,10 +6208,10 @@
   List<String>? hides;
 
   LibraryDependency({
-    required this.isImport,
-    required this.isDeferred,
-    required this.prefix,
-    required this.target,
+    this.isImport,
+    this.isDeferred,
+    this.prefix,
+    this.target,
     this.shows,
     this.hides,
   });
@@ -6276,14 +6276,14 @@
   InstanceRef? stackTrace;
 
   LogRecord({
-    required this.message,
-    required this.time,
-    required this.level,
-    required this.sequenceNumber,
-    required this.loggerName,
-    required this.zone,
-    required this.error,
-    required this.stackTrace,
+    this.message,
+    this.time,
+    this.level,
+    this.sequenceNumber,
+    this.loggerName,
+    this.zone,
+    this.error,
+    this.stackTrace,
   });
 
   LogRecord._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -6336,8 +6336,8 @@
   dynamic value;
 
   MapAssociation({
-    required this.key,
-    required this.value,
+    this.key,
+    this.value,
   });
 
   MapAssociation._fromJson(Map<String, dynamic> json) {
@@ -6383,9 +6383,9 @@
   int? heapUsage;
 
   MemoryUsage({
-    required this.externalUsage,
-    required this.heapCapacity,
-    required this.heapUsage,
+    this.externalUsage,
+    this.heapCapacity,
+    this.heapUsage,
   });
 
   MemoryUsage._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -6443,10 +6443,10 @@
   SourceLocation? location;
 
   Message({
-    required this.index,
-    required this.name,
-    required this.messageObjectId,
-    required this.size,
+    this.index,
+    this.name,
+    this.messageObjectId,
+    this.size,
     this.handler,
     this.location,
   });
@@ -6495,7 +6495,7 @@
   String? name;
 
   NativeFunction({
-    required this.name,
+    this.name,
   });
 
   NativeFunction._fromJson(Map<String, dynamic> json) {
@@ -6523,7 +6523,7 @@
   String? valueAsString;
 
   NullValRef({
-    required this.valueAsString,
+    this.valueAsString,
   }) : super(
           id: 'instance/null',
           identityHashCode: 0,
@@ -6575,7 +6575,7 @@
   String? valueAsString;
 
   NullVal({
-    required this.valueAsString,
+    this.valueAsString,
   }) : super(
           id: 'instance/null',
           identityHashCode: 0,
@@ -6633,7 +6633,7 @@
   bool? fixedId;
 
   ObjRef({
-    required this.id,
+    this.id,
     this.fixedId,
   });
 
@@ -6702,7 +6702,7 @@
   int? size;
 
   Obj({
-    required this.id,
+    this.id,
     this.fixedId,
     this.classRef,
     this.size,
@@ -6761,8 +6761,8 @@
   bool? required;
 
   Parameter({
-    required this.parameterType,
-    required this.fixed,
+    this.parameterType,
+    this.fixed,
     this.name,
     this.required,
   });
@@ -6801,7 +6801,7 @@
   List<InstanceRef>? ports;
 
   PortList({
-    required this.ports,
+    this.ports,
   });
 
   PortList._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -6851,11 +6851,11 @@
   dynamic function;
 
   ProfileFunction({
-    required this.kind,
-    required this.inclusiveTicks,
-    required this.exclusiveTicks,
-    required this.resolvedUrl,
-    required this.function,
+    this.kind,
+    this.inclusiveTicks,
+    this.exclusiveTicks,
+    this.resolvedUrl,
+    this.function,
   });
 
   ProfileFunction._fromJson(Map<String, dynamic> json) {
@@ -6896,7 +6896,7 @@
   List<Protocol>? protocols;
 
   ProtocolList({
-    required this.protocols,
+    this.protocols,
   });
 
   ProtocolList._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -6936,9 +6936,9 @@
   int? minor;
 
   Protocol({
-    required this.protocolName,
-    required this.major,
-    required this.minor,
+    this.protocolName,
+    this.major,
+    this.minor,
   });
 
   Protocol._fromJson(Map<String, dynamic> json) {
@@ -6969,7 +6969,7 @@
   ProcessMemoryItem? root;
 
   ProcessMemoryUsage({
-    required this.root,
+    this.root,
   });
 
   ProcessMemoryUsage._fromJson(Map<String, dynamic> json)
@@ -7012,10 +7012,10 @@
   List<ProcessMemoryItem>? children;
 
   ProcessMemoryItem({
-    required this.name,
-    required this.description,
-    required this.size,
-    required this.children,
+    this.name,
+    this.description,
+    this.size,
+    this.children,
   });
 
   ProcessMemoryItem._fromJson(Map<String, dynamic> json) {
@@ -7052,7 +7052,7 @@
   bool? success;
 
   ReloadReport({
-    required this.success,
+    this.success,
   });
 
   ReloadReport._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -7096,7 +7096,7 @@
   String? parentField;
 
   RetainingObject({
-    required this.value,
+    this.value,
     this.parentListIndex,
     this.parentMapKey,
     this.parentField,
@@ -7141,9 +7141,9 @@
   List<RetainingObject>? elements;
 
   RetainingPath({
-    required this.length,
-    required this.gcRootType,
-    required this.elements,
+    this.length,
+    this.gcRootType,
+    this.elements,
   });
 
   RetainingPath._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -7216,8 +7216,8 @@
   String? valueAsString;
 
   Sentinel({
-    required this.kind,
-    required this.valueAsString,
+    this.kind,
+    this.valueAsString,
   });
 
   Sentinel._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -7252,7 +7252,7 @@
   String? uri;
 
   ScriptRef({
-    required this.uri,
+    this.uri,
     required String id,
   }) : super(
           id: id,
@@ -7338,8 +7338,8 @@
   List<List<int>>? tokenPosTable;
 
   Script({
-    required this.uri,
-    required this.library,
+    this.uri,
+    this.library,
     required String id,
     this.lineOffset,
     this.columnOffset,
@@ -7424,7 +7424,7 @@
   List<ScriptRef>? scripts;
 
   ScriptList({
-    required this.scripts,
+    this.scripts,
   });
 
   ScriptList._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -7476,8 +7476,8 @@
   int? column;
 
   SourceLocation({
-    required this.script,
-    required this.tokenPos,
+    this.script,
+    this.tokenPos,
     this.endTokenPos,
     this.line,
     this.column,
@@ -7533,8 +7533,8 @@
   List<ScriptRef>? scripts;
 
   SourceReport({
-    required this.ranges,
-    required this.scripts,
+    this.ranges,
+    this.scripts,
   });
 
   SourceReport._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -7580,8 +7580,8 @@
   List<int>? misses;
 
   SourceReportCoverage({
-    required this.hits,
-    required this.misses,
+    this.hits,
+    this.misses,
   });
 
   SourceReportCoverage._fromJson(Map<String, dynamic> json) {
@@ -7648,10 +7648,10 @@
   SourceReportCoverage? branchCoverage;
 
   SourceReportRange({
-    required this.scriptIndex,
-    required this.startPos,
-    required this.endPos,
-    required this.compiled,
+    this.scriptIndex,
+    this.startPos,
+    this.endPos,
+    this.compiled,
     this.error,
     this.coverage,
     this.possibleBreakpoints,
@@ -7726,9 +7726,9 @@
   bool? truncated;
 
   Stack({
-    required this.frames,
-    required this.messages,
-    required this.truncated,
+    this.frames,
+    this.messages,
+    this.truncated,
     this.asyncCausalFrames,
     this.awaiterFrames,
   });
@@ -7814,9 +7814,9 @@
   int? timeExtentMicros;
 
   Timeline({
-    required this.traceEvents,
-    required this.timeOriginMicros,
-    required this.timeExtentMicros,
+    this.traceEvents,
+    this.timeOriginMicros,
+    this.timeExtentMicros,
   });
 
   Timeline._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -7888,9 +7888,9 @@
   List<String>? recordedStreams;
 
   TimelineFlags({
-    required this.recorderName,
-    required this.availableStreams,
-    required this.recordedStreams,
+    this.recorderName,
+    this.availableStreams,
+    this.recordedStreams,
   });
 
   TimelineFlags._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -7927,7 +7927,7 @@
   int? timestamp;
 
   Timestamp({
-    required this.timestamp,
+    this.timestamp,
   });
 
   Timestamp._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -7959,7 +7959,7 @@
   String? name;
 
   TypeArgumentsRef({
-    required this.name,
+    this.name,
     required String id,
   }) : super(
           id: id,
@@ -8006,8 +8006,8 @@
   List<InstanceRef>? types;
 
   TypeArguments({
-    required this.name,
-    required this.types,
+    this.name,
+    this.types,
     required String id,
   }) : super(
           id: id,
@@ -8058,9 +8058,9 @@
   TypeArgumentsRef? defaults;
 
   TypeParameters({
-    required this.names,
-    required this.bounds,
-    required this.defaults,
+    this.names,
+    this.bounds,
+    this.defaults,
   });
 
   TypeParameters._fromJson(Map<String, dynamic> json) {
@@ -8167,7 +8167,7 @@
   List<String?>? uris;
 
   UriList({
-    required this.uris,
+    this.uris,
   });
 
   UriList._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -8204,8 +8204,8 @@
   int? minor;
 
   Version({
-    required this.major,
-    required this.minor,
+    this.major,
+    this.minor,
   });
 
   Version._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -8239,7 +8239,7 @@
   String? name;
 
   VMRef({
-    required this.name,
+    this.name,
   });
 
   VMRef._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -8305,18 +8305,18 @@
   List<IsolateGroupRef>? systemIsolateGroups;
 
   VM({
-    required this.name,
-    required this.architectureBits,
-    required this.hostCPU,
-    required this.operatingSystem,
-    required this.targetCPU,
-    required this.version,
-    required this.pid,
-    required this.startTime,
-    required this.isolates,
-    required this.isolateGroups,
-    required this.systemIsolates,
-    required this.systemIsolateGroups,
+    this.name,
+    this.architectureBits,
+    this.hostCPU,
+    this.operatingSystem,
+    this.targetCPU,
+    this.version,
+    this.pid,
+    this.startTime,
+    this.isolates,
+    this.isolateGroups,
+    this.systemIsolates,
+    this.systemIsolateGroups,
   });
 
   VM._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 68572038..0f11496 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -1,5 +1,5 @@
 name: vm_service
-version: 9.0.0
+version: 9.1.0
 description: >-
   A library to communicate with a service implementing the Dart VM
   service protocol.
diff --git a/pkg/vm_service/tool/dart/generate_dart.dart b/pkg/vm_service/tool/dart/generate_dart.dart
index e15328c..ee8df6c 100644
--- a/pkg/vm_service/tool/dart/generate_dart.dart
+++ b/pkg/vm_service/tool/dart/generate_dart.dart
@@ -1829,13 +1829,10 @@
   }
 
   void generateNamedParameter(DartGenerator gen, {bool fromParent = false}) {
-    if (!optional) {
-      gen.write('required ');
-    }
     if (fromParent) {
       String? typeName =
           api.isEnumName(type.name) ? '/*${type.name}*/ String' : type.name;
-      gen.writeStatement('$typeName ${generatableName},');
+      gen.writeStatement('required $typeName ${generatableName},');
     } else {
       gen.writeStatement('this.${generatableName},');
     }
diff --git a/tools/VERSION b/tools/VERSION
index 3e29893..768d7f6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 283
+PRERELEASE 284
 PRERELEASE_PATCH 0
\ No newline at end of file