Skip to content

Commit 49e7d4a

Browse files
DanTupCommit Queue
authored andcommitted
[analysis_server] Support "Fix" CodeActions via commands if client doesn't support CodeActionLiterals
This updates fixes to return either CodeActionLiterals or Commands based on the capabilities of the client (similar to was done previously for assists). Change-Id: I688cb80aef415d329d65092edba247e5ec7a5a13 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428783 Reviewed-by: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 5f75bb8 commit 49e7d4a

File tree

9 files changed

+160
-116
lines changed

9 files changed

+160
-116
lines changed

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

Lines changed: 15 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -116,28 +116,25 @@ abstract class AbstractCodeActionsProducer
116116
);
117117
}
118118

119-
/// Creates a CodeAction to apply this assist.
119+
/// Creates a CodeAction to apply this change.
120120
///
121121
/// This code will fetch the version of each document being modified so it's
122122
/// important to call this immediately after computing edits to ensure the
123123
/// document is not modified before the version number is read.
124124
@protected
125-
CodeActionLiteral createAssistCodeActionLiteral(
125+
CodeActionLiteral createCodeActionLiteral(
126126
protocol.SourceChange change,
127127
CodeActionKind kind,
128-
String? loggedAssistId,
128+
String? loggedId,
129129
String path,
130-
LineInfo lineInfo,
131-
) {
132-
assert(
133-
kind == CodeActionKind.Refactor ||
134-
'$kind'.startsWith('${CodeActionKind.Refactor}.'),
135-
);
130+
LineInfo lineInfo, {
131+
Diagnostic? diagnostic,
132+
}) {
136133
return CodeActionLiteral(
137134
title: change.message,
138135
kind: kind,
139-
diagnostics: const [],
140-
command: createLogActionCommand(loggedAssistId),
136+
diagnostics: diagnostic != null ? [diagnostic] : const [],
137+
command: createLogActionCommand(loggedId),
141138
edit: createWorkspaceEdit(
142139
server,
143140
editorCapabilities,
@@ -161,24 +158,26 @@ abstract class AbstractCodeActionsProducer
161158
LineInfo lineInfo,
162159
SourceChange change,
163160
CodeActionKind kind,
164-
String? loggedAssistId,
165-
) {
161+
String? loggedId, {
162+
Diagnostic? diagnostic,
163+
}) {
166164
if (allowCodeActionLiterals) {
167165
return CodeAction.t1(
168-
createAssistCodeActionLiteral(
166+
createCodeActionLiteral(
169167
change,
170168
kind,
171-
loggedAssistId,
169+
loggedId,
172170
path,
173171
lineInfo,
172+
diagnostic: diagnostic,
174173
),
175174
);
176175
} else if (allowCommands) {
177176
return CodeAction.t2(
178177
createApplyCodeActionCommand(
179178
change.message,
180179
kind,
181-
loggedAssistId,
180+
loggedId,
182181
textDocument,
183182
range,
184183
),
@@ -204,40 +203,6 @@ abstract class AbstractCodeActionsProducer
204203
);
205204
}
206205

207-
/// Creates a CodeAction to apply this fix.
208-
///
209-
/// This code will fetch the version of each document being modified so it's
210-
/// important to call this immediately after computing edits to ensure the
211-
/// document is not modified before the version number is read.
212-
@protected
213-
CodeActionLiteral createFixCodeActionLiteral(
214-
protocol.SourceChange change,
215-
CodeActionKind kind,
216-
String? loggedFixId,
217-
Diagnostic diagnostic,
218-
String path,
219-
LineInfo lineInfo,
220-
) {
221-
assert(
222-
kind == CodeActionKind.QuickFix ||
223-
'$kind'.startsWith('${CodeActionKind.QuickFix}.'),
224-
);
225-
return CodeActionLiteral(
226-
title: change.message,
227-
kind: kind,
228-
diagnostics: [diagnostic],
229-
command: createLogActionCommand(loggedFixId),
230-
edit: createWorkspaceEdit(
231-
server,
232-
editorCapabilities,
233-
change,
234-
allowSnippets: true,
235-
filePath: path,
236-
lineInfo: lineInfo,
237-
),
238-
);
239-
}
240-
241206
/// Creates a command to log that a CodeAction was selected.
242207
///
243208
/// Code Actions that provide their edits inline (and not via a command) do

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,13 @@ class AnalysisOptionsCodeActionsProducer extends AbstractCodeActionsProducer {
112112
return null;
113113
}
114114
var action = CodeAction.t1(
115-
createFixCodeActionLiteral(
115+
createCodeActionLiteral(
116116
fix.change,
117117
kind,
118118
fix.change.id,
119-
diagnostic,
120119
path,
121120
lineInfo,
121+
diagnostic: diagnostic,
122122
),
123123
);
124124
return (action: action, priority: fix.kind.priority);

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

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,6 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
197197
Future<List<CodeActionWithPriority>> getFixActions(
198198
OperationPerformance? performance,
199199
) async {
200-
// These fixes are only provided as literal CodeActions.
201-
if (!allowCodeActionLiterals) {
202-
// TODO(dantup): Support this (via createCodeActionLiteralOrApplyCommand)
203-
return [];
204-
}
205-
206200
var lineInfo = unitResult.lineInfo;
207201
var codeActions = <CodeActionWithPriority>[];
208202

@@ -264,25 +258,26 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
264258
);
265259
codeActions.addAll(
266260
fixes.map((fix) {
267-
var kind = toCodeActionKind(
268-
fix.change.id,
269-
CodeActionKind.QuickFix,
270-
);
261+
var change = fix.change;
262+
var kind = toCodeActionKind(change.id, CodeActionKind.QuickFix);
271263
// TODO(dantup): Find a way to filter these earlier, so we don't
272264
// compute fixes we will filter out.
273265
if (!shouldIncludeKind(kind)) {
274266
return null;
275267
}
276-
var action = CodeAction.t1(
277-
createFixCodeActionLiteral(
278-
fix.change,
279-
kind,
280-
fix.change.id,
281-
diagnostic,
282-
path,
283-
lineInfo,
284-
),
268+
var action = createCodeActionLiteralOrApplyCommand(
269+
unitResult.path,
270+
docIdentifier,
271+
range,
272+
lineInfo,
273+
change,
274+
kind,
275+
change.id,
276+
diagnostic: diagnostic,
285277
);
278+
if (action == null) {
279+
return null;
280+
}
286281
return (action: action, priority: fix.kind.priority);
287282
}).nonNulls,
288283
);

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class PluginCodeActionsProducer extends AbstractCodeActionsProducer {
100100

101101
return (
102102
action: CodeAction.t1(
103-
createAssistCodeActionLiteral(
103+
createCodeActionLiteral(
104104
assist.change,
105105
kind,
106106
'assist from plugin',
@@ -131,13 +131,13 @@ class PluginCodeActionsProducer extends AbstractCodeActionsProducer {
131131
}
132132
return (
133133
action: CodeAction.t1(
134-
createFixCodeActionLiteral(
134+
createCodeActionLiteral(
135135
fix.change,
136136
kind,
137137
'fix from plugin',
138-
diagnostic,
139138
path,
140139
lineInfo,
140+
diagnostic: diagnostic,
141141
),
142142
),
143143
priority: fix.priority,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,13 @@ class PubspecCodeActionsProducer extends AbstractCodeActionsProducer {
9898
return null;
9999
}
100100
var action = CodeAction.t1(
101-
createFixCodeActionLiteral(
101+
createCodeActionLiteral(
102102
fix.change,
103103
kind,
104104
fix.change.id,
105-
diagnostic,
106105
path,
107106
lineInfo,
107+
diagnostic: diagnostic,
108108
),
109109
);
110110
return (action: action, priority: fix.kind.priority);

pkg/analysis_server/test/lsp/code_actions_mixin.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,32 @@ mixin CodeActionsTestMixin
9090
return action.asCodeActionLiteral;
9191
}
9292

93+
/// Initializes the server with some basic configuration and expects to find
94+
/// a [Command] code action (not a literal, even with a command) with
95+
/// [command]/[title].
96+
Future<Command> expectCommandCodeAction(
97+
String content, {
98+
CodeActionKind? kind,
99+
String? command,
100+
List<Object>? commandArgs,
101+
String? title,
102+
CodeActionTriggerKind? triggerKind,
103+
String? filePath,
104+
bool openTargetFile = false,
105+
}) async {
106+
var action = await expectCodeAction(
107+
TestCode.parse(content),
108+
kind: kind,
109+
command: command,
110+
commandArgs: commandArgs,
111+
title: title,
112+
triggerKind: triggerKind,
113+
filePath: filePath,
114+
openTargetFile: openTargetFile,
115+
);
116+
return action.asCommand;
117+
}
118+
93119
/// Verifies a command execution was logged to analytics.
94120
///
95121
/// Implementations are provided by the in-process test base classes. This
@@ -322,4 +348,43 @@ $expected''';
322348
workDoneToken: commandWorkDoneToken,
323349
);
324350
}
351+
352+
/// Initializes the server with some basic configuration and expects to find
353+
/// a [Command] code action (and not a literal, even with a command) with
354+
/// [title] that applies edits resulting in [expected].
355+
Future<LspChangeVerifier> verifyCommandCodeActionEdits(
356+
String content,
357+
String expected, {
358+
String? filePath,
359+
String? command,
360+
List<Object>? commandArgs,
361+
String? title,
362+
ProgressToken? commandWorkDoneToken,
363+
bool openTargetFile = false,
364+
}) async {
365+
filePath ??= testFilePath;
366+
367+
// For convenience, if a test doesn't provide an full set of edits
368+
// we assume only a single edit of the file that was being modified.
369+
if (!expected.startsWith(LspChangeVerifier.editMarkerStart)) {
370+
expected = '''
371+
${LspChangeVerifier.editMarkerStart} ${relativePath(filePath)}
372+
$expected''';
373+
}
374+
375+
var commandAction = await expectCommandCodeAction(
376+
filePath: filePath,
377+
content,
378+
command: command,
379+
commandArgs: commandArgs,
380+
title: title,
381+
openTargetFile: openTargetFile,
382+
);
383+
384+
return await verifyCodeActionEdits(
385+
CodeAction.t2(commandAction),
386+
expected,
387+
workDoneToken: commandWorkDoneToken,
388+
);
389+
}
325390
}

pkg/analysis_server/test/shared/shared_code_actions_assists_tests.dart

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import 'package:test/test.dart';
1515
import '../lsp/code_actions_mixin.dart';
1616
import '../lsp/request_helpers_mixin.dart';
1717
import '../lsp/server_abstract.dart';
18-
import '../utils/lsp_protocol_extensions.dart';
1918
import '../utils/test_code_extensions.dart';
2019
import 'shared_test_interface.dart';
2120

@@ -90,70 +89,53 @@ Future? f;
9089
setSnippetTextEditSupport();
9190
setSupportedCodeActionKinds([CodeActionKind.Refactor]);
9291

93-
var code = TestCode.parse('''
92+
const content = '''
9493
import 'package:flutter/widgets.dart';
9594
Widget build() {
9695
return Te^xt('');
9796
}
98-
''');
99-
100-
var action = await expectCodeAction(
101-
code,
102-
kind: CodeActionKind('refactor.flutter.wrap.center'),
103-
title: 'Wrap with Center',
104-
);
105-
106-
// Ensure we are a CodeAction literal.
107-
expect(action.isCodeActionLiteral, true);
97+
''';
10898

109-
await verifyCodeActionEdits(action, r'''
99+
const expectedContent = r'''
110100
>>>>>>>>>> lib/test.dart
111101
import 'package:flutter/widgets.dart';
112102
Widget build() {
113103
return Center($0child: Text(''));
114104
}
115-
''');
105+
''';
106+
107+
await verifyCodeActionLiteralEdits(
108+
content,
109+
expectedContent,
110+
title: 'Wrap with Center',
111+
);
116112
}
117113

118114
Future<void> test_codeActionLiterals_unsupported() async {
119115
setSnippetTextEditSupport();
120116
setSupportedCodeActionKinds(null); // no codeActionLiteralSupport
121117

122-
var code = TestCode.parse('''
118+
const content = '''
123119
import 'package:flutter/widgets.dart';
124120
Widget build() {
125121
return Te[!!]xt('');
126122
}
127-
''');
128-
129-
var action = await expectCodeAction(
130-
openTargetFile: true, // Open document to verify we get a version back.
131-
code,
132-
title: 'Wrap with Center',
133-
command: Commands.applyCodeAction,
134-
commandArgs: [
135-
{
136-
'textDocument': {'uri': testFileUri.toString(), 'version': 1},
137-
'range': code.range.range.toJson(),
138-
'kind': 'refactor.flutter.wrap.center',
139-
'loggedAction': 'dart.assist.flutter.wrap.center',
140-
},
141-
],
142-
);
143-
144-
// We don't support literals, so we expect the raw command instead.
145-
expect(action.isCommand, true);
146-
var command = action.asCommand;
123+
''';
147124

148-
// Verify that executing the command produces the correct edits (which will
149-
// come back via `workspace/applyEdit`).
150-
await verifyCommandEdits(command, r'''
125+
const expectedContent = r'''
151126
>>>>>>>>>> lib/test.dart
152127
import 'package:flutter/widgets.dart';
153128
Widget build() {
154129
return Center($0child: Text(''));
155130
}
156-
''');
131+
''';
132+
133+
await verifyCommandCodeActionEdits(
134+
content,
135+
expectedContent,
136+
command: Commands.applyCodeAction,
137+
title: 'Wrap with Center',
138+
);
157139

158140
expectCommandLogged(Commands.applyCodeAction);
159141
expectCommandLogged('dart.assist.flutter.wrap.center');

0 commit comments

Comments
 (0)