diff --git a/pkg/analysis_server/lib/src/services/correction/dart/wrap_in_unawaited.dart b/pkg/analysis_server/lib/src/services/correction/dart/wrap_in_unawaited.dart new file mode 100644 index 00000000000..31bc9e84e17 --- /dev/null +++ b/pkg/analysis_server/lib/src/services/correction/dart/wrap_in_unawaited.dart @@ -0,0 +1,42 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart'; +import 'package:analysis_server/src/services/correction/fix.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:analyzer_plugin/utilities/range_factory.dart'; + +class WrapInUnawaited extends CorrectionProducer { + @override + FixKind get fixKind => DartFixKind.WRAP_IN_UNAWAITED; + + @override + Future compute(ChangeBuilder builder) async { + AstNode? node = this.node; + if (node is SimpleIdentifier) { + node = node.parent; + } + Expression? expression; + if (node is Expression) { + expression = node; + } else if (node is ExpressionStatement) { + expression = node.expression; + } + if (expression == null) return; + + var value = utils.getNodeText(expression); + + await builder.addDartFileEdit(file, (builder) { + var libraryPrefix = + builder.importLibraryElement(Uri.parse('dart:async')).prefix; + var prefix = libraryPrefix != null ? '$libraryPrefix.' : ''; + builder.addSimpleReplacement( + range.node(expression!), + '${prefix}unawaited($value)', + ); + }); + } +} diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart index e98c4749203..9dd12866efa 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix.dart @@ -1630,6 +1630,11 @@ class DartFixKind { DartFixKindPriority.DEFAULT, "Wrap in a 'Text' widget", ); + static const WRAP_IN_UNAWAITED = FixKind( + 'dart.fix.wrap.unawaited', + DartFixKindPriority.DEFAULT, + "Wrap in 'unawaited'", + ); } class DartFixKindPriority { diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart index a6c590690ca..84466439c07 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart @@ -194,6 +194,7 @@ import 'package:analysis_server/src/services/correction/dart/use_not_eq_null.dar import 'package:analysis_server/src/services/correction/dart/use_rethrow.dart'; import 'package:analysis_server/src/services/correction/dart/wrap_in_future.dart'; import 'package:analysis_server/src/services/correction/dart/wrap_in_text.dart'; +import 'package:analysis_server/src/services/correction/dart/wrap_in_unawaited.dart'; import 'package:analysis_server/src/services/correction/fix.dart'; import 'package:analysis_server/src/services/correction/util.dart'; import 'package:analysis_server/src/services/linter/lint_names.dart'; @@ -449,6 +450,7 @@ class FixProcessor extends BaseProcessor { ], LintNames.discarded_futures: [ AddAsync.new, + WrapInUnawaited.new, ], LintNames.empty_catches: [ RemoveEmptyCatch.new, @@ -625,6 +627,7 @@ class FixProcessor extends BaseProcessor { ], LintNames.unawaited_futures: [ AddAwait.unawaited, + WrapInUnawaited.new, ], LintNames.unnecessary_brace_in_string_interps: [ RemoveInterpolationBraces.new, diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart index 323361b0f9d..58af3442021 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart @@ -234,6 +234,7 @@ import 'use_not_eq_null_test.dart' as use_not_eq_null; import 'use_rethrow_test.dart' as use_rethrow; import 'wrap_in_future_test.dart' as wrap_in_future; import 'wrap_in_text_test.dart' as wrap_in_text; +import 'wrap_in_unawaited_test.dart' as wrap_in_unawaited; void main() { defineReflectiveSuite(() { @@ -438,5 +439,6 @@ void main() { use_rethrow.main(); wrap_in_future.main(); wrap_in_text.main(); + wrap_in_unawaited.main(); }, name: 'fix'); } diff --git a/pkg/analysis_server/test/src/services/correction/fix/wrap_in_unawaited_test.dart b/pkg/analysis_server/test/src/services/correction/fix/wrap_in_unawaited_test.dart new file mode 100644 index 00000000000..548d60d6f5a --- /dev/null +++ b/pkg/analysis_server/test/src/services/correction/fix/wrap_in_unawaited_test.dart @@ -0,0 +1,167 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analysis_server/src/services/correction/fix.dart'; +import 'package:analysis_server/src/services/linter/lint_names.dart'; +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'fix_processor.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(WrapInUnawaitedTest); + defineReflectiveTests(WrapDiscardedFutureInUnawaitedTest); + }); +} + +@reflectiveTest +class WrapDiscardedFutureInUnawaitedTest extends FixProcessorLintTest { + @override + FixKind get kind => DartFixKind.WRAP_IN_UNAWAITED; + + @override + String get lintCode => LintNames.discarded_futures; + + Future test_expressionStatement() async { + await resolveTestCode(''' +void f() { + g(); +} + +Future g() async { } +'''); + await assertHasFix(''' +import 'dart:async'; + +void f() { + unawaited(g()); +} + +Future g() async { } +'''); + } +} + +@reflectiveTest +class WrapInUnawaitedTest extends FixProcessorLintTest { + @override + FixKind get kind => DartFixKind.WRAP_IN_UNAWAITED; + + @override + String get lintCode => LintNames.unawaited_futures; + + Future test_expressionStatement() async { + await resolveTestCode(''' +Future f() async { + g(); +} + +Future g() async { } +'''); + await assertHasFix(''' +import 'dart:async'; + +Future f() async { + unawaited(g()); +} + +Future g() async { } +'''); + } + + Future test_expressionStatement_prefixed() async { + newFile('$testPackageLibPath/g.dart', ''' +Future g() async { } +'''); + + await resolveTestCode(''' +import 'g.dart' as g_lib; + +Future f() async { + g_lib.g(); +} +'''); + await assertHasFix(''' +import 'dart:async'; + +import 'g.dart' as g_lib; + +Future f() async { + unawaited(g_lib.g()); +} +'''); + } + + Future test_expressionStatement_prefixedImport() async { + await resolveTestCode(''' +import 'dart:async' as dart_async; + +dart_async.Future f() async { + g(); +} + +dart_async.Future g() async { } +'''); + await assertHasFix(''' +import 'dart:async' as dart_async; + +dart_async.Future f() async { + dart_async.unawaited(g()); +} + +dart_async.Future g() async { } +'''); + } + + Future test_expressionStatement_prefixedTarget() async { + newFile('$testPackageLibPath/g.dart', ''' +class C { + static Future g() async { } +} +'''); + + await resolveTestCode(''' +import 'g.dart' as g_lib; + +Future f() async { + g_lib.C.g(); +} +'''); + await assertHasFix(''' +import 'dart:async'; + +import 'g.dart' as g_lib; + +Future f() async { + unawaited(g_lib.C.g()); +} +'''); + } + + Future test_expressionStatement_target() async { + await resolveTestCode(''' +class C { + Future g() async { } +} + +Future f() async { + var c = C(); + c.g(); +} +'''); + await assertHasFix(''' +import 'dart:async'; + +class C { + Future g() async { } +} + +Future f() async { + var c = C(); + unawaited(c.g()); +} +'''); + } +}