Skip to content

Commit 699b42c

Browse files
DanTupCommit Queue
authored and
Commit Queue
committed
[analysis_server] Support adding multiple imports for @OVERRIDES completions
Related to Dart-Code/Dart-Code#4116. Change-Id: I81b9e7bfbe4bbd093770925f9dcb640b72487142 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/270181 Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 3602351 commit 699b42c

File tree

8 files changed

+239
-115
lines changed

8 files changed

+239
-115
lines changed

pkg/analysis_server/lib/lsp_protocol/protocol_custom_generated.dart

Lines changed: 100 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,8 @@ class CompletionItemResolutionInfo implements ToJsonable {
211211
);
212212

213213
static CompletionItemResolutionInfo fromJson(Map<String, Object?> json) {
214-
if (DartNotImportedCompletionResolutionInfo.canParse(
215-
json, nullLspJsonReporter)) {
216-
return DartNotImportedCompletionResolutionInfo.fromJson(json);
214+
if (DartCompletionResolutionInfo.canParse(json, nullLspJsonReporter)) {
215+
return DartCompletionResolutionInfo.fromJson(json);
217216
}
218217
if (PubPackageCompletionItemResolutionInfo.canParse(
219218
json, nullLspJsonReporter)) {
@@ -247,79 +246,30 @@ class CompletionItemResolutionInfo implements ToJsonable {
247246
String toString() => jsonEncoder.convert(toJson());
248247
}
249248

250-
class DartDiagnosticServer implements ToJsonable {
251-
static const jsonHandler = LspJsonHandler(
252-
DartDiagnosticServer.canParse,
253-
DartDiagnosticServer.fromJson,
254-
);
255-
256-
DartDiagnosticServer({
257-
required this.port,
258-
});
259-
static DartDiagnosticServer fromJson(Map<String, Object?> json) {
260-
final portJson = json['port'];
261-
final port = portJson as int;
262-
return DartDiagnosticServer(
263-
port: port,
264-
);
265-
}
266-
267-
final int port;
268-
269-
@override
270-
Map<String, Object?> toJson() {
271-
var result = <String, Object?>{};
272-
result['port'] = port;
273-
return result;
274-
}
275-
276-
static bool canParse(Object? obj, LspJsonReporter reporter) {
277-
if (obj is Map<String, Object?>) {
278-
return _canParseInt(obj, reporter, 'port',
279-
allowsUndefined: false, allowsNull: false);
280-
} else {
281-
reporter.reportError('must be of type DartDiagnosticServer');
282-
return false;
283-
}
284-
}
285-
286-
@override
287-
bool operator ==(Object other) {
288-
return other is DartDiagnosticServer &&
289-
other.runtimeType == DartDiagnosticServer &&
290-
port == other.port;
291-
}
292-
293-
@override
294-
int get hashCode => port.hashCode;
295-
296-
@override
297-
String toString() => jsonEncoder.convert(toJson());
298-
}
299-
300-
class DartNotImportedCompletionResolutionInfo
249+
class DartCompletionResolutionInfo
301250
implements CompletionItemResolutionInfo, ToJsonable {
302251
static const jsonHandler = LspJsonHandler(
303-
DartNotImportedCompletionResolutionInfo.canParse,
304-
DartNotImportedCompletionResolutionInfo.fromJson,
252+
DartCompletionResolutionInfo.canParse,
253+
DartCompletionResolutionInfo.fromJson,
305254
);
306255

307-
DartNotImportedCompletionResolutionInfo({
256+
DartCompletionResolutionInfo({
308257
required this.file,
309-
required this.libraryUri,
258+
required this.importUris,
310259
this.ref,
311260
});
312-
static DartNotImportedCompletionResolutionInfo fromJson(
313-
Map<String, Object?> json) {
261+
static DartCompletionResolutionInfo fromJson(Map<String, Object?> json) {
314262
final fileJson = json['file'];
315263
final file = fileJson as String;
316-
final libraryUriJson = json['libraryUri'];
317-
final libraryUri = libraryUriJson as String;
264+
final importUrisJson = json['importUris'];
265+
final importUris = (importUrisJson as List<Object?>)
266+
.map((item) => item as String)
267+
.toList();
318268
final refJson = json['ref'];
319269
final ref = refJson as String?;
320-
return DartNotImportedCompletionResolutionInfo(
270+
return DartCompletionResolutionInfo(
321271
file: file,
322-
libraryUri: libraryUri,
272+
importUris: importUris,
323273
ref: ref,
324274
);
325275
}
@@ -329,8 +279,8 @@ class DartNotImportedCompletionResolutionInfo
329279
/// This is used to compute where to add the import.
330280
final String file;
331281

332-
/// The URI to be imported if this completion is selected.
333-
final String libraryUri;
282+
/// The URIs to be imported if this completion is selected.
283+
final List<String> importUris;
334284

335285
/// The ElementLocation of the item being completed.
336286
///
@@ -341,7 +291,7 @@ class DartNotImportedCompletionResolutionInfo
341291
Map<String, Object?> toJson() {
342292
var result = <String, Object?>{};
343293
result['file'] = file;
344-
result['libraryUri'] = libraryUri;
294+
result['importUris'] = importUris;
345295
if (ref != null) {
346296
result['ref'] = ref;
347297
}
@@ -354,39 +304,89 @@ class DartNotImportedCompletionResolutionInfo
354304
allowsUndefined: false, allowsNull: false)) {
355305
return false;
356306
}
357-
if (!_canParseString(obj, reporter, 'libraryUri',
307+
if (!_canParseListString(obj, reporter, 'importUris',
358308
allowsUndefined: false, allowsNull: false)) {
359309
return false;
360310
}
361311
return _canParseString(obj, reporter, 'ref',
362312
allowsUndefined: true, allowsNull: false);
363313
} else {
364-
reporter.reportError(
365-
'must be of type DartNotImportedCompletionResolutionInfo');
314+
reporter.reportError('must be of type DartCompletionResolutionInfo');
366315
return false;
367316
}
368317
}
369318

370319
@override
371320
bool operator ==(Object other) {
372-
return other is DartNotImportedCompletionResolutionInfo &&
373-
other.runtimeType == DartNotImportedCompletionResolutionInfo &&
321+
return other is DartCompletionResolutionInfo &&
322+
other.runtimeType == DartCompletionResolutionInfo &&
374323
file == other.file &&
375-
libraryUri == other.libraryUri &&
324+
listEqual(
325+
importUris, other.importUris, (String a, String b) => a == b) &&
376326
ref == other.ref;
377327
}
378328

379329
@override
380330
int get hashCode => Object.hash(
381331
file,
382-
libraryUri,
332+
lspHashCode(importUris),
383333
ref,
384334
);
385335

386336
@override
387337
String toString() => jsonEncoder.convert(toJson());
388338
}
389339

340+
class DartDiagnosticServer implements ToJsonable {
341+
static const jsonHandler = LspJsonHandler(
342+
DartDiagnosticServer.canParse,
343+
DartDiagnosticServer.fromJson,
344+
);
345+
346+
DartDiagnosticServer({
347+
required this.port,
348+
});
349+
static DartDiagnosticServer fromJson(Map<String, Object?> json) {
350+
final portJson = json['port'];
351+
final port = portJson as int;
352+
return DartDiagnosticServer(
353+
port: port,
354+
);
355+
}
356+
357+
final int port;
358+
359+
@override
360+
Map<String, Object?> toJson() {
361+
var result = <String, Object?>{};
362+
result['port'] = port;
363+
return result;
364+
}
365+
366+
static bool canParse(Object? obj, LspJsonReporter reporter) {
367+
if (obj is Map<String, Object?>) {
368+
return _canParseInt(obj, reporter, 'port',
369+
allowsUndefined: false, allowsNull: false);
370+
} else {
371+
reporter.reportError('must be of type DartDiagnosticServer');
372+
return false;
373+
}
374+
}
375+
376+
@override
377+
bool operator ==(Object other) {
378+
return other is DartDiagnosticServer &&
379+
other.runtimeType == DartDiagnosticServer &&
380+
port == other.port;
381+
}
382+
383+
@override
384+
int get hashCode => port.hashCode;
385+
386+
@override
387+
String toString() => jsonEncoder.convert(toJson());
388+
}
389+
390390
class Element implements ToJsonable {
391391
static const jsonHandler = LspJsonHandler(
392392
Element.canParse,
@@ -2396,6 +2396,32 @@ bool _canParseListOutline(
23962396
return true;
23972397
}
23982398

2399+
bool _canParseListString(
2400+
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
2401+
{required bool allowsUndefined, required bool allowsNull}) {
2402+
reporter.push(fieldName);
2403+
try {
2404+
if (!allowsUndefined && !map.containsKey(fieldName)) {
2405+
reporter.reportError('must not be undefined');
2406+
return false;
2407+
}
2408+
final value = map[fieldName];
2409+
final nullCheck = allowsNull || allowsUndefined;
2410+
if (!nullCheck && value == null) {
2411+
reporter.reportError('must not be null');
2412+
return false;
2413+
}
2414+
if ((!nullCheck || value != null) &&
2415+
(value is! List<Object?> || value.any((item) => item is! String))) {
2416+
reporter.reportError('must be of type List<String>');
2417+
return false;
2418+
}
2419+
} finally {
2420+
reporter.pop();
2421+
}
2422+
return true;
2423+
}
2424+
23992425
bool _canParseLiteral(
24002426
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
24012427
{required bool allowsUndefined,

pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -451,23 +451,21 @@ class CompletionHandler extends MessageHandler<CompletionParams, CompletionList>
451451
final insertionRange =
452452
toRange(unit.lineInfo, itemReplacementOffset, itemInsertLength);
453453

454-
// For not-imported items, we need to include the file+uri to be able
455-
// to compute the import-inserting edits in the `completionItem/resolve`
456-
// call later.
454+
// For items that need imports, we'll round-trip some additional info
455+
// to allow their additional edits (and documentation) to be handled
456+
// lazily to reduce the payload.
457457
CompletionItemResolutionInfo? resolutionInfo;
458-
final libraryUri = item.libraryUri;
459-
460-
if (useNotImportedCompletions &&
461-
libraryUri != null &&
462-
(item.isNotImported ?? false)) {
463-
final dartElement =
464-
item is DartCompletionSuggestion ? item.dartElement : null;
465-
466-
resolutionInfo = DartNotImportedCompletionResolutionInfo(
467-
file: unit.path,
468-
libraryUri: libraryUri,
469-
ref: dartElement?.location?.encoding,
470-
);
458+
if (item is DartCompletionSuggestion) {
459+
final dartElement = item.dartElement;
460+
final importUris = item.requiredImports;
461+
462+
if (importUris.isNotEmpty) {
463+
resolutionInfo = DartCompletionResolutionInfo(
464+
file: unit.path,
465+
importUris: importUris.map((uri) => uri.toString()).toList(),
466+
ref: dartElement?.location?.encoding,
467+
);
468+
}
471469
}
472470

473471
return toCompletionItem(

pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,18 @@ class CompletionResolveHandler
3838
) async {
3939
final resolutionInfo = params.data;
4040

41-
if (resolutionInfo is DartNotImportedCompletionResolutionInfo) {
42-
return resolveDartNotImportedCompletion(params, resolutionInfo, token);
41+
if (resolutionInfo is DartCompletionResolutionInfo) {
42+
return resolveDartCompletion(params, resolutionInfo, token);
4343
} else if (resolutionInfo is PubPackageCompletionItemResolutionInfo) {
4444
return resolvePubPackageCompletion(params, resolutionInfo, token);
4545
} else {
4646
return success(params);
4747
}
4848
}
4949

50-
Future<ErrorOr<CompletionItem>> resolveDartNotImportedCompletion(
50+
Future<ErrorOr<CompletionItem>> resolveDartCompletion(
5151
CompletionItem item,
52-
DartNotImportedCompletionResolutionInfo data,
52+
DartCompletionResolutionInfo data,
5353
CancellationToken token,
5454
) async {
5555
final clientCapabilities = server.clientCapabilities;
@@ -60,7 +60,7 @@ class CompletionResolveHandler
6060
}
6161

6262
final file = data.file;
63-
final libraryUri = Uri.parse(data.libraryUri);
63+
final importUris = data.importUris.map(Uri.parse).toList();
6464
final elementLocationReference = data.ref;
6565
final elementLocation = elementLocationReference != null
6666
? ElementLocationImpl.con2(elementLocationReference)
@@ -91,7 +91,9 @@ class CompletionResolveHandler
9191

9292
final builder = ChangeBuilder(session: session);
9393
await builder.addDartFileEdit(file, (builder) {
94-
builder.importLibraryElement(libraryUri);
94+
for (final uri in importUris) {
95+
builder.importLibraryElement(uri);
96+
}
9597
});
9698

9799
if (token.isCancellationRequested) {
@@ -136,26 +138,35 @@ class CompletionResolveHandler
136138
: null;
137139
}
138140

139-
// If the only URI we have is a file:// URI, display it as relative to
140-
// the file we're importing into, rather than the full URI.
141-
final pathContext = server.resourceProvider.pathContext;
142-
final autoImportDisplayUri = libraryUri.isScheme('file')
143-
// Compute the relative path and then put into a URI so the display
144-
// always uses forward slashes (as a URI) regardless of platform.
145-
? Uri.file(pathContext.relative(
146-
libraryUri.toFilePath(),
147-
from: pathContext.dirname(file),
148-
))
149-
: libraryUri;
141+
String? detail = item.detail;
142+
if (changes.edits.isNotEmpty && importUris.isNotEmpty) {
143+
if (importUris.length == 1) {
144+
// If the only URI we have is a file:// URI, display it as relative to
145+
// the file we're importing into, rather than the full URI.
146+
final pathContext = server.resourceProvider.pathContext;
147+
final libraryUri = importUris.first;
148+
final autoImportDisplayUri = libraryUri.isScheme('file')
149+
// Compute the relative path and then put into a URI so the display
150+
// always uses forward slashes (as a URI) regardless of platform.
151+
? Uri.file(pathContext.relative(
152+
libraryUri.toFilePath(),
153+
from: pathContext.dirname(file),
154+
))
155+
: libraryUri;
156+
157+
detail =
158+
"Auto import from '$autoImportDisplayUri'\n\n${item.detail ?? ''}"
159+
.trim();
160+
} else {
161+
detail = "Auto import required URIs\n\n${item.detail ?? ''}".trim();
162+
}
163+
}
150164

151165
return success(CompletionItem(
152166
label: item.label,
153167
kind: item.kind,
154168
tags: item.tags,
155-
detail: changes.edits.isNotEmpty
156-
? "Auto import from '$autoImportDisplayUri'\n\n${item.detail ?? ''}"
157-
.trim()
158-
: item.detail,
169+
detail: detail,
159170
documentation: documentation,
160171
deprecated: item.deprecated,
161172
preselect: item.preselect,

0 commit comments

Comments
 (0)