Skip to content

Commit 4b4e3f4

Browse files
DanTupCommit Queue
authored andcommitted
[analysis_server] Update CodeActionsProducers to support either kind of CodeAction
No functional changes, just some additional refactors extracted from an upcoming CL to make it easier to review. This updates the `CodeActionProducer`s (and related code in `CodeActionComputer`) so their signatures use `CodeAction` instead of `CodeActionLiteral`, which means this code will be able to support `Command` code actions in future. Mostly it's trivial changes, but the code that de-dupes and merges actions needed a little more updating to handle the different kinds. Change-Id: Ic2255b2d9864b6974a542d1d3ee3b099c4d5ea32 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427222 Reviewed-by: Samuel Rawlins <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 3e65c9d commit 4b4e3f4

16 files changed

+158
-103
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/lsp_protocol/protocol.dart';
6+
7+
extension CodeActionExtensions on CodeAction {
8+
/// The [Command] for this [CodeAction], whether it's a [CodeActionLiteral]
9+
/// or a [Command].
10+
Command? get command {
11+
return map((literal) => literal.command, (command) => command);
12+
}
13+
14+
/// The title for this [CodeAction], whether it's a [CodeActionLiteral]
15+
/// or a [Command].
16+
String get title {
17+
return map((literal) => literal.title, (command) => command.title);
18+
}
19+
}

pkg/analysis_server/lib/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ import 'package:analyzer/src/dart/analysis/results.dart' as engine;
2020
import 'package:analyzer/src/util/performance/operation_performance.dart';
2121
import 'package:meta/meta.dart';
2222

23-
typedef CodeActionWithPriority = ({CodeActionLiteral action, int priority});
23+
typedef CodeActionWithPriority = ({CodeAction action, int priority});
2424

2525
typedef CodeActionWithPriorityAndIndex =
26-
({CodeActionLiteral action, int priority, int index});
26+
({CodeAction action, int priority, int index});
2727

28-
/// A base for classes that produce [CodeActionLiteral]s for the LSP handler.
28+
/// A base for classes that produce [CodeAction]s for the LSP handler.
2929
abstract class AbstractCodeActionsProducer
3030
with RequestHandlerMixin<LspAnalysisServer> {
3131
final File file;
@@ -69,7 +69,7 @@ abstract class AbstractCodeActionsProducer
6969
/// important to call this immediately after computing edits to ensure the
7070
/// document is not modified before the version number is read.
7171
@protected
72-
CodeActionLiteral createAssistAction(
72+
CodeActionLiteral createAssistCodeActionLiteral(
7373
protocol.SourceChange change,
7474
CodeActionKind kind,
7575
String? loggedAssistId,
@@ -118,7 +118,7 @@ abstract class AbstractCodeActionsProducer
118118
/// important to call this immediately after computing edits to ensure the
119119
/// document is not modified before the version number is read.
120120
@protected
121-
CodeActionLiteral createFixAction(
121+
CodeActionLiteral createFixCodeActionLiteral(
122122
protocol.SourceChange change,
123123
CodeActionKind kind,
124124
String? loggedFixId,

pkg/analysis_server/lib/src/lsp/handlers/code_actions/analysis_options.dart

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ class AnalysisOptionsCodeActionsProducer extends AbstractCodeActionsProducer {
4242
Future<List<CodeActionWithPriority>> getFixActions(
4343
OperationPerformance? performance,
4444
) async {
45+
// These fixes are only provided as literal CodeActions.
46+
if (!supportsLiterals) {
47+
return [];
48+
}
49+
4550
var session = await server.getAnalysisSession(path);
4651
if (session == null) {
4752
return [];
@@ -102,13 +107,15 @@ class AnalysisOptionsCodeActionsProducer extends AbstractCodeActionsProducer {
102107
if (!shouldIncludeKind(kind)) {
103108
return null;
104109
}
105-
var action = createFixAction(
106-
fix.change,
107-
kind,
108-
fix.change.id,
109-
diagnostic,
110-
path,
111-
lineInfo,
110+
var action = CodeAction.t1(
111+
createFixCodeActionLiteral(
112+
fix.change,
113+
kind,
114+
fix.change.id,
115+
diagnostic,
116+
path,
117+
lineInfo,
118+
),
112119
);
113120
return (action: action, priority: fix.kind.priority);
114121
}).nonNulls,

pkg/analysis_server/lib/src/lsp/handlers/code_actions/code_action_computer.dart

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ library;
99
import 'package:analysis_server/lsp_protocol/protocol.dart';
1010
import 'package:analysis_server/src/lsp/client_capabilities.dart';
1111
import 'package:analysis_server/src/lsp/error_or.dart';
12+
import 'package:analysis_server/src/lsp/extensions/code_action.dart';
1213
import 'package:analysis_server/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart';
1314
import 'package:analysis_server/src/lsp/handlers/code_actions/analysis_options.dart';
1415
import 'package:analysis_server/src/lsp/handlers/code_actions/dart.dart';
@@ -344,17 +345,21 @@ class _CodeActionSorter {
344345
}).toList();
345346
dedupedActionsWithIndex.sort(_compareCodeActions);
346347

347-
return dedupedActionsWithIndex
348-
.map((action) => CodeAction.t1(action.action))
349-
.toList();
348+
return dedupedActionsWithIndex.map((action) => action.action).toList();
350349
}
351350

352-
/// Creates a comparer for [CodeActionLiteral]s that compares the column distance from
353-
/// [pos].
354-
int Function(CodeActionLiteral a, CodeActionLiteral b)
355-
_codeActionColumnDistanceComparer(Position pos) {
356-
Position posOf(CodeActionLiteral action) {
357-
var diagnostics = action.diagnostics;
351+
/// Creates a comparer for [CodeAction]s that compares the column
352+
/// distance from [pos].
353+
///
354+
/// If a [CodeAction] has no diagnostics, considers the action at [pos].
355+
int Function(CodeAction a, CodeAction b) _codeActionColumnDistanceComparer(
356+
Position pos,
357+
) {
358+
Position posOf(CodeAction action) {
359+
var diagnostics = action.map(
360+
(literal) => literal.diagnostics,
361+
(command) => null,
362+
);
358363
return diagnostics != null && diagnostics.isNotEmpty
359364
? diagnostics.first.range.start
360365
: pos;
@@ -423,35 +428,53 @@ class _CodeActionSorter {
423428
// Otherwise, find the action nearest to the caret.
424429
var comparer = _codeActionColumnDistanceComparer(position);
425430
actions.sort((a, b) => comparer(a.action, b.action));
426-
var first = actions.first.action;
431+
var first = actions.first;
432+
var firstAction = first.action;
427433
var priority = actions.first.priority;
428434

429-
// Get any actions with the same fix (edit/command) for merging diagnostics.
435+
// If this action is not a literal (it is a command), just return as-is
436+
// because we can't merge any diagnostics into it.
437+
var firstLiteral = firstAction.map(
438+
(literal) => literal,
439+
(command) => null,
440+
);
441+
if (firstLiteral == null) {
442+
return first;
443+
}
444+
445+
// Get any literal actions with the same fix (edit/command) for merging
446+
// diagnostics.
430447
var others = actions
431448
.skip(1)
432-
.where(
433-
(other) =>
434-
// Compare either edits or commands based on which the selected action has.
435-
first.edit != null
436-
? first.edit == other.action.edit
437-
: first.command != null
438-
? first.command == other.action.command
439-
: false,
440-
);
449+
.map(
450+
(action) =>
451+
action.action.map((literal) => literal, (command) => null),
452+
)
453+
.nonNulls
454+
.where((other) {
455+
// Compare either edits or commands based on which the selected action has.
456+
return firstLiteral.edit != null
457+
? firstLiteral.edit == other.edit
458+
: firstLiteral.command != null
459+
? firstLiteral.command == other.command
460+
: false;
461+
});
441462

442463
// Build a new CodeAction that merges the diagnostics from each same
443464
// code action onto a single one.
444465
return (
445-
action: CodeActionLiteral(
446-
title: first.title,
447-
kind: first.kind,
448-
// Merge diagnostics from all of the matching CodeActions.
449-
diagnostics: [
450-
...?first.diagnostics,
451-
for (var other in others) ...?other.action.diagnostics,
452-
],
453-
edit: first.edit,
454-
command: first.command,
466+
action: CodeAction.t1(
467+
CodeActionLiteral(
468+
title: firstAction.title,
469+
kind: firstLiteral.kind,
470+
// Merge diagnostics from all of the matching CodeActions.
471+
diagnostics: [
472+
...?firstLiteral.diagnostics,
473+
for (var other in others) ...?other.diagnostics,
474+
],
475+
edit: firstLiteral.edit,
476+
command: firstAction.command,
477+
),
455478
),
456479
priority: priority,
457480
);

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

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
7272
(() => Commands.serverSupportedCommands.contains(command))(),
7373
'serverSupportedCommands did not contain $command',
7474
);
75-
return _commandOrCodeActionLiteral(
75+
return _maybeWrapCommandInCodeActionLiteral(
7676
actionKind,
7777
Command(
7878
title: title,
@@ -102,7 +102,7 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
102102
'serverSupportedCommands did not contain $command',
103103
);
104104

105-
return _commandOrCodeActionLiteral(
105+
return _maybeWrapCommandInCodeActionLiteral(
106106
actionKind,
107107
Command(
108108
title: name,
@@ -165,21 +165,21 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
165165

166166
return assists
167167
.map((assist) {
168-
var kind = toCodeActionKind(
169-
assist.change.id,
170-
CodeActionKind.Refactor,
171-
);
168+
var change = assist.change;
169+
var kind = toCodeActionKind(change.id, CodeActionKind.Refactor);
172170
// TODO(dantup): Find a way to filter these earlier, so we don't
173171
// compute fixes we will filter out.
174172
if (!shouldIncludeKind(kind)) {
175173
return null;
176174
}
177-
var action = createAssistAction(
178-
assist.change,
179-
kind,
180-
assist.change.id,
181-
unitResult.path,
182-
unitResult.lineInfo,
175+
var action = CodeAction.t1(
176+
createAssistCodeActionLiteral(
177+
assist.change,
178+
kind,
179+
assist.change.id,
180+
unitResult.path,
181+
unitResult.lineInfo,
182+
),
183183
);
184184
return (action: action, priority: assist.kind.priority);
185185
})
@@ -272,13 +272,15 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
272272
if (!shouldIncludeKind(kind)) {
273273
return null;
274274
}
275-
var action = createFixAction(
276-
fix.change,
277-
kind,
278-
fix.change.id,
279-
diagnostic,
280-
path,
281-
lineInfo,
275+
var action = CodeAction.t1(
276+
createFixCodeActionLiteral(
277+
fix.change,
278+
kind,
279+
fix.change.id,
280+
diagnostic,
281+
path,
282+
lineInfo,
283+
),
282284
);
283285
return (action: action, priority: fix.kind.priority);
284286
}).nonNulls,
@@ -531,9 +533,13 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
531533
];
532534
}
533535

534-
/// Wraps a command in a CodeAction if the client supports it so that a
535-
/// CodeActionKind can be supplied.
536-
CodeAction _commandOrCodeActionLiteral(CodeActionKind kind, Command command) {
536+
/// Returns a [CodeAction] that is [command] wrapped in a [CodeActionLiteral]
537+
/// (to allow a [CodeActionKind] to be supplied) if the client supports it,
538+
/// otherwise the bare command.
539+
CodeAction _maybeWrapCommandInCodeActionLiteral(
540+
CodeActionKind kind,
541+
Command command,
542+
) {
537543
return supportsLiterals
538544
? CodeAction.t1(
539545
CodeActionLiteral(title: command.title, kind: kind, command: command),

pkg/analysis_server/lib/src/lsp/handlers/code_actions/plugins.dart

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,14 @@ class PluginCodeActionsProducer extends AbstractCodeActionsProducer {
9090
}
9191

9292
return (
93-
action: createAssistAction(
94-
assist.change,
95-
kind,
96-
'assist from plugin',
97-
path,
98-
lineInfo,
93+
action: CodeAction.t1(
94+
createAssistCodeActionLiteral(
95+
assist.change,
96+
kind,
97+
'assist from plugin',
98+
path,
99+
lineInfo,
100+
),
99101
),
100102
priority: assist.priority,
101103
);
@@ -119,13 +121,15 @@ class PluginCodeActionsProducer extends AbstractCodeActionsProducer {
119121
return null;
120122
}
121123
return (
122-
action: createFixAction(
123-
fix.change,
124-
kind,
125-
'fix from plugin',
126-
diagnostic,
127-
path,
128-
lineInfo,
124+
action: CodeAction.t1(
125+
createFixCodeActionLiteral(
126+
fix.change,
127+
kind,
128+
'fix from plugin',
129+
diagnostic,
130+
path,
131+
lineInfo,
132+
),
129133
),
130134
priority: fix.priority,
131135
);

pkg/analysis_server/lib/src/lsp/handlers/code_actions/pubspec.dart

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class PubspecCodeActionsProducer extends AbstractCodeActionsProducer {
3939
Future<List<CodeActionWithPriority>> getFixActions(
4040
OperationPerformance? performance,
4141
) async {
42+
// These fixes are only provided as literal CodeActions.
43+
if (!supportsLiterals) {
44+
return [];
45+
}
46+
4247
var session = await server.getAnalysisSession(path);
4348
if (session == null) {
4449
return [];
@@ -88,13 +93,15 @@ class PubspecCodeActionsProducer extends AbstractCodeActionsProducer {
8893
if (!shouldIncludeKind(kind)) {
8994
return null;
9095
}
91-
var action = createFixAction(
92-
fix.change,
93-
kind,
94-
fix.change.id,
95-
diagnostic,
96-
path,
97-
lineInfo,
96+
var action = CodeAction.t1(
97+
createFixCodeActionLiteral(
98+
fix.change,
99+
kind,
100+
fix.change.id,
101+
diagnostic,
102+
path,
103+
lineInfo,
104+
),
98105
);
99106
return (action: action, priority: fix.kind.priority);
100107
}).nonNulls,

pkg/analysis_server/test/lsp/code_actions_abstract.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:analysis_server/lsp_protocol/protocol.dart';
66
import 'package:analysis_server/src/lsp/constants.dart';
7+
import 'package:analysis_server/src/lsp/extensions/code_action.dart';
78
import 'package:analysis_server/src/services/correction/assist_internal.dart';
89
import 'package:analysis_server/src/services/correction/fix_internal.dart';
910
import 'package:analyzer/src/test_utilities/test_code_format.dart';

pkg/analysis_server/test/lsp/code_actions_assists_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import 'dart:convert';
66

77
import 'package:analysis_server/lsp_protocol/protocol.dart';
88
import 'package:analysis_server/src/analysis_server.dart';
9+
import 'package:analysis_server/src/lsp/extensions/code_action.dart';
910
import 'package:analyzer/src/test_utilities/test_code_format.dart';
1011
import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
1112
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
1213
import 'package:test/test.dart';
1314
import 'package:test_reflective_loader/test_reflective_loader.dart';
1415

15-
import '../utils/lsp_protocol_extensions.dart';
1616
import '../utils/test_code_extensions.dart';
1717
import 'code_actions_abstract.dart';
1818

0 commit comments

Comments
 (0)