[Analyzer] Split LSP completion resolution info into base/sub-classes
Change-Id: I57f61b8d0467c32562d865de3b87d75995cfb35f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/174129
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/lsp_protocol/protocol_custom_generated.dart b/pkg/analysis_server/lib/lsp_protocol/protocol_custom_generated.dart
index 5c97433..16daa2d 100644
--- a/pkg/analysis_server/lib/lsp_protocol/protocol_custom_generated.dart
+++ b/pkg/analysis_server/lib/lsp_protocol/protocol_custom_generated.dart
@@ -192,66 +192,30 @@
CompletionItemResolutionInfo.canParse,
CompletionItemResolutionInfo.fromJson);
- CompletionItemResolutionInfo(
- {@required this.file,
- @required this.offset,
- @required this.libId,
- @required this.displayUri,
- @required this.rOffset,
- @required this.rLength}) {
+ CompletionItemResolutionInfo({@required this.file, @required this.offset}) {
if (file == null) {
throw 'file is required but was not provided';
}
if (offset == null) {
throw 'offset is required but was not provided';
}
- if (libId == null) {
- throw 'libId is required but was not provided';
- }
- if (displayUri == null) {
- throw 'displayUri is required but was not provided';
- }
- if (rOffset == null) {
- throw 'rOffset is required but was not provided';
- }
- if (rLength == null) {
- throw 'rLength is required but was not provided';
- }
}
static CompletionItemResolutionInfo fromJson(Map<String, dynamic> json) {
+ if (DartCompletionItemResolutionInfo.canParse(json, nullLspJsonReporter)) {
+ return DartCompletionItemResolutionInfo.fromJson(json);
+ }
final file = json['file'];
final offset = json['offset'];
- final libId = json['libId'];
- final displayUri = json['displayUri'];
- final rOffset = json['rOffset'];
- final rLength = json['rLength'];
- return CompletionItemResolutionInfo(
- file: file,
- offset: offset,
- libId: libId,
- displayUri: displayUri,
- rOffset: rOffset,
- rLength: rLength);
+ return CompletionItemResolutionInfo(file: file, offset: offset);
}
- final String displayUri;
final String file;
- final num libId;
final num offset;
- final num rLength;
- final num rOffset;
Map<String, dynamic> toJson() {
var __result = <String, dynamic>{};
__result['file'] = file ?? (throw 'file is required but was not set');
__result['offset'] = offset ?? (throw 'offset is required but was not set');
- __result['libId'] = libId ?? (throw 'libId is required but was not set');
- __result['displayUri'] =
- displayUri ?? (throw 'displayUri is required but was not set');
- __result['rOffset'] =
- rOffset ?? (throw 'rOffset is required but was not set');
- __result['rLength'] =
- rLength ?? (throw 'rLength is required but was not set');
return __result;
}
@@ -291,6 +255,105 @@
} finally {
reporter.pop();
}
+ return true;
+ } else {
+ reporter.reportError('must be of type CompletionItemResolutionInfo');
+ return false;
+ }
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (other is CompletionItemResolutionInfo &&
+ other.runtimeType == CompletionItemResolutionInfo) {
+ return file == other.file && offset == other.offset && true;
+ }
+ return false;
+ }
+
+ @override
+ int get hashCode {
+ var hash = 0;
+ hash = JenkinsSmiHash.combine(hash, file.hashCode);
+ hash = JenkinsSmiHash.combine(hash, offset.hashCode);
+ return JenkinsSmiHash.finish(hash);
+ }
+
+ @override
+ String toString() => jsonEncoder.convert(toJson());
+}
+
+class DartCompletionItemResolutionInfo
+ implements CompletionItemResolutionInfo, ToJsonable {
+ static const jsonHandler = LspJsonHandler(
+ DartCompletionItemResolutionInfo.canParse,
+ DartCompletionItemResolutionInfo.fromJson);
+
+ DartCompletionItemResolutionInfo(
+ {@required this.libId,
+ @required this.displayUri,
+ @required this.rOffset,
+ @required this.rLength,
+ @required this.file,
+ @required this.offset}) {
+ if (libId == null) {
+ throw 'libId is required but was not provided';
+ }
+ if (displayUri == null) {
+ throw 'displayUri is required but was not provided';
+ }
+ if (rOffset == null) {
+ throw 'rOffset is required but was not provided';
+ }
+ if (rLength == null) {
+ throw 'rLength is required but was not provided';
+ }
+ if (file == null) {
+ throw 'file is required but was not provided';
+ }
+ if (offset == null) {
+ throw 'offset is required but was not provided';
+ }
+ }
+ static DartCompletionItemResolutionInfo fromJson(Map<String, dynamic> json) {
+ final libId = json['libId'];
+ final displayUri = json['displayUri'];
+ final rOffset = json['rOffset'];
+ final rLength = json['rLength'];
+ final file = json['file'];
+ final offset = json['offset'];
+ return DartCompletionItemResolutionInfo(
+ libId: libId,
+ displayUri: displayUri,
+ rOffset: rOffset,
+ rLength: rLength,
+ file: file,
+ offset: offset);
+ }
+
+ final String displayUri;
+ final String file;
+ final num libId;
+ final num offset;
+ final num rLength;
+ final num rOffset;
+
+ Map<String, dynamic> toJson() {
+ var __result = <String, dynamic>{};
+ __result['libId'] = libId ?? (throw 'libId is required but was not set');
+ __result['displayUri'] =
+ displayUri ?? (throw 'displayUri is required but was not set');
+ __result['rOffset'] =
+ rOffset ?? (throw 'rOffset is required but was not set');
+ __result['rLength'] =
+ rLength ?? (throw 'rLength is required but was not set');
+ __result['file'] = file ?? (throw 'file is required but was not set');
+ __result['offset'] = offset ?? (throw 'offset is required but was not set');
+ return __result;
+ }
+
+ static bool canParse(Object obj, LspJsonReporter reporter) {
+ if (obj is Map<String, dynamic>) {
reporter.push('libId');
try {
if (!obj.containsKey('libId')) {
@@ -359,23 +422,57 @@
} finally {
reporter.pop();
}
+ reporter.push('file');
+ try {
+ if (!obj.containsKey('file')) {
+ reporter.reportError('must not be undefined');
+ return false;
+ }
+ if (obj['file'] == null) {
+ reporter.reportError('must not be null');
+ return false;
+ }
+ if (!(obj['file'] is String)) {
+ reporter.reportError('must be of type String');
+ return false;
+ }
+ } finally {
+ reporter.pop();
+ }
+ reporter.push('offset');
+ try {
+ if (!obj.containsKey('offset')) {
+ reporter.reportError('must not be undefined');
+ return false;
+ }
+ if (obj['offset'] == null) {
+ reporter.reportError('must not be null');
+ return false;
+ }
+ if (!(obj['offset'] is num)) {
+ reporter.reportError('must be of type num');
+ return false;
+ }
+ } finally {
+ reporter.pop();
+ }
return true;
} else {
- reporter.reportError('must be of type CompletionItemResolutionInfo');
+ reporter.reportError('must be of type DartCompletionItemResolutionInfo');
return false;
}
}
@override
bool operator ==(Object other) {
- if (other is CompletionItemResolutionInfo &&
- other.runtimeType == CompletionItemResolutionInfo) {
- return file == other.file &&
- offset == other.offset &&
- libId == other.libId &&
+ if (other is DartCompletionItemResolutionInfo &&
+ other.runtimeType == DartCompletionItemResolutionInfo) {
+ return libId == other.libId &&
displayUri == other.displayUri &&
rOffset == other.rOffset &&
rLength == other.rLength &&
+ file == other.file &&
+ offset == other.offset &&
true;
}
return false;
@@ -384,12 +481,12 @@
@override
int get hashCode {
var hash = 0;
- hash = JenkinsSmiHash.combine(hash, file.hashCode);
- hash = JenkinsSmiHash.combine(hash, offset.hashCode);
hash = JenkinsSmiHash.combine(hash, libId.hashCode);
hash = JenkinsSmiHash.combine(hash, displayUri.hashCode);
hash = JenkinsSmiHash.combine(hash, rOffset.hashCode);
hash = JenkinsSmiHash.combine(hash, rLength.hashCode);
+ hash = JenkinsSmiHash.combine(hash, file.hashCode);
+ hash = JenkinsSmiHash.combine(hash, offset.hashCode);
return JenkinsSmiHash.finish(hash);
}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
index d7f1610..c7be4e9 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
@@ -2,6 +2,7 @@
// 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_custom_generated.dart';
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';
@@ -15,10 +16,11 @@
class CompletionResolveHandler
extends MessageHandler<CompletionItem, CompletionItem> {
+ /// The last completion item we asked to be resolved.
///
- /// The latest completion item we were asked to resolve. We use it to abort
- /// previous requests.
- ///
+ /// Used to abort previous requests in async handlers if another resolve request
+ /// arrives while the previous is being processed (for clients that don't send
+ /// cancel events).
CompletionItem _latestCompletionItem;
CompletionResolveHandler(LspAnalysisServer server) : super(server);
@@ -31,13 +33,23 @@
@override
Future<ErrorOr<CompletionItem>> handle(
- CompletionItem item, CancellationToken token) async {
- // If this isn't an item with resolution data, return the same item back.
- if (item.data == null) {
+ CompletionItem item,
+ CancellationToken token,
+ ) async {
+ final resolutionInfo = item.data;
+
+ if (resolutionInfo is DartCompletionItemResolutionInfo) {
+ return resolveDartCompletion(item, resolutionInfo, token);
+ } else {
return success(item);
}
+ }
- final data = item.data;
+ Future<ErrorOr<CompletionItem>> resolveDartCompletion(
+ CompletionItem item,
+ DartCompletionItemResolutionInfo data,
+ CancellationToken token,
+ ) async {
final lineInfo = server.getLineInfo(data.file);
if (lineInfo == null) {
return error(
@@ -166,7 +178,7 @@
textEdit: TextEdit(
// TODO(dantup): If `clientSupportsSnippets == true` then we should map
// `selection` in to a snippet (see how Dart Code does this).
- range: toRange(lineInfo, item.data.rOffset, item.data.rLength),
+ range: toRange(lineInfo, data.rOffset, data.rLength),
newText: newInsertText,
),
additionalTextEdits: thisFilesChanges
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index ce49475..56c7ee0 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -301,7 +301,7 @@
? insertText
: null, // insertText uses label if not set
// data, used for completionItem/resolve.
- data: lsp.CompletionItemResolutionInfo(
+ data: lsp.DartCompletionItemResolutionInfo(
file: file,
offset: offset,
libId: includedSuggestionSet.id,
@@ -783,6 +783,7 @@
int replacementLength, {
@required bool includeCommitCharacters,
@required bool completeFunctionCalls,
+ Object resolutionData,
}) {
// Build display labels and text to insert. insertText and filterText may
// differ from label (for ex. if the label includes things like (…)). If
@@ -876,6 +877,7 @@
: null,
commitCharacters:
includeCommitCharacters ? dartCompletionCommitCharacters : null,
+ data: resolutionData,
detail: getCompletionDetail(suggestion, completionKind,
supportsDeprecatedFlag || supportsDeprecatedTag),
documentation:
diff --git a/pkg/analysis_server/tool/lsp_spec/generate_all.dart b/pkg/analysis_server/tool/lsp_spec/generate_all.dart
index 26b8916..2998c8f 100644
--- a/pkg/analysis_server/tool/lsp_spec/generate_all.dart
+++ b/pkg/analysis_server/tool/lsp_spec/generate_all.dart
@@ -182,8 +182,14 @@
''';
List<AstNode> getCustomClasses() {
- Interface interface(String name, List<Member> fields) {
- return Interface(null, Token.identifier(name), [], [], fields);
+ Interface interface(String name, List<Member> fields, {String baseType}) {
+ return Interface(
+ null,
+ Token.identifier(name),
+ [],
+ [if (baseType != null) Type.identifier(baseType)],
+ fields,
+ );
}
Field field(String name,
@@ -250,11 +256,17 @@
[
field('file', type: 'string'),
field('offset', type: 'number'),
+ ],
+ ),
+ interface(
+ 'DartCompletionItemResolutionInfo',
+ [
field('libId', type: 'number'),
field('displayUri', type: 'string'),
field('rOffset', type: 'number'),
- field('rLength', type: 'number')
+ field('rLength', type: 'number'),
],
+ baseType: 'CompletionItemResolutionInfo',
),
];
return customTypes;