[analysis_server] [lsp] Fix navigation for field/super formal params

Fixes https://github.com/Dart-Code/Dart-Code/issues/4031.

Change-Id: Ic1a6ec5407cf3599bb4b88d5be1ff8c3efe50066
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251108
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_execute_command.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_execute_command.dart
index e75e0f1..2454202 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_execute_command.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_execute_command.dart
@@ -52,11 +52,11 @@
             ? ProgressReporter.serverCreated(server)
             : ProgressReporter.noop;
 
-    // To make parsing arguments easier in commands, instead of a
+    // To make passing arguments easier in commands, instead of a
     // `List<Object?>` we now use `Map<String, Object?>`.
     //
     // However, some handlers still support the list for compatibility so we
-    // must allow the to convert a `List` to a `Map`.
+    // must allow them to convert a `List` to a `Map`.
     final arguments = params.arguments ?? const [];
     Map<String, Object?> commandParams;
     if (arguments.length == 1 && arguments[0] is Map<String, Object?>) {
diff --git a/pkg/analysis_server/test/lsp/definition_test.dart b/pkg/analysis_server/test/lsp/definition_test.dart
index 1577e53..8c6c351 100644
--- a/pkg/analysis_server/test/lsp/definition_test.dart
+++ b/pkg/analysis_server/test/lsp/definition_test.dart
@@ -173,6 +173,17 @@
     await testContents(contents);
   }
 
+  Future<void> test_fieldFormalParam() async {
+    final contents = '''
+class A {
+  final String [[a]];
+  A(this.^a});
+}
+''';
+
+    await testContents(contents);
+  }
+
   Future<void> test_fromPlugins() async {
     final pluginAnalyzedFilePath = join(projectFolderPath, 'lib', 'foo.foo');
     final pluginAnalyzedFileUri = Uri.file(pluginAnalyzedFilePath);
@@ -402,6 +413,19 @@
     await testContents(contents);
   }
 
+  Future<void> test_superFormalParam() async {
+    final contents = '''
+class A {
+  A({required int [[a]]});
+}
+class B extends A {
+  B({required super.^a}) : assert(a > 0);
+}
+''';
+
+    await testContents(contents);
+  }
+
   Future<void> test_type() async {
     final contents = '''
 f() {
diff --git a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
index 418ea31..23df1dd 100644
--- a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
@@ -26,17 +26,39 @@
     unit.accept(visitor);
   } else {
     var node = _getNodeForRange(unit, offset, length);
-    // Take the outer-most node that shares this offset/length so that we get
-    // things like ConstructorName instead of SimpleIdentifier.
-    // https://github.com/dart-lang/sdk/issues/46725
     if (node != null) {
-      node = _getOutermostNode(node);
+      node = _getNavigationTargetNode(node);
     }
     node?.accept(visitor);
   }
   return collector;
 }
 
+/// Gets the nearest node that should be used for navigation.
+///
+/// This is usually the outermost node with the same offset as node but in some
+/// cases may be a different ancestor where required to produce the correct
+/// result.
+AstNode _getNavigationTargetNode(AstNode node) {
+  AstNode? current = node;
+  while (current != null &&
+      current.parent != null &&
+      current.offset == current.parent!.offset) {
+    current = current.parent;
+  }
+  current ??= node;
+
+  // To navigate to formal params, we need to visit the parameter and not just
+  // the identifier but they don't start at the same offset as they have a
+  // prefix.
+  final parent = current.parent;
+  if (parent is FormalParameter) {
+    current = parent;
+  }
+
+  return current;
+}
+
 AstNode? _getNodeForRange(CompilationUnit unit, int offset, int length) {
   var node = NodeLocator(offset, offset + length).searchWithin(unit);
   for (var n = node; n != null; n = n.parent) {
@@ -47,21 +69,6 @@
   return node;
 }
 
-/// Gets the outer-most node with the same offset as node.
-///
-/// This reduces the number of nodes the visitor needs to walk when collecting
-/// navigation for a specific location in the file.
-AstNode _getOutermostNode(AstNode node) {
-  AstNode? current = node;
-  while (current != null &&
-      current.parent != null &&
-      current != current.parent &&
-      current.offset == current.parent!.offset) {
-    current = current.parent;
-  }
-  return current ?? node;
-}
-
 /// A Dart specific wrapper around [NavigationCollector].
 class _DartNavigationCollector {
   final NavigationCollector collector;