Add fix for creating a parameter
The variant of the parameter, optional/required and named/positional, is inferred from the other parameters used in the method.
Change-Id: I0c26e5d16e759978410ba854c0fed0a75d33724f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365826
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Moritz Sümmermann <mosum@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_parameter.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_parameter.dart
new file mode 100644
index 0000000..56dd9ee
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/create_parameter.dart
@@ -0,0 +1,112 @@
+// Copyright (c) 2024, 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/dart/element/nullability_suffix.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 CreateParameter extends ResolvedCorrectionProducer {
+ String _parameterName = '';
+
+ @override
+ CorrectionApplicability get applicability =>
+ CorrectionApplicability.singleLocation;
+
+ @override
+ List<String> get fixArguments => [_parameterName];
+
+ @override
+ FixKind get fixKind => DartFixKind.CREATE_PARAMETER;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ var nameNode = node;
+ if (nameNode is! SimpleIdentifier) {
+ return;
+ }
+ _parameterName = nameNode.name;
+ // prepare target Statement
+ var parameters =
+ node.thisOrAncestorOfType<FunctionExpression>()?.parameters ??
+ node.thisOrAncestorOfType<MethodDeclaration>()?.parameters ??
+ node.thisOrAncestorOfType<ConstructorDeclaration>()?.parameters;
+ if (parameters == null) {
+ return;
+ }
+
+ var requiredPositionals =
+ parameters.parameters.where((p) => p.isRequiredPositional);
+ var namedParameters = parameters.parameters.where((p) => p.isNamed);
+ var somethingAfterPositionals = requiredPositionals.isNotEmpty &&
+ parameters.parameters.any((p) => !p.isRequiredPositional);
+ var somethingBeforeNamed = requiredPositionals.isEmpty &&
+ parameters.parameters.any((p) => !p.isNamed);
+ var hasFollowingParameters =
+ somethingAfterPositionals || somethingBeforeNamed;
+
+ // compute type
+ var type =
+ inferUndefinedExpressionType(nameNode) ?? typeProvider.dynamicType;
+ var lastRequiredPositional = requiredPositionals.lastOrNull;
+ var lastNamed = namedParameters.lastOrNull;
+ var hasPreviousParameters =
+ lastRequiredPositional != null || lastNamed != null;
+ var last = lastRequiredPositional ?? lastNamed;
+ var trailingComma =
+ parameters.parameters.lastOrNull?.endToken.next?.lexeme == ',';
+
+ int insertionToken;
+ if (hasPreviousParameters) {
+ if (trailingComma) {
+ // After comma
+ insertionToken = last!.endToken.next!.end;
+ } else if (hasFollowingParameters) {
+ // After whitespace after comma
+ insertionToken = last!.endToken.next!.end + 1;
+ } else {
+ // After last, as there is no comma
+ insertionToken = last!.end;
+ }
+ } else {
+ // At first position
+ insertionToken = parameters.leftParenthesis.end;
+ }
+
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addInsertion(insertionToken, (builder) {
+ //prefix
+ if (hasPreviousParameters) {
+ if (trailingComma) {
+ builder.writeln();
+ var whitespace = utils.getNodePrefix(last!);
+ builder.write(whitespace);
+ } else if (!hasFollowingParameters) {
+ builder.write(', ');
+ }
+ }
+ builder.writeParameter(
+ _parameterName,
+ type: type,
+ nameGroupName: 'NAME',
+ typeGroupName: 'TYPE',
+ isRequiredType: true,
+ isRequiredNamed: last != null &&
+ last == lastNamed &&
+ type.nullabilitySuffix != NullabilitySuffix.question,
+ );
+ //suffix
+ if (trailingComma) {
+ builder.write(',');
+ } else if (hasFollowingParameters) {
+ builder.write(', ');
+ }
+ });
+ builder.addLinkedPosition(range.node(node), 'NAME');
+ });
+ }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 6d98b87..f0c2d69 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -723,6 +723,11 @@
49,
"Create 'noSuchMethod' method",
);
+ static const CREATE_PARAMETER = FixKind(
+ 'dart.fix.create.parameter',
+ DartFixKindPriority.DEFAULT,
+ "Create required positional parameter '{0}'",
+ );
static const CREATE_SETTER = FixKind(
'dart.fix.create.setter',
DartFixKindPriority.DEFAULT,
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 38a4b32..9ef02b0 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -91,6 +91,7 @@
import 'package:analysis_server/src/services/correction/dart/create_missing_overrides.dart';
import 'package:analysis_server/src/services/correction/dart/create_mixin.dart';
import 'package:analysis_server/src/services/correction/dart/create_no_such_method.dart';
+import 'package:analysis_server/src/services/correction/dart/create_parameter.dart';
import 'package:analysis_server/src/services/correction/dart/create_setter.dart';
import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
import 'package:analysis_server/src/services/correction/dart/extend_class_for_mixin.dart';
@@ -1259,6 +1260,7 @@
CreateField.new,
CreateGetter.new,
CreateLocalVariable.new,
+ CreateParameter.new,
CreateMethodOrFunction.new,
CreateMixin.new,
CreateSetter.new,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_parameter_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_parameter_test.dart
new file mode 100644
index 0000000..5fcfb8d
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/create_parameter_test.dart
@@ -0,0 +1,254 @@
+// Copyright (c) 2024, 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:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(CreateParameterTest);
+ });
+}
+
+@reflectiveTest
+class CreateParameterTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.CREATE_PARAMETER;
+
+ Future<void> test_dynamic_type() async {
+ await resolveTestCode('''
+int f(
+ int b,
+) {
+ var i = b2;
+ return i;
+}
+''');
+ await assertHasFix('''
+int f(
+ int b,
+ dynamic b2,
+) {
+ var i = b2;
+ return i;
+}
+''');
+ }
+
+ Future<void> test_final_comma() async {
+ await resolveTestCode('''
+int f(
+ int b,
+) {
+ int i = b2;
+ return i;
+}
+''');
+ await assertHasFix('''
+int f(
+ int b,
+ int b2,
+) {
+ int i = b2;
+ return i;
+}
+''');
+ }
+
+ Future<void> test_method_type() async {
+ await resolveTestCode('''
+class A{
+ int f(
+ int b,
+ ) {
+ int i = b2;
+ return i;
+ }
+}
+''');
+ await assertHasFix('''
+class A{
+ int f(
+ int b,
+ int b2,
+ ) {
+ int i = b2;
+ return i;
+ }
+}
+''');
+ }
+
+ Future<void> test_multi() async {
+ await resolveTestCode('''
+int f(int b) {
+ int i = b2;
+ return i;
+}
+''');
+ await assertHasFix('''
+int f(int b, int b2) {
+ int i = b2;
+ return i;
+}
+''');
+ }
+
+ Future<void> test_only() async {
+ await resolveTestCode('''
+int f() {
+ int i = b;
+ return i;
+}
+''');
+ await assertHasFix('''
+int f(int b) {
+ int i = b;
+ return i;
+}
+''');
+ }
+
+ Future<void> test_with_constructor() async {
+ await resolveTestCode('''
+class A {
+ A() {
+ int i = b;
+ g(i);
+ }
+}
+void g(int n) {}
+''');
+ await assertHasFix('''
+class A {
+ A(int b) {
+ int i = b;
+ g(i);
+ }
+}
+void g(int n) {}
+''');
+ }
+
+ Future<void> test_with_local_function() async {
+ await resolveTestCode('''
+void f(int a) {
+ int g(int a){
+ int i = b2;
+ return i;
+ }
+ g(3);
+}
+''');
+ await assertHasFix('''
+void f(int a) {
+ int g(int a, int b2){
+ int i = b2;
+ return i;
+ }
+ g(3);
+}
+''');
+ }
+
+ Future<void> test_with_named() async {
+ await resolveTestCode('''
+int f({int? b}) {
+ int i = b2;
+ return i;
+}
+''');
+ await assertHasFix('''
+int f({int? b, required int b2}) {
+ int i = b2;
+ return i;
+}
+''');
+ }
+
+ Future<void> test_with_named_and_positional() async {
+ await resolveTestCode('''
+int f(int a, {int? b}) {
+ int i = b2;
+ return i;
+}
+''');
+ await assertHasFix('''
+int f(int a, int b2, {int? b}) {
+ int i = b2;
+ return i;
+}
+''');
+ }
+
+ Future<void> test_with_named_nullable() async {
+ await resolveTestCode('''
+int? f({int? b}) {
+ int? i = b2;
+ return i;
+}
+''');
+ await assertHasFix('''
+int? f({int? b, int? b2}) {
+ int? i = b2;
+ return i;
+}
+''');
+ }
+
+ Future<void> test_with_optional_positional() async {
+ await resolveTestCode('''
+int f([int? b]) {
+ int i = b2;
+ return i;
+}
+''');
+ await assertHasFix('''
+int f(int b2, [int? b]) {
+ int i = b2;
+ return i;
+}
+''');
+ }
+
+ Future<void> test_with_required_positional_and_optional() async {
+ await resolveTestCode('''
+int f(int a, [int? b]) {
+ int i = b2;
+ return i;
+}
+''');
+ await assertHasFix('''
+int f(int a, int b2, [int? b]) {
+ int i = b2;
+ return i;
+}
+''');
+ }
+
+ Future<void> test_with_required_positional_and_optional_trailing() async {
+ await resolveTestCode('''
+int f(
+ int a, [
+ int? b,
+]) {
+ int i = b2;
+ return i;
+}
+''');
+ await assertHasFix('''
+int f(
+ int a,
+ int b2, [
+ int? b,
+]) {
+ int i = b2;
+ return i;
+}
+''');
+ }
+}
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 1e3ea49..595e1ad 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
@@ -117,6 +117,7 @@
import 'create_missing_overrides_test.dart' as create_missing_overrides;
import 'create_mixin_test.dart' as create_mixin;
import 'create_no_such_method_test.dart' as create_no_such_method;
+import 'create_parameter_test.dart' as create_parameter;
import 'create_setter_test.dart' as create_setter;
import 'data_driven/test_all.dart' as data_driven;
import 'directives_ordering_test.dart' as directives_ordering;
@@ -381,6 +382,7 @@
create_function.main();
create_getter.main();
create_local_variable.main();
+ create_parameter.main();
create_method.main();
create_missing_overrides.main();
create_mixin.main();