Skip to content

Commit 37edff7

Browse files
FMorschelCommit Queue
authored andcommitted
[DAS] Adds new Remove async assist
Fixes: #59814 Fixes: #23962 Change-Id: I8121da0106f4f81b93bfcdc0908b0977210fa3c3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/425602 Commit-Queue: Keerti Parthasarathy <[email protected]> Reviewed-by: Keerti Parthasarathy <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Auto-Submit: Felipe Morschel <[email protected]>
1 parent 3a08da0 commit 37edff7

File tree

12 files changed

+710
-6
lines changed

12 files changed

+710
-6
lines changed

pkg/analysis_server/lib/src/services/correction/assist.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,11 @@ abstract final class DartAssistKind {
361361
DartAssistKindPriority.DEFAULT,
362362
'Join variable declaration',
363363
);
364+
static const REMOVE_ASYNC = AssistKind(
365+
'dart.assist.remove.async',
366+
DartAssistKindPriority.DEFAULT,
367+
"Remove 'async' modifier",
368+
);
364369
static const REMOVE_DIGIT_SEPARATORS = AssistKind(
365370
'dart.assist.remove.digitSeparators',
366371
DartAssistKindPriority.DEFAULT,

pkg/analysis_server/lib/src/services/correction/assist_internal.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import 'package:analysis_server/src/services/correction/dart/join_else_with_if.d
6161
import 'package:analysis_server/src/services/correction/dart/join_if_with_inner.dart';
6262
import 'package:analysis_server/src/services/correction/dart/join_if_with_outer.dart';
6363
import 'package:analysis_server/src/services/correction/dart/join_variable_declaration.dart';
64+
import 'package:analysis_server/src/services/correction/dart/remove_async.dart';
6465
import 'package:analysis_server/src/services/correction/dart/remove_digit_separators.dart';
6566
import 'package:analysis_server/src/services/correction/dart/remove_type_annotation.dart';
6667
import 'package:analysis_server/src/services/correction/dart/replace_conditional_with_if_else.dart';
@@ -138,6 +139,7 @@ const Set<ProducerGenerator> _builtInGenerators = {
138139
JoinIfWithInner.new,
139140
JoinIfWithOuter.new,
140141
JoinVariableDeclaration.new,
142+
RemoveAsync.new,
141143
RemoveDigitSeparators.new,
142144
RemoveTypeAnnotation.other,
143145
ReplaceConditionalWithIfElse.new,
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
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/src/services/correction/assist.dart';
6+
import 'package:analysis_server/src/services/correction/fix.dart';
7+
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
8+
import 'package:analyzer/dart/ast/ast.dart';
9+
import 'package:analyzer/dart/ast/token.dart';
10+
import 'package:analyzer/dart/ast/visitor.dart';
11+
import 'package:analyzer/dart/element/type.dart';
12+
import 'package:analyzer/dart/element/type_provider.dart';
13+
import 'package:analyzer/dart/element/type_system.dart';
14+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
15+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
16+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
17+
import 'package:analyzer_plugin/utilities/range_factory.dart';
18+
19+
class RemoveAsync extends ResolvedCorrectionProducer {
20+
final _Type _type;
21+
22+
RemoveAsync({required super.context}) : _type = _Type.other;
23+
24+
RemoveAsync.unnecessary({required super.context}) : _type = _Type.unnecessary;
25+
26+
@override
27+
CorrectionApplicability get applicability =>
28+
// Not predictably the correct action.
29+
CorrectionApplicability.singleLocation;
30+
31+
@override
32+
AssistKind get assistKind => DartAssistKind.REMOVE_ASYNC;
33+
34+
@override
35+
FixKind get fixKind => DartFixKind.REMOVE_ASYNC;
36+
37+
@override
38+
Future<void> compute(ChangeBuilder builder) async {
39+
bool updateReturnType = true;
40+
AstNode? node = this.node;
41+
FunctionBody body;
42+
DartType? returnType;
43+
if (_type == _Type.unnecessary) {
44+
if (node.thisOrAncestorOfType<FunctionBody>() case var ancestorBody?) {
45+
body = ancestorBody;
46+
} else {
47+
return;
48+
}
49+
} else {
50+
if (node is Block) {
51+
node = node.parent;
52+
}
53+
if (node is BlockFunctionBody) {
54+
node = node.parent;
55+
} else if (node is ExpressionFunctionBody) {
56+
node = node.parent;
57+
}
58+
if (node case FormalParameterList(:var parent?)) {
59+
node = parent;
60+
}
61+
if (node case NamedType(:var parent?)) {
62+
node = parent;
63+
}
64+
if (node case FunctionExpression(:FunctionDeclaration parent)) {
65+
node = parent;
66+
}
67+
switch (node) {
68+
case FunctionExpression():
69+
body = node.body;
70+
case FunctionDeclaration(
71+
:var functionExpression,
72+
:var declaredFragment,
73+
):
74+
body = functionExpression.body;
75+
if (declaredFragment?.element case var declaredElement?) {
76+
returnType = declaredElement.returnType;
77+
} else if (declaredFragment case var declaredFragment?) {
78+
returnType = declaredFragment.element.returnType;
79+
}
80+
case MethodDeclaration():
81+
body = node.body;
82+
returnType = node.declaredFragment!.element.returnType;
83+
default:
84+
return;
85+
}
86+
}
87+
if (body.keyword?.lexeme != Keyword.ASYNC.lexeme || body.star != null) {
88+
return;
89+
}
90+
if (returnType is InterfaceType &&
91+
(returnType.isDartAsyncFuture || returnType.isDartAsyncFutureOr)) {
92+
var newReturn = returnType.typeArguments.first;
93+
var visitor = _VisitorTester(typeSystem, typeProvider, newReturn);
94+
if (!visitor.returnsWillBeAssignable(body)) {
95+
if (visitor.returnsAreAssignable(body)) {
96+
updateReturnType = false;
97+
} else {
98+
return;
99+
}
100+
}
101+
if (visitor.foundAwait) {
102+
return;
103+
}
104+
} else if (returnType != null &&
105+
!_VisitorTester(
106+
typeSystem,
107+
typeProvider,
108+
returnType,
109+
).returnsAreAssignable(body)) {
110+
return;
111+
} else {
112+
updateReturnType = false;
113+
}
114+
if (updateReturnType) {
115+
return await builder.addDartFileEdit(file, (builder) {
116+
builder.convertFunctionFromAsyncToSync(
117+
body: body,
118+
typeSystem: typeSystem,
119+
typeProvider: typeProvider,
120+
);
121+
});
122+
} else {
123+
await builder.addDartFileEdit(file, (builder) {
124+
var keyword = body.keyword!;
125+
builder.addDeletion(
126+
range.startOffsetEndOffset(
127+
keyword.offset,
128+
keyword.end + (keyword.next!.offset == (keyword.end) ? 0 : 1),
129+
),
130+
);
131+
});
132+
}
133+
}
134+
}
135+
136+
enum _Type { unnecessary, other }
137+
138+
/// An AST visitor used to test if all return statements in a function body
139+
/// are assignable to a given type.
140+
class _VisitorTester extends RecursiveAstVisitor<void> {
141+
/// A flag indicating whether a return statement was visited.
142+
bool _foundOneReturn = false;
143+
144+
/// A flag indicating whether an await expression was found.
145+
bool _foundAwait = false;
146+
147+
/// A flag indicating whether the return type is assignable considering
148+
/// [_isAssignable].
149+
bool _returnsAreAssignable = true;
150+
151+
final TypeProvider typeProvider;
152+
153+
/// The type system used to check assignability.
154+
final TypeSystem typeSystem;
155+
156+
/// The type that the return statements should be assignable to.
157+
final DartType argumentType;
158+
159+
bool _processingFuture = false;
160+
161+
/// Initialize a newly created visitor.
162+
_VisitorTester(this.typeSystem, this.typeProvider, this.argumentType);
163+
164+
/// A flag indicating whether an await expression was found.
165+
bool get foundAwait => _foundAwait;
166+
167+
/// Returns `true` if all return statements in the given [node] are
168+
/// assignable to the [argumentType] type.
169+
bool returnsAreAssignable(AstNode node) {
170+
_foundAwait = false;
171+
_returnsAreAssignable = true;
172+
_foundOneReturn = false;
173+
_processingFuture = true;
174+
node.accept(this);
175+
return _returnsAreAssignable && _foundOneReturn || !_foundOneReturn;
176+
}
177+
178+
/// Returns `true` if all return statements in the given [node] are
179+
/// assignable to the [argumentType] type.
180+
bool returnsWillBeAssignable(AstNode node) {
181+
_foundAwait = false;
182+
_returnsAreAssignable = true;
183+
_foundOneReturn = false;
184+
node.accept(this);
185+
return _returnsAreAssignable && _foundOneReturn || !_foundOneReturn;
186+
}
187+
188+
@override
189+
void visitAwaitExpression(AwaitExpression node) {
190+
_foundAwait = true;
191+
// No need to continue processing if we found an await expression.
192+
}
193+
194+
@override
195+
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
196+
_foundOneReturn = true;
197+
if (node.expression.staticType case var type?) {
198+
_returnsAreAssignable &= _isAssignable(type);
199+
} else {
200+
_returnsAreAssignable = false;
201+
}
202+
}
203+
204+
@override
205+
void visitFunctionExpression(FunctionExpression node) {
206+
// Return statements within closures aren't counted.
207+
}
208+
209+
@override
210+
void visitReturnStatement(ReturnStatement node) {
211+
_foundOneReturn = true;
212+
if (node.expression?.staticType case var type?) {
213+
_returnsAreAssignable &= _isAssignable(type);
214+
} else {
215+
_returnsAreAssignable = false;
216+
}
217+
}
218+
219+
/// Tests whether a type is assignable to the [argumentType] type.
220+
bool _isAssignable(DartType type) {
221+
if (_processingFuture) {
222+
return typeSystem.isAssignableTo(
223+
type,
224+
typeProvider.futureOrType(argumentType),
225+
);
226+
}
227+
return typeSystem.isAssignableTo(type, argumentType);
228+
}
229+
}

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2415,9 +2415,7 @@ LintCode.unintended_html_in_doc_comment:
24152415
notes: |-
24162416
The fix is to encode the angle brackets.
24172417
LintCode.unnecessary_async:
2418-
status: needsFix
2419-
notes: |-
2420-
And probably also quick assist that works even if returns `Future`.
2418+
status: hasFix
24212419
LintCode.unnecessary_await_in_return:
24222420
status: hasFix
24232421
LintCode.unnecessary_brace_in_string_interps:

pkg/analysis_server/lib/src/services/correction/fix.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,11 @@ abstract final class DartFixKind {
11761176
DartFixKindPriority.inFile,
11771177
'Remove unnecessary assignments everywhere in file',
11781178
);
1179+
static const REMOVE_ASYNC = FixKind(
1180+
'dart.fix.remove.async',
1181+
DartFixKindPriority.standard,
1182+
"Remove 'async' modifier",
1183+
);
11791184
static const REMOVE_AWAIT = FixKind(
11801185
'dart.fix.remove.await',
11811186
DartFixKindPriority.standard,

pkg/analysis_server/lib/src/services/correction/fix_internal.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ import 'package:analysis_server/src/services/correction/dart/remove_annotation.d
134134
import 'package:analysis_server/src/services/correction/dart/remove_argument.dart';
135135
import 'package:analysis_server/src/services/correction/dart/remove_assertion.dart';
136136
import 'package:analysis_server/src/services/correction/dart/remove_assignment.dart';
137+
import 'package:analysis_server/src/services/correction/dart/remove_async.dart';
137138
import 'package:analysis_server/src/services/correction/dart/remove_await.dart';
138139
import 'package:analysis_server/src/services/correction/dart/remove_break.dart';
139140
import 'package:analysis_server/src/services/correction/dart/remove_character.dart';
@@ -489,6 +490,7 @@ final _builtInLintGenerators = <LintCode, List<ProducerGenerator>>{
489490
ConvertToWildcardPattern.new,
490491
],
491492
LinterLintCode.unawaited_futures: [AddAwait.unawaited, WrapInUnawaited.new],
493+
LinterLintCode.unnecessary_async: [RemoveAsync.unnecessary],
492494
LinterLintCode.unnecessary_await_in_return: [RemoveAwait.new],
493495
LinterLintCode.unnecessary_brace_in_string_interps: [
494496
RemoveInterpolationBraces.new,
@@ -698,7 +700,10 @@ final _builtInNonLintGenerators = <DiagnosticCode, List<ProducerGenerator>>{
698700
CompileTimeErrorCode.ILLEGAL_ASYNC_GENERATOR_RETURN_TYPE: [
699701
ReplaceReturnTypeStream.new,
700702
],
701-
CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE: [ReplaceReturnTypeFuture.new],
703+
CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE: [
704+
ReplaceReturnTypeFuture.new,
705+
RemoveAsync.new,
706+
],
702707
CompileTimeErrorCode.ILLEGAL_SYNC_GENERATOR_RETURN_TYPE: [
703708
ReplaceReturnTypeIterable.new,
704709
],

0 commit comments

Comments
 (0)