Skip to content

Commit 3c161d3

Browse files
DanTupCommit Queue
authored andcommitted
[analysis_server] Move CodeActionKind filter earlier in production of code actions
This removes a filter of CodeActionKinds from the final step (and a class named `_CodeActionSorter`!) and instead applies it earlier during building of the actions. This will help apply the filter in the case where we return bare Commands (which don't have `kind`s) instead of `CodeActionLiteral`s. I added some TODOs because I still don't think this is filtering early enough (because in the case of invoking an action via the command, we need to not have to produce _all_ code actions just to locate the _one_ we want to execute), but it's a step closer (and easier to review this without it being lumped in with the CL that supports returning Commands). Change-Id: Ibc73c4900d939ac833f6345e9e8f1fdf3f5d6823 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427000 Reviewed-by: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent b32e5e5 commit 3c161d3

File tree

10 files changed

+151
-52
lines changed

10 files changed

+151
-52
lines changed

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

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,26 @@ abstract class AbstractCodeActionsProducer
6363

6464
bool get supportsLiterals => capabilities.literalCodeActions;
6565

66-
/// Creates a CodeAction to apply this assist. Note: This code will fetch the
67-
/// version of each document being modified so it's important to call this
68-
/// immediately after computing edits to ensure the document is not modified
69-
/// before the version number is read.
66+
/// Creates a CodeAction to apply this assist.
67+
///
68+
/// This code will fetch the version of each document being modified so it's
69+
/// important to call this immediately after computing edits to ensure the
70+
/// document is not modified before the version number is read.
7071
@protected
7172
CodeActionLiteral createAssistAction(
7273
protocol.SourceChange change,
74+
CodeActionKind kind,
7375
String? loggedAssistId,
7476
String path,
7577
LineInfo lineInfo,
7678
) {
79+
assert(
80+
kind == CodeActionKind.Refactor ||
81+
'$kind'.startsWith('${CodeActionKind.Refactor}.'),
82+
);
7783
return CodeActionLiteral(
7884
title: change.message,
79-
kind: toCodeActionKind(change.id, CodeActionKind.Refactor),
85+
kind: kind,
8086
diagnostics: const [],
8187
command: createLogActionCommand(loggedAssistId),
8288
edit: createWorkspaceEdit(
@@ -106,21 +112,27 @@ abstract class AbstractCodeActionsProducer
106112
);
107113
}
108114

109-
/// Creates a CodeAction to apply this fix. Note: This code will fetch the
110-
/// version of each document being modified so it's important to call this
111-
/// immediately after computing edits to ensure the document is not modified
112-
/// before the version number is read.
115+
/// Creates a CodeAction to apply this fix.
116+
///
117+
/// This code will fetch the version of each document being modified so it's
118+
/// important to call this immediately after computing edits to ensure the
119+
/// document is not modified before the version number is read.
113120
@protected
114121
CodeActionLiteral createFixAction(
115122
protocol.SourceChange change,
123+
CodeActionKind kind,
116124
String? loggedFixId,
117125
Diagnostic diagnostic,
118126
String path,
119127
LineInfo lineInfo,
120128
) {
129+
assert(
130+
kind == CodeActionKind.QuickFix ||
131+
'$kind'.startsWith('${CodeActionKind.QuickFix}.'),
132+
);
121133
return CodeActionLiteral(
122134
title: change.message,
123-
kind: toCodeActionKind(change.id, CodeActionKind.QuickFix),
135+
kind: kind,
124136
diagnostics: [diagnostic],
125137
command: createLogActionCommand(loggedFixId),
126138
edit: createWorkspaceEdit(

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66

77
import 'package:analysis_server/lsp_protocol/protocol.dart';
88
import 'package:analysis_server/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart';
9+
import 'package:analysis_server/src/lsp/mapping.dart';
910
import 'package:analysis_server/src/services/correction/fix/analysis_options/fix_generator.dart';
1011
import 'package:analyzer/source/file_source.dart';
1112
import 'package:analyzer/source/line_info.dart';
@@ -95,15 +96,22 @@ class AnalysisOptionsCodeActionsProducer extends AbstractCodeActionsProducer {
9596
var diagnostic = createDiagnostic(lineInfo, result, error);
9697
codeActions.addAll(
9798
fixes.map((fix) {
99+
var kind = toCodeActionKind(fix.change.id, CodeActionKind.QuickFix);
100+
// TODO(dantup): Find a way to filter these earlier, so we don't
101+
// compute fixes we will filter out.
102+
if (!shouldIncludeKind(kind)) {
103+
return null;
104+
}
98105
var action = createFixAction(
99106
fix.change,
107+
kind,
100108
fix.change.id,
101109
diagnostic,
102110
path,
103111
lineInfo,
104112
);
105113
return (action: action, priority: fix.kind.priority);
106-
}),
114+
}).nonNulls,
107115
);
108116
}
109117

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

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,28 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
162162
assists = await computeAssists(context);
163163
}
164164

165-
return assists.map((assist) {
166-
var action = createAssistAction(
167-
assist.change,
168-
assist.change.id,
169-
unitResult.path,
170-
unitResult.lineInfo,
171-
);
172-
return (action: action, priority: assist.kind.priority);
173-
}).toList();
165+
return assists
166+
.map((assist) {
167+
var kind = toCodeActionKind(
168+
assist.change.id,
169+
CodeActionKind.Refactor,
170+
);
171+
// TODO(dantup): Find a way to filter these earlier, so we don't
172+
// compute fixes we will filter out.
173+
if (!shouldIncludeKind(kind)) {
174+
return null;
175+
}
176+
var action = createAssistAction(
177+
assist.change,
178+
kind,
179+
assist.change.id,
180+
unitResult.path,
181+
unitResult.lineInfo,
182+
);
183+
return (action: action, priority: assist.kind.priority);
184+
})
185+
.nonNulls
186+
.toList();
174187
} on InconsistentAnalysisException {
175188
// If an InconsistentAnalysisException occurs, it's likely the user modified
176189
// the source and therefore is no longer interested in the results, so
@@ -247,15 +260,25 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
247260
);
248261
codeActions.addAll(
249262
fixes.map((fix) {
263+
var kind = toCodeActionKind(
264+
fix.change.id,
265+
CodeActionKind.QuickFix,
266+
);
267+
// TODO(dantup): Find a way to filter these earlier, so we don't
268+
// compute fixes we will filter out.
269+
if (!shouldIncludeKind(kind)) {
270+
return null;
271+
}
250272
var action = createFixAction(
251273
fix.change,
274+
kind,
252275
fix.change.id,
253276
diagnostic,
254277
path,
255278
lineInfo,
256279
);
257280
return (action: action, priority: fix.kind.priority);
258-
}),
281+
}).nonNulls,
259282
);
260283
}
261284
}
@@ -300,7 +323,11 @@ class DartCodeActionsProducer extends AbstractCodeActionsProducer {
300323
performance: performanceTracker,
301324
);
302325
var actions = await processor.compute();
303-
refactorActions.addAll(actions.map(CodeAction.t1));
326+
refactorActions.addAll(
327+
actions
328+
.where((literal) => shouldIncludeKind(literal.kind))
329+
.map(CodeAction.t1),
330+
);
304331

305332
// Extracts
306333
if (shouldIncludeKind(CodeActionKind.RefactorExtract)) {

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class PluginCodeActionsProducer extends AbstractCodeActionsProducer {
4848
.map((response) => plugin.EditGetAssistsResult.fromResponse(response))
4949
.expand((response) => response.assists)
5050
.map(_convertAssist)
51+
.nonNulls
5152
.toList();
5253
}
5354

@@ -78,10 +79,20 @@ class PluginCodeActionsProducer extends AbstractCodeActionsProducer {
7879
@override
7980
Future<List<CodeAction>> getSourceActions() async => [];
8081

81-
CodeActionWithPriority _convertAssist(plugin.PrioritizedSourceChange assist) {
82+
CodeActionWithPriority? _convertAssist(
83+
plugin.PrioritizedSourceChange assist,
84+
) {
85+
var kind = toCodeActionKind(assist.change.id, CodeActionKind.Refactor);
86+
// TODO(dantup): Find a way to filter these earlier, so we don't
87+
// compute fixes we will filter out.
88+
if (!shouldIncludeKind(kind)) {
89+
return null;
90+
}
91+
8292
return (
8393
action: createAssistAction(
8494
assist.change,
95+
kind,
8596
'assist from plugin',
8697
path,
8798
lineInfo,
@@ -100,18 +111,25 @@ class PluginCodeActionsProducer extends AbstractCodeActionsProducer {
100111
supportedTags: supportedDiagnosticTags,
101112
clientSupportsCodeDescription: supportsCodeDescription,
102113
);
103-
return fixes.fixes.map(
104-
(fix) => (
114+
return fixes.fixes.map((fix) {
115+
var kind = toCodeActionKind(fix.change.id, CodeActionKind.QuickFix);
116+
// TODO(dantup): Find a way to filter these earlier, so we don't
117+
// compute fixes we will filter out.
118+
if (!shouldIncludeKind(kind)) {
119+
return null;
120+
}
121+
return (
105122
action: createFixAction(
106123
fix.change,
124+
kind,
107125
'fix from plugin',
108126
diagnostic,
109127
path,
110128
lineInfo,
111129
),
112130
priority: fix.priority,
113-
),
114-
);
131+
);
132+
}).nonNulls;
115133
}
116134

117135
Future<List<plugin.Response>> _sendPluginRequest(

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66

77
import 'package:analysis_server/lsp_protocol/protocol.dart';
88
import 'package:analysis_server/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart';
9+
import 'package:analysis_server/src/lsp/mapping.dart';
910
import 'package:analysis_server/src/services/correction/fix/pubspec/fix_generator.dart';
1011
import 'package:analyzer/source/file_source.dart';
1112
import 'package:analyzer/source/line_info.dart';
@@ -81,15 +82,22 @@ class PubspecCodeActionsProducer extends AbstractCodeActionsProducer {
8182
var diagnostic = createDiagnostic(lineInfo, result, error);
8283
codeActions.addAll(
8384
fixes.map((fix) {
85+
var kind = toCodeActionKind(fix.change.id, CodeActionKind.QuickFix);
86+
// TODO(dantup): Find a way to filter these earlier, so we don't
87+
// compute fixes we will filter out.
88+
if (!shouldIncludeKind(kind)) {
89+
return null;
90+
}
8491
var action = createFixAction(
8592
fix.change,
93+
kind,
8694
fix.change.id,
8795
diagnostic,
8896
path,
8997
lineInfo,
9098
);
9199
return (action: action, priority: fix.kind.priority);
92-
}),
100+
}).nonNulls,
93101
);
94102
}
95103

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ class CodeActionHandler
218218
analysisOptions: analysisOptions,
219219
),
220220
];
221-
var sorter = _CodeActionSorter(params.range, shouldIncludeKind);
221+
var sorter = _CodeActionSorter(params.range);
222222

223223
var allActions = <CodeAction>[
224224
// Like-kinded actions are grouped (and prioritized) together
@@ -303,9 +303,8 @@ class CodeActionRegistrations extends FeatureRegistration
303303
/// the one nearest [range].
304304
class _CodeActionSorter {
305305
final Range range;
306-
final bool Function(CodeActionKind?) shouldIncludeKind;
307306

308-
_CodeActionSorter(this.range, this.shouldIncludeKind);
307+
_CodeActionSorter(this.range);
309308

310309
List<CodeAction> sort(List<CodeActionWithPriority> actions) {
311310
var dedupedActions = _dedupeActions(actions, range.start);
@@ -323,7 +322,6 @@ class _CodeActionSorter {
323322
dedupedActionsWithIndex.sort(_compareCodeActions);
324323

325324
return dedupedActionsWithIndex
326-
.where((action) => shouldIncludeKind(action.action.kind))
327325
.map((action) => CodeAction.t1(action.action))
328326
.toList();
329327
}

pkg/analysis_server/test/lsp/code_actions_fixes_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,10 @@ Future foo;
312312
ofKind(CodeActionKind kind) =>
313313
getCodeActions(mainFileUri, range: code.range.range, kinds: [kind]);
314314

315-
// The code above will return a quickfix.remove.unusedImport
315+
// The code above will return a 'quickfix.remove.unusedImport'.
316316
expect(await ofKind(CodeActionKind.QuickFix), isNotEmpty);
317317
expect(await ofKind(CodeActionKind('quickfix.remove')), isNotEmpty);
318+
expect(await ofKind(CodeActionKind('quickfix.remove.foo')), isEmpty);
318319
expect(await ofKind(CodeActionKind('quickfix.other')), isEmpty);
319320
expect(await ofKind(CodeActionKind.Refactor), isEmpty);
320321
}

pkg/analysis_server/test/lsp/code_actions_refactor_test.dart

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import 'package:test/test.dart';
1212
import 'package:test_reflective_loader/test_reflective_loader.dart';
1313

1414
import '../tool/lsp_spec/matchers.dart';
15-
import '../utils/lsp_protocol_extensions.dart';
1615
import '../utils/test_code_extensions.dart';
1716
import 'code_actions_abstract.dart';
1817
import 'request_helpers_mixin.dart';
@@ -255,22 +254,12 @@ void f() {
255254
ofKind(CodeActionKind kind) =>
256255
getCodeActions(mainFileUri, range: code.range.range, kinds: [kind]);
257256

258-
// Helper that requests CodeActions for [kind] and ensures all results
259-
// returned have either an equal kind, or a kind that is prefixed with the
260-
// requested kind followed by a dot.
261-
Future<void> checkResults(CodeActionKind kind) async {
262-
var results = await ofKind(kind);
263-
for (var result in results) {
264-
var resultKind = result.asCodeActionLiteral.kind;
265-
expect('$resultKind', anyOf([equals('$kind'), startsWith('$kind.')]));
266-
}
267-
}
268-
269-
// Check a few of each that will produces multiple matches and no matches.
270-
await checkResults(CodeActionKind.Refactor);
271-
await checkResults(CodeActionKind.RefactorExtract);
272-
await checkResults(CodeActionKind('refactor.extract.foo'));
273-
await checkResults(CodeActionKind.RefactorRewrite);
257+
// The code above will return a 'refactor.extract' (as well as some other
258+
// refactors, but not rewrite).
259+
expect(await ofKind(CodeActionKind.Refactor), isNotEmpty);
260+
expect(await ofKind(CodeActionKind.RefactorExtract), isNotEmpty);
261+
expect(await ofKind(CodeActionKind('refactor.extract.foo')), isEmpty);
262+
expect(await ofKind(CodeActionKind.RefactorRewrite), isEmpty);
274263
}
275264

276265
Future<void> test_generatesNames() async {

pkg/analysis_server/test/lsp/request_helpers_mixin.dart

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:path/path.dart' as path;
1818
import 'package:test/test.dart' as test show expect;
1919
import 'package:test/test.dart';
2020

21+
import '../utils/lsp_protocol_extensions.dart';
2122
import 'change_verifier.dart';
2223

2324
/// A mixin with helpers for applying LSP edits to strings.
@@ -346,7 +347,7 @@ mixin LspRequestHelpersMixin {
346347
List<CodeActionKind>? kinds,
347348
CodeActionTriggerKind? triggerKind,
348349
ProgressToken? workDoneToken,
349-
}) {
350+
}) async {
350351
range ??=
351352
position != null
352353
? Range(start: position, end: position)
@@ -367,7 +368,8 @@ mixin LspRequestHelpersMixin {
367368
workDoneToken: workDoneToken,
368369
),
369370
);
370-
return expectSuccessfulResponseTo(
371+
372+
var actions = await expectSuccessfulResponseTo(
371373
request,
372374
_fromJsonList(
373375
_generateFromJsonFor(
@@ -378,6 +380,26 @@ mixin LspRequestHelpersMixin {
378380
),
379381
),
380382
);
383+
384+
// As an additional check, ensure all returned values are either exact
385+
// matches or sub-kinds of the requested kind(s).
386+
if (kinds != null && kinds.isNotEmpty) {
387+
// Kinds must either by an exact match, or start with the
388+
// requested value followed by a dot (a sub-kind).
389+
var allowedKinds =
390+
kinds
391+
.expand((kind) => [equals('$kind'), startsWith('$kind.')])
392+
.toList();
393+
394+
// Only CodeActionLiterals can be checked because bare commands do not
395+
// have CodeActionKinds (once they've left the server).
396+
var literals = actions.where((action) => action.isCodeActionLiteral);
397+
for (var result in literals) {
398+
expect(result.asCodeActionLiteral.kind.toString(), anyOf(allowedKinds));
399+
}
400+
}
401+
402+
return actions;
381403
}
382404

383405
Future<TextDocumentCodeLensResult> getCodeLens(Uri uri) {

0 commit comments

Comments
 (0)