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