QuickFix. Issue 55805. Create extension setter.
Bug: https://github.com/dart-lang/sdk/issues/55805
Change-Id: I0972eee46a1b0ab8e14235e9a56fd08949f634f8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/369522
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_extension_member.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_extension_member.dart
index 9a23022..cd24467 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/create_extension_member.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/create_extension_member.dart
@@ -171,6 +171,85 @@
}
}
+class CreateExtensionSetter extends _CreateExtensionMember {
+ String _setterName = '';
+
+ CreateExtensionSetter({
+ required super.context,
+ });
+
+ @override
+ List<String> get fixArguments => [_setterName];
+
+ @override
+ FixKind get fixKind => DartFixKind.CREATE_EXTENSION_SETTER;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ var nameNode = node;
+ if (nameNode is! SimpleIdentifier) {
+ return;
+ }
+ if (!nameNode.inSetterContext()) {
+ return;
+ }
+
+ _setterName = nameNode.name;
+
+ // prepare target
+ Expression? target;
+ switch (nameNode.parent) {
+ case PrefixedIdentifier prefixedIdentifier:
+ if (prefixedIdentifier.identifier == nameNode) {
+ target = prefixedIdentifier.prefix;
+ }
+ case PropertyAccess propertyAccess:
+ if (propertyAccess.propertyName == nameNode) {
+ target = propertyAccess.realTarget;
+ }
+ }
+ if (target == null) {
+ return;
+ }
+
+ // We need the type for the extension.
+ var targetType = target.staticType;
+ if (targetType == null ||
+ targetType is DynamicType ||
+ targetType is InvalidType) {
+ return;
+ }
+
+ // Try to find the type of the field.
+ var fieldTypeNode = climbPropertyAccess(nameNode);
+ var fieldType = inferUndefinedExpressionType(fieldTypeNode);
+
+ void writeSetter(DartEditBuilder builder) {
+ builder.writeSetterDeclaration(
+ _setterName,
+ nameGroupName: 'NAME',
+ parameterType: fieldType,
+ parameterTypeGroupName: 'TYPE',
+ );
+ }
+
+ var updatedExisting = await _updateExistingExtension(
+ builder,
+ targetType,
+ (extension, builder) {
+ builder.insertGetter(extension, (builder) {
+ writeSetter(builder);
+ });
+ },
+ );
+ if (updatedExisting) {
+ return;
+ }
+
+ await _addNewExtension(builder, targetType, nameNode, writeSetter);
+ }
+}
+
abstract class _CreateExtensionMember extends ResolvedCorrectionProducer {
_CreateExtensionMember({
required super.context,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 036f2ce..86a551f 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -691,6 +691,11 @@
DartFixKindPriority.DEFAULT - 20,
"Create extension method '{0}'",
);
+ static const CREATE_EXTENSION_SETTER = FixKind(
+ 'dart.fix.create.extension.setter',
+ DartFixKindPriority.DEFAULT - 20,
+ "Create extension setter '{0}'",
+ );
static const CREATE_FIELD = FixKind(
'dart.fix.create.field',
49,
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 2f8b91a..08aa4a8 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -1297,6 +1297,7 @@
],
CompileTimeErrorCode.UNDEFINED_SETTER: [
ChangeTo.getterOrSetter,
+ CreateExtensionSetter.new,
CreateField.new,
CreateSetter.new,
],
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart
index 8798115..b53a26a 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart
@@ -12,6 +12,7 @@
defineReflectiveSuite(() {
defineReflectiveTests(CreateExtensionGetterTest);
defineReflectiveTests(CreateExtensionMethodTest);
+ defineReflectiveTests(CreateExtensionSetterTest);
});
}
@@ -488,3 +489,193 @@
''');
}
}
+
+@reflectiveTest
+class CreateExtensionSetterTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.CREATE_EXTENSION_SETTER;
+
+ Future<void> test_existingExtension() async {
+ await resolveTestCode('''
+void f() {
+ ''.test = 0;
+}
+
+extension on String {}
+''');
+ await assertHasFix('''
+void f() {
+ ''.test = 0;
+}
+
+extension on String {
+ set test(int test) {}
+}
+''');
+ }
+
+ Future<void> test_existingExtension_generic_matching() async {
+ await resolveTestCode('''
+void f(List<int> a) {
+ a.test = 0;
+}
+
+extension E<T> on Iterable<T> {}
+''');
+ await assertHasFix('''
+void f(List<int> a) {
+ a.test = 0;
+}
+
+extension E<T> on Iterable<T> {
+ set test(int test) {}
+}
+''');
+ }
+
+ Future<void> test_existingExtension_generic_notMatching() async {
+ await resolveTestCode('''
+void f(List<int> a) {
+ a.test = 0;
+}
+
+extension E<K, V> on Map<K, V> {}
+''');
+ await assertHasFix('''
+void f(List<int> a) {
+ a.test = 0;
+}
+
+extension on List<int> {
+ set test(int test) {}
+}
+
+extension E<K, V> on Map<K, V> {}
+''');
+ }
+
+ Future<void> test_existingExtension_hasMethod() async {
+ await resolveTestCode('''
+void f() {
+ ''.test = 0;
+}
+
+extension E on String {
+ // ignore:unused_element
+ void foo() {}
+}
+''');
+ await assertHasFix('''
+void f() {
+ ''.test = 0;
+}
+
+extension E on String {
+ set test(int test) {}
+
+ // ignore:unused_element
+ void foo() {}
+}
+''');
+ }
+
+ Future<void> test_existingExtension_notGeneric_matching() async {
+ await resolveTestCode('''
+void f() {
+ ''.test = 0;
+}
+
+extension on String {}
+''');
+ await assertHasFix('''
+void f() {
+ ''.test = 0;
+}
+
+extension on String {
+ set test(int test) {}
+}
+''');
+ }
+
+ Future<void> test_existingExtension_notGeneric_notMatching() async {
+ await resolveTestCode('''
+void f() {
+ ''.test = 0;
+}
+
+extension on int {}
+''');
+ await assertHasFix('''
+void f() {
+ ''.test = 0;
+}
+
+extension on String {
+ set test(int test) {}
+}
+
+extension on int {}
+''');
+ }
+
+ Future<void> test_parent_nothing() async {
+ await resolveTestCode('''
+void f() {
+ test = 0;
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_parent_prefixedIdentifier() async {
+ await resolveTestCode('''
+void f(String a) {
+ a.test = 0;
+}
+''');
+ await assertHasFix('''
+void f(String a) {
+ a.test = 0;
+}
+
+extension on String {
+ set test(int test) {}
+}
+''');
+ }
+
+ Future<void> test_parent_propertyAccess_cascade() async {
+ await resolveTestCode('''
+void f(String a) {
+ a..test = 0;
+}
+''');
+ await assertHasFix('''
+void f(String a) {
+ a..test = 0;
+}
+
+extension on String {
+ set test(int test) {}
+}
+''');
+ }
+
+ Future<void> test_targetType_hasTypeArguments() async {
+ await resolveTestCode('''
+void f(List<int> a) {
+ a.test = 0;
+}
+''');
+ await assertHasFix('''
+void f(List<int> a) {
+ a.test = 0;
+}
+
+extension on List<int> {
+ set test(int test) {}
+}
+''');
+ }
+}