[DAS] Adds 'Create extension method/operator' fixes
R=srawlins@google.com
Fixes: https://github.com/dart-lang/sdk/issues/60203
Change-Id: I2527c130dd68cf678ac4529eda42397ef80ca53c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/412060
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Auto-Submit: Felipe Morschel <git@fmorschel.dev>
Commit-Queue: Samuel Rawlins <srawlins@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 d6293e5..6a4642a 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
@@ -5,14 +5,18 @@
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/util.dart';
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/resolver/applicable_extensions.dart';
import 'package:analyzer/src/utilities/extensions/ast.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:collection/collection.dart';
class CreateExtensionGetter extends _CreateExtensionMember {
String _getterName = '';
@@ -53,6 +57,8 @@
return;
}
+ // TODO(FMorschel): We should take into account if the target type contains
+ // a setter for the same name and stop the fix from being applied.
// We need the type for the extension.
var targetType = target.staticType;
if (targetType == null ||
@@ -67,7 +73,7 @@
void writeGetter(DartEditBuilder builder) {
if (fieldType != null) {
- builder.writeType(fieldType);
+ builder.writeType(fieldType, methodBeingCopied: methodBeingCopied);
builder.write(' ');
}
builder.write('get $_getterName => ');
@@ -89,7 +95,13 @@
return;
}
- await _addNewExtension(builder, targetType, nameNode, writeGetter);
+ await _addNewExtension(
+ builder,
+ targetType,
+ nameNode,
+ writeGetter,
+ involvedTypes: [fieldType],
+ );
}
}
@@ -137,7 +149,11 @@
var returnType = inferUndefinedExpressionType(invocation);
void writeMethod(DartEditBuilder builder) {
- if (builder.writeType(returnType, groupName: 'RETURN_TYPE')) {
+ if (builder.writeType(
+ returnType,
+ groupName: 'RETURN_TYPE',
+ methodBeingCopied: methodBeingCopied,
+ )) {
builder.write(' ');
}
@@ -145,8 +161,20 @@
builder.write(_methodName);
});
+ builder.writeTypeParameters(
+ [
+ returnType,
+ ...invocation.argumentList.arguments.map((e) => e.staticType),
+ ].typeParameters
+ .whereNot([targetType].typeParameters.contains)
+ .toList(),
+ );
+
builder.write('(');
- builder.writeParametersMatchingArguments(invocation.argumentList);
+ builder.writeParametersMatchingArguments(
+ invocation.argumentList,
+ methodBeingCopied: methodBeingCopied,
+ );
builder.write(') {}');
}
@@ -166,6 +194,136 @@
}
}
+class CreateExtensionOperator extends _CreateExtensionMember {
+ String _operator = '';
+
+ CreateExtensionOperator({required super.context});
+
+ @override
+ List<String>? get fixArguments => [_operator];
+
+ @override
+ FixKind get fixKind => DartFixKind.CREATE_EXTENSION_OPERATOR;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ bool indexSetter = false;
+ bool innerParameter = true;
+ var node = this.node;
+ if (node is! Expression) {
+ return;
+ }
+
+ Expression? target;
+ DartType? parameterType;
+ DartType? assigningType;
+
+ switch (node) {
+ case IndexExpression(:var parent):
+ if (node.target?.staticType?.element3.declaresIndex ?? true) {
+ return;
+ }
+ target = node.target;
+ _operator = TokenType.INDEX.lexeme;
+ parameterType = node.index.staticType;
+ if (parameterType == null) {
+ return;
+ }
+ if (parent case AssignmentExpression(
+ :var leftHandSide,
+ :var rightHandSide,
+ ) when leftHandSide == node) {
+ assigningType = rightHandSide.staticType;
+ indexSetter = true;
+ _operator = TokenType.INDEX_EQ.lexeme;
+ }
+ case BinaryExpression():
+ target = node.leftOperand;
+ _operator = node.operator.lexeme;
+ parameterType = node.rightOperand.staticType;
+ if (parameterType == null) {
+ return;
+ }
+ case PrefixExpression():
+ target = node.operand;
+ _operator = node.operator.lexeme;
+ innerParameter = false;
+ }
+
+ 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;
+ }
+
+ DartType returnType;
+ // If this is an index setter, the return type must be void.
+ if (indexSetter) {
+ returnType = VoidTypeImpl.instance;
+ } else {
+ // Try to find the return type.
+ returnType = inferUndefinedExpressionType(node) ?? VoidTypeImpl.instance;
+ }
+
+ void writeMethod(DartEditBuilder builder) {
+ if (builder.writeType(
+ returnType,
+ groupName: 'RETURN_TYPE',
+ methodBeingCopied: methodBeingCopied,
+ )) {
+ builder.write(' ');
+ }
+
+ builder.write('operator ');
+ builder.write(_operator);
+
+ builder.write('(');
+ if (innerParameter) {
+ builder.writeFormalParameter(
+ indexSetter ? 'index' : 'other',
+ type: parameterType,
+ methodBeingCopied: methodBeingCopied,
+ );
+ }
+ if (indexSetter) {
+ builder.write(', ');
+ builder.writeFormalParameter(
+ 'newValue',
+ type: assigningType,
+ methodBeingCopied: methodBeingCopied,
+ );
+ }
+ builder.write(') {}');
+ }
+
+ var updatedExisting = await _updateExistingExtension(builder, targetType, (
+ extension,
+ builder,
+ ) {
+ builder.insertMethod(extension, (builder) {
+ writeMethod(builder);
+ });
+ });
+ if (updatedExisting) {
+ return;
+ }
+
+ await _addNewExtension(
+ builder,
+ targetType,
+ target,
+ writeMethod,
+ involvedTypes: [parameterType, returnType],
+ );
+ }
+}
+
class CreateExtensionSetter extends _CreateExtensionMember {
String _setterName = '';
@@ -205,6 +363,8 @@
return;
}
+ // TODO(FMorschel): We should take into account if the target type contains
+ // a setter for the same name and stop the fix from being applied.
// We need the type for the extension.
var targetType = target.staticType;
if (targetType == null ||
@@ -223,6 +383,7 @@
nameGroupName: 'NAME',
parameterType: fieldType,
parameterTypeGroupName: 'TYPE',
+ methodBeingCopied: methodBeingCopied,
);
}
@@ -238,7 +399,13 @@
return;
}
- await _addNewExtension(builder, targetType, nameNode, writeSetter);
+ await _addNewExtension(
+ builder,
+ targetType,
+ nameNode,
+ writeSetter,
+ involvedTypes: [fieldType],
+ );
}
}
@@ -251,24 +418,50 @@
return CorrectionApplicability.singleLocation;
}
+ ExecutableElement2? get methodBeingCopied =>
+ _enclosingFunction?.declaredFragment?.element;
+
+ FunctionDeclaration? get _enclosingFunction => node.thisOrAncestorOfType();
+
+ /// Creates a change for creating a new extension on the given [targetType].
+ ///
+ /// The new extension should be added after the [nameNode].
+ ///
+ /// The [write] function is used to write the body of the new extension.
+ /// Meaning a method, getter, setter or operator.
+ ///
+ /// The [involvedTypes] are the types that are used in the new extension and
+ /// it's member. This is used to determine the type parameters of the new
+ /// extension.
Future<void> _addNewExtension(
ChangeBuilder builder,
DartType targetType,
- SimpleIdentifier nameNode,
- void Function(DartEditBuilder builder) write,
- ) async {
+ AstNode nameNode,
+ void Function(DartEditBuilder builder) write, {
+ List<DartType?> involvedTypes = const [],
+ }) async {
// The new extension should be added after it.
var enclosingUnitChild = nameNode.enclosingUnitChild;
if (enclosingUnitChild == null) {
return;
}
+ var extensionTypeParameters = [targetType, ...involvedTypes].typeParameters;
+
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(enclosingUnitChild.end, (builder) {
builder.writeln();
builder.writeln();
- builder.write('extension on ');
- builder.writeType(targetType);
+ builder.write('extension ');
+ if (extensionTypeParameters.isNotEmpty) {
+ builder.writeTypeParameters(
+ extensionTypeParameters,
+ methodBeingCopied: methodBeingCopied,
+ );
+ builder.write(' ');
+ }
+ builder.write('on ');
+ builder.writeType(targetType, methodBeingCopied: methodBeingCopied);
builder.writeln(' {');
builder.write(' ');
write(builder);
@@ -312,3 +505,50 @@
return true;
}
}
+
+extension on List<DartType?> {
+ /// Returns a list of type parameters that are used in the types.
+ ///
+ /// Iterates over every type in the list:
+ /// - If it is itself a [TypeParameterType], it is added to the list.
+ /// - If it is itself a [TypeParameterType] and it has a
+ /// [TypeParameterType.bound], we get the type parameters with this getter.
+ /// - If it is an [InterfaceType], we get the [InterfaceType.typeArguments]
+ /// it uses and get any type parameters they use by using this same getter.
+ ///
+ /// These types are added internally to a set so that we don't add duplicates.
+ List<TypeParameterElement2> get typeParameters =>
+ {
+ for (var type in whereType<TypeParameterType>()) ...[
+ type.element3,
+ ...[type.bound].typeParameters,
+ ],
+ for (var type in whereType<InterfaceType>())
+ ...type.typeArguments.typeParameters,
+ }.toList();
+}
+
+extension on Element2? {
+ bool get declaresIndex {
+ var element = this;
+ if (element is! InterfaceElement2) {
+ return false;
+ }
+ var inheritanceManager3 = InheritanceManager3();
+ var interface = inheritanceManager3.getInterface2(element);
+ var bracesGetter =
+ interface.map2[Name.forLibrary(
+ element.library2,
+ TokenType.INDEX.lexeme,
+ )];
+ if (bracesGetter != null) {
+ return true;
+ }
+ var bracesSetter =
+ interface.map2[Name.forLibrary(
+ element.library2,
+ TokenType.INDEX_EQ.lexeme,
+ )];
+ return bracesSetter != null;
+ }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 31bda72..05a3611 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -758,6 +758,11 @@
DartFixKindPriority.standard - 20,
"Create extension method '{0}'",
);
+ static const CREATE_EXTENSION_OPERATOR = FixKind(
+ 'dart.fix.create.extension.operator',
+ DartFixKindPriority.standard - 20,
+ "Create extension operator '{0}'",
+ );
static const CREATE_EXTENSION_SETTER = FixKind(
'dart.fix.create.extension.setter',
DartFixKindPriority.standard - 20,
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 71ddaa3..1b5ca0f 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -1038,14 +1038,18 @@
AddNullCheck.new,
ExtractLocalVariable.new,
ReplaceWithNullAware.single,
+ CreateExtensionMethod.new,
],
CompileTimeErrorCode.UNCHECKED_OPERATOR_INVOCATION_OF_NULLABLE_VALUE: [
AddNullCheck.new,
+ CreateExtensionOperator.new,
],
CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE: [
AddNullCheck.new,
ExtractLocalVariable.new,
ReplaceWithNullAware.single,
+ CreateExtensionGetter.new,
+ CreateExtensionSetter.new,
],
CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_AS_CONDITION: [
AddNullCheck.new,
@@ -1086,6 +1090,7 @@
],
CompileTimeErrorCode.UNDEFINED_EXTENSION_METHOD: [
ChangeTo.method,
+ CreateExtensionMethod.new,
CreateMethod.method,
],
CompileTimeErrorCode.UNDEFINED_EXTENSION_SETTER: [
@@ -1131,6 +1136,7 @@
ConvertFlutterChild.new,
ConvertFlutterChildren.new,
],
+ CompileTimeErrorCode.UNDEFINED_OPERATOR: [CreateExtensionOperator.new],
CompileTimeErrorCode.UNDEFINED_SETTER: [
ChangeTo.getterOrSetter,
CreateExtensionSetter.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 b53a26a..310e255 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(CreateExtensionOperatorTest);
defineReflectiveTests(CreateExtensionSetterTest);
});
}
@@ -21,6 +22,19 @@
@override
FixKind get kind => DartFixKind.CREATE_EXTENSION_GETTER;
+ @FailingTest(reason: 'Should not be a fix because it will conflict with a')
+ Future<void> test_conflicting_setter() async {
+ await resolveTestCode('''
+class A {
+ void set a(int value) {}
+}
+void f() {
+ A().a;
+}
+''');
+ await assertNoFix();
+ }
+
Future<void> test_contextType() async {
await resolveTestCode('''
void f() {
@@ -184,6 +198,40 @@
''');
}
+ Future<void> test_nullableTargetType() async {
+ await resolveTestCode('''
+void f(int? p) {
+ int _ = p.test;
+}
+''');
+ await assertHasFix('''
+void f(int? p) {
+ int _ = p.test;
+}
+
+extension on int? {
+ int get test => null;
+}
+''');
+ }
+
+ Future<void> test_parameterType() async {
+ await resolveTestCode('''
+void f<T>(T t) {
+ int _ = t.test;
+}
+''');
+ await assertHasFix('''
+void f<T>(T t) {
+ int _ = t.test;
+}
+
+extension <T> on T {
+ int get test => null;
+}
+''');
+ }
+
Future<void> test_parent_nothing() async {
await resolveTestCode('''
void f() {
@@ -343,6 +391,25 @@
''');
}
+ Future<void> test_existingExtension_generic_matching2() async {
+ await resolveTestCode('''
+void f(List<int> a) {
+ a.test();
+}
+
+extension E<T extends Iterable<int>> on T {}
+''');
+ await assertHasFix('''
+void f(List<int> a) {
+ a.test();
+}
+
+extension E<T extends Iterable<int>> on T {
+ void test() {}
+}
+''');
+ }
+
Future<void> test_existingExtension_generic_notMatching() async {
await resolveTestCode('''
void f(List<int> a) {
@@ -364,6 +431,27 @@
''');
}
+ Future<void> test_existingExtension_generic_notMatching2() async {
+ await resolveTestCode('''
+void f(List<int> a) {
+ a.test();
+}
+
+extension E<T extends Iterable<int>> on Iterable<T> {}
+''');
+ await assertHasFix('''
+void f(List<int> a) {
+ a.test();
+}
+
+extension on List<int> {
+ void test() {}
+}
+
+extension E<T extends Iterable<int>> on Iterable<T> {}
+''');
+ }
+
Future<void> test_existingExtension_hasMethod() async {
await resolveTestCode('''
void f() {
@@ -429,6 +517,66 @@
''');
}
+ Future<void> test_nullableTargetType() async {
+ await resolveTestCode('''
+void f(int? p) {
+ int _ = p.test();
+}
+''');
+ await assertHasFix('''
+void f(int? p) {
+ int _ = p.test();
+}
+
+extension on int? {
+ int test() {}
+}
+''');
+ }
+
+ Future<void> test_parameters() async {
+ await resolveTestCode('''
+void f() {
+ ''.test(1, '');
+}
+''');
+ await assertHasFix('''
+void f() {
+ ''.test(1, '');
+}
+
+extension on String {
+ void test(int i, String s) {}
+}
+''');
+ }
+
+ Future<void> test_parent_nothing() async {
+ await resolveTestCode('''
+void f() {
+ test();
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_parent_prefixedIdentifier() async {
+ await resolveTestCode('''
+void f(String a) {
+ a.test();
+}
+''');
+ await assertHasFix('''
+void f(String a) {
+ a.test();
+}
+
+extension on String {
+ void test() {}
+}
+''');
+ }
+
Future<void> test_target_identifier() async {
await resolveTestCode('''
void f(String a) {
@@ -472,6 +620,23 @@
await assertNoFix();
}
+ Future<void> test_targetType_functionType() async {
+ await resolveTestCode('''
+void f(void Function(int p) a) {
+ a.test();
+}
+''');
+ await assertHasFix('''
+void f(void Function(int p) a) {
+ a.test();
+}
+
+extension on void Function(int p) {
+ void test() {}
+}
+''');
+ }
+
Future<void> test_targetType_hasTypeArguments() async {
await resolveTestCode('''
void f(List<int> a) {
@@ -488,6 +653,351 @@
}
''');
}
+
+ Future<void> test_targetType_prefixed1() async {
+ await resolveTestCode('''
+import 'dart:math' as math;
+
+void f(math.Random a) {
+ a.test();
+}
+''');
+ await assertHasFix('''
+import 'dart:math' as math;
+
+void f(math.Random a) {
+ a.test();
+}
+
+extension on math.Random {
+ void test() {}
+}
+''');
+ }
+
+ Future<void> test_targetType_prefixed2() async {
+ await resolveTestCode('''
+import 'dart:math' as math;
+
+void f(List<math.Random> a) {
+ a.test();
+}
+''');
+ await assertHasFix('''
+import 'dart:math' as math;
+
+void f(List<math.Random> a) {
+ a.test();
+}
+
+extension on List<math.Random> {
+ void test() {}
+}
+''');
+ }
+
+ Future<void> test_targetType_record() async {
+ await resolveTestCode('''
+void f((int, String) a) {
+ a.test();
+}
+''');
+ await assertHasFix('''
+void f((int, String) a) {
+ a.test();
+}
+
+extension on (int, String) {
+ void test() {}
+}
+''');
+ }
+
+ Future<void> test_targetType_typeVariable1() async {
+ await resolveTestCode('''
+void f<T>(T a) {
+ a.test();
+}
+''');
+ await assertHasFix('''
+void f<T>(T a) {
+ a.test();
+}
+
+extension <T> on T {
+ void test() {}
+}
+''');
+ }
+
+ Future<void> test_targetType_typeVariable2() async {
+ await resolveTestCode('''
+void f<T>(List<T> a, T b) {
+ a.test(b);
+}
+''');
+ await assertHasFix('''
+void f<T>(List<T> a, T b) {
+ a.test(b);
+}
+
+extension <T> on List<T> {
+ void test(T b) {}
+}
+''');
+ }
+
+ Future<void> test_targetType_typeVariable_bound() async {
+ await resolveTestCode('''
+void f<T extends num>(T a) {
+ a.test();
+}
+''');
+ await assertHasFix('''
+void f<T extends num>(T a) {
+ a.test();
+}
+
+extension <T extends num> on T {
+ void test() {}
+}
+''');
+ }
+
+ Future<void> test_typeArgument_parameter() async {
+ await resolveTestCode('''
+void f<T>(T p) {
+ ''.test(p);
+}
+''');
+ await assertHasFix('''
+void f<T>(T p) {
+ ''.test(p);
+}
+
+extension on String {
+ void test<T>(T p) {}
+}
+''');
+ }
+
+ Future<void> test_typeArgument_return() async {
+ await resolveTestCode('''
+void f<T>() {
+ T _ = ''.test(0, 1.2);
+}
+''');
+ await assertHasFix('''
+void f<T>() {
+ T _ = ''.test(0, 1.2);
+}
+
+extension on String {
+ T test<T>(int i, double d) {}
+}
+''');
+ }
+
+ Future<void> test_typeArguments_bounded() async {
+ await resolveTestCode('''
+void f<T, R extends T>(R target) {
+ R _ = target.test();
+}
+''');
+ await assertHasFix('''
+void f<T, R extends T>(R target) {
+ R _ = target.test();
+}
+
+extension <R extends T, T> on R {
+ R test() {}
+}
+''');
+ }
+
+ Future<void> test_typeArguments_everywhere() async {
+ await resolveTestCode('''
+void f<R, T, T2>(T2 target, T value) {
+ R _ = target.test(value);
+}
+''');
+ await assertHasFix('''
+void f<R, T, T2>(T2 target, T value) {
+ R _ = target.test(value);
+}
+
+extension <T2> on T2 {
+ R test<R, T>(T value) {}
+}
+''');
+ }
+
+ Future<void> test_typeArguments_same() async {
+ await resolveTestCode('''
+void f<T>(T target) {
+ T _ = target.test();
+}
+''');
+ await assertHasFix('''
+void f<T>(T target) {
+ T _ = target.test();
+}
+
+extension <T> on T {
+ T test() {}
+}
+''');
+ }
+}
+
+@reflectiveTest
+class CreateExtensionOperatorTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.CREATE_EXTENSION_OPERATOR;
+
+ Future<void> test_binary() async {
+ await resolveTestCode('''
+void f() {
+ '' / 1;
+}
+''');
+ await assertHasFix('''
+void f() {
+ '' / 1;
+}
+
+extension on String {
+ void operator /(int other) {}
+}
+''');
+ }
+
+ Future<void> test_index_getter() async {
+ await resolveTestCode('''
+class A {
+ void operator []=(String index, bool newValue) {}
+}
+
+void f() {
+ A()[0];
+}
+''');
+ // Because A already has a []= operator, we don't add a new getter for it.
+ await assertNoFix();
+ }
+
+ Future<void> test_index_getter_ok() async {
+ await resolveTestCode('''
+void f() {
+ String _ = 0[false];
+}
+''');
+ await assertHasFix('''
+void f() {
+ String _ = 0[false];
+}
+
+extension on int {
+ String operator [](bool other) {}
+}
+''');
+ }
+
+ Future<void> test_index_setter() async {
+ await resolveTestCode('''
+void f() {
+ ''[0] = false;
+}
+''');
+ // Because String already has a [] operator, we don't add a new setter for
+ // it.
+ await assertNoFix();
+ }
+
+ Future<void> test_index_setter_ok() async {
+ await resolveTestCode('''
+void f() {
+ 0[''] = false;
+}
+''');
+ await assertHasFix('''
+void f() {
+ 0[''] = false;
+}
+
+extension on int {
+ void operator []=(String index, bool newValue) {}
+}
+''');
+ }
+
+ Future<void> test_nullableTargetType() async {
+ await resolveTestCode('''
+void f(int? p) {
+ p + 0;
+}
+''');
+ await assertHasFix('''
+void f(int? p) {
+ p + 0;
+}
+
+extension on int? {
+ void operator +(int other) {}
+}
+''');
+ }
+
+ Future<void> test_returnType() async {
+ await resolveTestCode('''
+void f() {
+ int _ = '' / 1;
+}
+''');
+ await assertHasFix('''
+void f() {
+ int _ = '' / 1;
+}
+
+extension on String {
+ int operator /(int other) {}
+}
+''');
+ }
+
+ Future<void> test_typeParameter() async {
+ await resolveTestCode('''
+void f<T>(T a) {
+ a / a;
+}
+''');
+ await assertHasFix('''
+void f<T>(T a) {
+ a / a;
+}
+
+extension <T> on T {
+ void operator /(T other) {}
+}
+''');
+ }
+
+ Future<void> test_unary() async {
+ await resolveTestCode('''
+void f() {
+ int _ = ~'';
+}
+''');
+ await assertHasFix('''
+void f() {
+ int _ = ~'';
+}
+
+extension on String {
+ int operator ~() {}
+}
+''');
+ }
}
@reflectiveTest
@@ -495,6 +1005,19 @@
@override
FixKind get kind => DartFixKind.CREATE_EXTENSION_SETTER;
+ @FailingTest(reason: 'Should not be a fix because it will conflict with a')
+ Future<void> test_conflicting_getter() async {
+ await resolveTestCode('''
+class A {
+ int get a() {}
+}
+void f() {
+ A().a = 0;
+}
+''');
+ await assertNoFix();
+ }
+
Future<void> test_existingExtension() async {
await resolveTestCode('''
void f() {
@@ -619,6 +1142,23 @@
''');
}
+ Future<void> test_nullableTargetType() async {
+ await resolveTestCode('''
+void f(int? p) {
+ p.test = 0;
+}
+''');
+ await assertHasFix('''
+void f(int? p) {
+ p.test = 0;
+}
+
+extension on int? {
+ set test(int test) {}
+}
+''');
+ }
+
Future<void> test_parent_nothing() async {
await resolveTestCode('''
void f() {
@@ -678,4 +1218,21 @@
}
''');
}
+
+ Future<void> test_typeParameter() async {
+ await resolveTestCode('''
+void f<T>(T a) {
+ a.test = 0;
+}
+''');
+ await assertHasFix('''
+void f<T>(T a) {
+ a.test = 0;
+}
+
+extension <T> on T {
+ set test(int test) {}
+}
+''');
+ }
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart
index 4a35456..869e4ea 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart
@@ -10,225 +10,12 @@
void main() {
defineReflectiveSuite(() {
- defineReflectiveTests(CreateExtensionGetterTest);
defineReflectiveTests(CreateGetterTest);
defineReflectiveTests(CreateGetterMixinTest);
});
}
@reflectiveTest
-class CreateExtensionGetterTest extends FixProcessorTest {
- @override
- FixKind get kind => DartFixKind.CREATE_EXTENSION_GETTER;
-
- Future<void> test_contextType() async {
- await resolveTestCode('''
-void f() {
- // ignore:unused_local_variable
- int v = ''.test;
-}
-''');
- await assertHasFix('''
-void f() {
- // ignore:unused_local_variable
- int v = ''.test;
-}
-
-extension on String {
- int get test => null;
-}
-''');
- }
-
- Future<void> test_contextType_no() async {
- await resolveTestCode('''
-void f() {
- ''.test;
-}
-''');
- await assertHasFix('''
-void f() {
- ''.test;
-}
-
-extension on String {
- get test => null;
-}
-''');
- assertLinkedGroup(change.linkedEditGroups[0], ['null']);
- }
-
- Future<void> test_existingExtension_contextType() async {
- await resolveTestCode('''
-void f() {
- // ignore:unused_local_variable
- int v = ''.test;
-}
-
-extension on String {}
-''');
- await assertHasFix('''
-void f() {
- // ignore:unused_local_variable
- int v = ''.test;
-}
-
-extension on String {
- int get test => null;
-}
-''');
- }
-
- Future<void> test_existingExtension_generic_matching() async {
- await resolveTestCode('''
-void f(List<int> a) {
- a.test;
-}
-
-extension E<T> on Iterable<T> {}
-''');
- await assertHasFix('''
-void f(List<int> a) {
- a.test;
-}
-
-extension E<T> on Iterable<T> {
- get test => null;
-}
-''');
- }
-
- Future<void> test_existingExtension_generic_notMatching() async {
- await resolveTestCode('''
-void f(List<int> a) {
- a.test;
-}
-
-extension E<K, V> on Map<K, V> {}
-''');
- await assertHasFix('''
-void f(List<int> a) {
- a.test;
-}
-
-extension on List<int> {
- get test => null;
-}
-
-extension E<K, V> on Map<K, V> {}
-''');
- }
-
- Future<void> test_existingExtension_hasMethod() async {
- await resolveTestCode('''
-void f() {
- ''.test;
-}
-
-extension E on String {
- // ignore:unused_element
- void foo() {}
-}
-''');
- await assertHasFix('''
-void f() {
- ''.test;
-}
-
-extension E on String {
- get test => null;
-
- // ignore:unused_element
- void foo() {}
-}
-''');
- }
-
- Future<void> test_existingExtension_notGeneric_matching() async {
- await resolveTestCode('''
-void f() {
- ''.test;
-}
-
-extension on String {}
-''');
- await assertHasFix('''
-void f() {
- ''.test;
-}
-
-extension on String {
- get test => null;
-}
-''');
- }
-
- Future<void> test_existingExtension_notGeneric_notMatching() async {
- await resolveTestCode('''
-void f() {
- ''.test;
-}
-
-extension on int {}
-''');
- await assertHasFix('''
-void f() {
- ''.test;
-}
-
-extension on String {
- get test => null;
-}
-
-extension on int {}
-''');
- }
-
- Future<void> test_parent_nothing() async {
- await resolveTestCode('''
-void f() {
- test;
-}
-''');
- await assertNoFix();
- }
-
- Future<void> test_parent_prefixedIdentifier() async {
- await resolveTestCode('''
-void f(String a) {
- a.test;
-}
-''');
- await assertHasFix('''
-void f(String a) {
- a.test;
-}
-
-extension on String {
- get test => null;
-}
-''');
- }
-
- Future<void> test_targetType_hasTypeArguments() async {
- await resolveTestCode('''
-void f(List<int> a) {
- a.test;
-}
-''');
- await assertHasFix('''
-void f(List<int> a) {
- a.test;
-}
-
-extension on List<int> {
- get test => null;
-}
-''');
- }
-}
-
-@reflectiveTest
class CreateGetterMixinTest extends FixProcessorTest {
@override
FixKind get kind => DartFixKind.CREATE_GETTER;
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
index 2884a41..0ed5248 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
@@ -680,7 +680,8 @@
@override
void writeParameterMatchingArgument(
- Expression argument, int index, Set<String> usedNames) {
+ Expression argument, int index, Set<String> usedNames,
+ {ExecutableElement2? methodBeingCopied}) {
// Append type name.
var type = argument.staticType;
if (type == null || type.isBottom || type.isDartCoreNull) {
@@ -690,7 +691,12 @@
type.nullabilitySuffix == NullabilitySuffix.none) {
write('required ');
}
- if (writeType(type, addSupertypeProposals: true, groupName: 'TYPE$index')) {
+ if (writeType(
+ type,
+ addSupertypeProposals: true,
+ groupName: 'TYPE$index',
+ methodBeingCopied: methodBeingCopied,
+ )) {
write(' ');
}
// Append parameter name.
@@ -707,7 +713,8 @@
}
@override
- void writeParametersMatchingArguments(ArgumentList argumentList) {
+ void writeParametersMatchingArguments(ArgumentList argumentList,
+ {ExecutableElement2? methodBeingCopied}) {
// TODO(brianwilkerson): Handle the case when there are required parameters
// after named parameters.
var usedNames = <String>{};
@@ -722,7 +729,8 @@
hasNamedParameters = true;
write('{');
}
- writeParameterMatchingArgument(argument, i, usedNames);
+ writeParameterMatchingArgument(argument, i, usedNames,
+ methodBeingCopied: methodBeingCopied);
}
if (hasNamedParameters) {
write('}');
@@ -738,12 +746,15 @@
}
@override
- void writeSetterDeclaration(String name,
- {void Function()? bodyWriter,
- bool isStatic = false,
- String? nameGroupName,
- DartType? parameterType,
- String? parameterTypeGroupName}) {
+ void writeSetterDeclaration(
+ String name, {
+ void Function()? bodyWriter,
+ bool isStatic = false,
+ String? nameGroupName,
+ DartType? parameterType,
+ String? parameterTypeGroupName,
+ ExecutableElement2? methodBeingCopied,
+ }) {
if (isStatic) {
write(Keyword.STATIC.lexeme);
write(' ');
@@ -757,7 +768,9 @@
}
write('(');
if (parameterType != null && parameterType is! DynamicType) {
- if (writeType(parameterType, groupName: parameterTypeGroupName)) {
+ if (writeType(parameterType,
+ groupName: parameterTypeGroupName,
+ methodBeingCopied: methodBeingCopied)) {
write(' ');
}
}
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
index 985c5e0..9107688 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
@@ -286,13 +286,20 @@
/// ensure that the name is unique (and the chosen name will be added to the
/// set).
void writeParameterMatchingArgument(
- Expression argument, int index, Set<String> usedNames);
+ Expression argument,
+ int index,
+ Set<String> usedNames, {
+ ExecutableElement2? methodBeingCopied,
+ });
/// Writes the code for a list of parameters that would match the given list
/// of [arguments].
///
/// The surrounding parentheses are *not* written.
- void writeParametersMatchingArguments(ArgumentList arguments);
+ void writeParametersMatchingArguments(
+ ArgumentList arguments, {
+ ExecutableElement2? methodBeingCopied,
+ });
/// Writes the code that references the [element].
///
@@ -310,12 +317,15 @@
/// [parameterType] is provided, then it will be used as the type of the
/// parameter. If a [parameterTypeGroupName] is provided, then if a parameter
/// type was written it will be in the linked edit group with that name.
- void writeSetterDeclaration(String name,
- {void Function()? bodyWriter,
- bool isStatic = false,
- String? nameGroupName,
- DartType? parameterType,
- String? parameterTypeGroupName});
+ void writeSetterDeclaration(
+ String name, {
+ void Function()? bodyWriter,
+ bool isStatic = false,
+ String? nameGroupName,
+ DartType? parameterType,
+ String? parameterTypeGroupName,
+ ExecutableElement2? methodBeingCopied,
+ });
/// Writes the code for a type annotation for the given [type].
///