Add LSP method to fetch super method/class for a position

Change-Id: If8f28a708312113916cc9cecd75e768f7fec393b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104823
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Danny Tuppeny <dantup@google.com>
diff --git a/pkg/analysis_server/lib/src/lsp/constants.dart b/pkg/analysis_server/lib/src/lsp/constants.dart
index 5e0c09b..1801317 100644
--- a/pkg/analysis_server/lib/src/lsp/constants.dart
+++ b/pkg/analysis_server/lib/src/lsp/constants.dart
@@ -53,6 +53,7 @@
   static const DiagnosticServer = const Method('dart/diagnosticServer');
   static const PublishClosingLabels =
       const Method('dart/textDocument/publishClosingLabels');
+  static const Super = const Method('dart/textDocument/super');
   static const AnalyzerStatus = const Method(r'$/analyzerStatus');
 }
 
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/custom/handler_super.dart b/pkg/analysis_server/lib/src/lsp/handlers/custom/handler_super.dart
new file mode 100644
index 0000000..79c8ccb
--- /dev/null
+++ b/pkg/analysis_server/lib/src/lsp/handlers/custom/handler_super.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2019, 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/lsp/constants.dart';
+import 'package:analysis_server/src/lsp/handlers/handlers.dart';
+import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
+import 'package:analysis_server/src/lsp/mapping.dart';
+import 'package:analysis_server/src/search/type_hierarchy.dart';
+import 'package:analyzer/dart/element/element.dart';
+
+class SuperHandler
+    extends MessageHandler<TextDocumentPositionParams, Location> {
+  SuperHandler(LspAnalysisServer server) : super(server);
+  Method get handlesMessage => CustomMethods.Super;
+
+  @override
+  LspJsonHandler<TextDocumentPositionParams> get jsonHandler =>
+      TextDocumentPositionParams.jsonHandler;
+
+  @override
+  Future<ErrorOr<Location>> handle(
+      TextDocumentPositionParams params, CancellationToken token) async {
+    if (!isDartDocument(params.textDocument)) {
+      return success(null);
+    }
+
+    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) async {
+      var node = await server.getNodeAtOffset(path.result, offset);
+      if (node == null) {
+        return success(null);
+      }
+
+      // Walk up the nodes until we find one that has an element so we can support
+      // finding supers even if the cursor location was inside a method or on its
+      // return type.
+      var element = server.getElementOfNode(node);
+      while (element == null && node.parent != null) {
+        node = node.parent;
+        element = server.getElementOfNode(node);
+      }
+      if (element == null) {
+        return success(null);
+      }
+
+      final computer = TypeHierarchyComputer(server.searchEngine, element);
+      final items = computer.computeSuper();
+
+      // We expect to get at least two items back - the first will be the input
+      // element so we start looking from the second.
+      if (items == null || items.length < 2) {
+        return success(null);
+      }
+
+      // The class will have a memberElement if we were searching for an element
+      // otherwise we're looking for a class.
+      final isMember = items.first.memberElement != null;
+      final superItem = items.skip(1).firstWhere(
+            (elm) =>
+                isMember ? elm.memberElement != null : elm.classElement != null,
+            orElse: () => null,
+          );
+
+      if (superItem == null) {
+        return success(null);
+      }
+
+      final location = isMember
+          ? superItem.memberElement.location
+          : superItem.classElement.location;
+
+      return success(toLocation(location, server.getLineInfo(location.file)));
+    });
+  }
+}
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 a4302ba..a0e1d15 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
@@ -8,6 +8,7 @@
 import 'package:analysis_server/lsp_protocol/protocol_special.dart';
 import 'package:analysis_server/src/lsp/constants.dart';
 import 'package:analysis_server/src/lsp/handlers/custom/handler_diagnostic_server.dart';
+import 'package:analysis_server/src/lsp/handlers/custom/handler_super.dart';
 import 'package:analysis_server/src/lsp/handlers/handler_code_actions.dart';
 import 'package:analysis_server/src/lsp/handlers/handler_completion.dart';
 import 'package:analysis_server/src/lsp/handlers/handler_completion_resolve.dart';
@@ -79,6 +80,7 @@
     registerHandler(new CompletionResolveHandler(server));
     registerHandler(new SignatureHelpHandler(server));
     registerHandler(new DefinitionHandler(server));
+    registerHandler(new SuperHandler(server));
     registerHandler(new ReferencesHandler(server));
     registerHandler(new ImplementationHandler(server));
     registerHandler(new FormattingHandler(server));
diff --git a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
index 2cfd0a9..301e631 100644
--- a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
+++ b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
@@ -344,6 +344,9 @@
   void logException(String message, exception, stackTrace) {
     if (exception is CaughtException) {
       stackTrace ??= exception.stackTrace;
+      message = '$message: ${exception.exception}';
+    } else if (exception != null) {
+      message = '$message: $exception';
     }
 
     final fullError = stackTrace == null ? message : '$message\n$stackTrace';
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index 0c4ef07..a548530 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -650,6 +650,16 @@
       .toList();
 }
 
+lsp.Location toLocation(server.Location location, server.LineInfo lineInfo) =>
+    lsp.Location(
+      Uri.file(location.file).toString(),
+      toRange(
+        lineInfo,
+        location.offset,
+        location.length,
+      ),
+    );
+
 ErrorOr<int> toOffset(
   server.LineInfo lineInfo,
   lsp.Position pos, {
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index 647cc28..a531618 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -486,6 +486,20 @@
     return expectSuccessfulResponseTo<SignatureHelp>(request);
   }
 
+  Future<Location> getSuper(
+    Uri uri,
+    Position pos,
+  ) {
+    final request = makeRequest(
+      CustomMethods.Super,
+      new TextDocumentPositionParams(
+        new TextDocumentIdentifier(uri.toString()),
+        pos,
+      ),
+    );
+    return expectSuccessfulResponseTo<Location>(request);
+  }
+
   /// Executes [f] then waits for a request of type [method] from the server which
   /// is passed to [handler] to process, then waits for (and returns) the
   /// response to the original request.
diff --git a/pkg/analysis_server/test/lsp/super_test.dart b/pkg/analysis_server/test/lsp/super_test.dart
new file mode 100644
index 0000000..d1bc684
--- /dev/null
+++ b/pkg/analysis_server/test/lsp/super_test.dart
@@ -0,0 +1,146 @@
+// Copyright (c) 2019, 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 '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';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SuperTest);
+  });
+}
+
+@reflectiveTest
+class SuperTest extends AbstractLspAnalysisServerTest {
+  test_className() async {
+    final content = '''
+class A {}
+
+class [[B]] extends A {}
+
+class C^ extends B {}
+''';
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getSuper(
+      mainFileUri,
+      positionFromMarker(content),
+    );
+
+    expect(
+        res,
+        equals(
+            new Location(mainFileUri.toString(), rangeFromMarkers(content))));
+  }
+
+  test_insideClass() async {
+    final content = '''
+class A {}
+
+class [[B]] extends A {}
+
+class C extends B {
+  ^
+}
+''';
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getSuper(
+      mainFileUri,
+      positionFromMarker(content),
+    );
+
+    expect(
+        res,
+        equals(
+            new Location(mainFileUri.toString(), rangeFromMarkers(content))));
+  }
+
+  test_insideMethod() async {
+    final content = '''
+class A {
+  void [[foo]]() {}
+}
+
+class B extends A {}
+
+class C extends B {
+  @override
+  void foo() {
+    // fo^oC
+  }
+}
+''';
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getSuper(
+      mainFileUri,
+      positionFromMarker(content),
+    );
+
+    expect(
+        res,
+        equals(
+            new Location(mainFileUri.toString(), rangeFromMarkers(content))));
+  }
+
+  test_methodName() async {
+    final content = '''
+class A {
+  void [[foo]]() {}
+}
+
+class B extends A {}
+
+class C extends B {
+  @override
+  void fo^o() {
+    // fooC
+  }
+}
+''';
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getSuper(
+      mainFileUri,
+      positionFromMarker(content),
+    );
+
+    expect(
+        res,
+        equals(
+            new Location(mainFileUri.toString(), rangeFromMarkers(content))));
+  }
+
+  test_methodReturnType() async {
+    final content = '''
+class A {
+  void [[foo]]() {}
+}
+
+class B extends A {}
+
+class C extends B {
+  @override
+  vo^id foo() {
+    // fooC
+  }
+}
+''';
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getSuper(
+      mainFileUri,
+      positionFromMarker(content),
+    );
+
+    expect(
+        res,
+        equals(
+            new Location(mainFileUri.toString(), rangeFromMarkers(content))));
+  }
+}
diff --git a/pkg/analysis_server/test/lsp/test_all.dart b/pkg/analysis_server/test/lsp/test_all.dart
index a05ca45..f1cc08c 100644
--- a/pkg/analysis_server/test/lsp/test_all.dart
+++ b/pkg/analysis_server/test/lsp/test_all.dart
@@ -20,6 +20,7 @@
 import 'file_modification_test.dart' as file_modification;
 import 'folding_test.dart' as folding;
 import 'format_test.dart' as format;
+import 'super_test.dart' as get_super;
 import 'hover_test.dart' as hover;
 import 'implementation_test.dart' as implementation;
 import 'initialization_test.dart' as initialization;
@@ -48,6 +49,7 @@
     file_modification.main();
     folding.main();
     format.main();
+    get_super.main();
     hover.main();
     implementation.main();
     initialization.main();
diff --git a/pkg/analysis_server/tool/lsp_spec/README.md b/pkg/analysis_server/tool/lsp_spec/README.md
index 0d0ac7e..d0cd4be 100644
--- a/pkg/analysis_server/tool/lsp_spec/README.md
+++ b/pkg/analysis_server/tool/lsp_spec/README.md
@@ -99,6 +99,14 @@
 
 Starts the analzyer diagnostics server (if not already running) and returns the port number it's listening on.
 
+### dart/textDocument/super Method
+
+Direction: Client -> Server
+Params: `TextDocumentPositionParams`
+Returns: `Location | null`
+
+Returns the location of the super definition of the class or method at the provided position or `null` if there isn't one.
+
 ### $/analyzerStatus Notification
 
 Direction: Server -> Client