55import 'package:analysis_server/src/services/correction/fix.dart' ;
66import 'package:analysis_server/src/services/correction/util.dart' ;
77import 'package:analysis_server_plugin/edit/dart/correction_producer.dart' ;
8+ import 'package:analyzer/dart/ast/token.dart' ;
9+ import 'package:analyzer/dart/element/element2.dart' ;
810import 'package:analyzer/dart/element/type.dart' ;
911import 'package:analyzer/src/dart/ast/ast.dart' ;
12+ import 'package:analyzer/src/dart/element/inheritance_manager3.dart' ;
1013import 'package:analyzer/src/dart/element/type.dart' ;
1114import 'package:analyzer/src/dart/resolver/applicable_extensions.dart' ;
1215import 'package:analyzer/src/utilities/extensions/ast.dart' ;
1316import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart' ;
1417import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart' ;
1518import 'package:analyzer_plugin/utilities/fixes/fixes.dart' ;
19+ import 'package:collection/collection.dart' ;
1620
1721class CreateExtensionGetter extends _CreateExtensionMember {
1822 String _getterName = '' ;
@@ -53,6 +57,8 @@ class CreateExtensionGetter extends _CreateExtensionMember {
5357 return ;
5458 }
5559
60+ // TODO(FMorschel): We should take into account if the target type contains
61+ // a setter for the same name and stop the fix from being applied.
5662 // We need the type for the extension.
5763 var targetType = target.staticType;
5864 if (targetType == null ||
@@ -67,7 +73,7 @@ class CreateExtensionGetter extends _CreateExtensionMember {
6773
6874 void writeGetter (DartEditBuilder builder) {
6975 if (fieldType != null ) {
70- builder.writeType (fieldType);
76+ builder.writeType (fieldType, methodBeingCopied : methodBeingCopied );
7177 builder.write (' ' );
7278 }
7379 builder.write ('get $_getterName => ' );
@@ -89,7 +95,13 @@ class CreateExtensionGetter extends _CreateExtensionMember {
8995 return ;
9096 }
9197
92- await _addNewExtension (builder, targetType, nameNode, writeGetter);
98+ await _addNewExtension (
99+ builder,
100+ targetType,
101+ nameNode,
102+ writeGetter,
103+ involvedTypes: [fieldType],
104+ );
93105 }
94106}
95107
@@ -137,16 +149,32 @@ class CreateExtensionMethod extends _CreateExtensionMember {
137149 var returnType = inferUndefinedExpressionType (invocation);
138150
139151 void writeMethod (DartEditBuilder builder) {
140- if (builder.writeType (returnType, groupName: 'RETURN_TYPE' )) {
152+ if (builder.writeType (
153+ returnType,
154+ groupName: 'RETURN_TYPE' ,
155+ methodBeingCopied: methodBeingCopied,
156+ )) {
141157 builder.write (' ' );
142158 }
143159
144160 builder.addLinkedEdit ('NAME' , (builder) {
145161 builder.write (_methodName);
146162 });
147163
164+ builder.writeTypeParameters (
165+ [
166+ returnType,
167+ ...invocation.argumentList.arguments.map ((e) => e.staticType),
168+ ].typeParameters
169+ .whereNot ([targetType].typeParameters.contains)
170+ .toList (),
171+ );
172+
148173 builder.write ('(' );
149- builder.writeParametersMatchingArguments (invocation.argumentList);
174+ builder.writeParametersMatchingArguments (
175+ invocation.argumentList,
176+ methodBeingCopied: methodBeingCopied,
177+ );
150178 builder.write (') {}' );
151179 }
152180
@@ -166,6 +194,136 @@ class CreateExtensionMethod extends _CreateExtensionMember {
166194 }
167195}
168196
197+ class CreateExtensionOperator extends _CreateExtensionMember {
198+ String _operator = '' ;
199+
200+ CreateExtensionOperator ({required super .context});
201+
202+ @override
203+ List <String >? get fixArguments => [_operator];
204+
205+ @override
206+ FixKind get fixKind => DartFixKind .CREATE_EXTENSION_OPERATOR ;
207+
208+ @override
209+ Future <void > compute (ChangeBuilder builder) async {
210+ bool indexSetter = false ;
211+ bool innerParameter = true ;
212+ var node = this .node;
213+ if (node is ! Expression ) {
214+ return ;
215+ }
216+
217+ Expression ? target;
218+ DartType ? parameterType;
219+ DartType ? assigningType;
220+
221+ switch (node) {
222+ case IndexExpression (: var parent):
223+ if (node.target? .staticType? .element3.declaresIndex ?? true ) {
224+ return ;
225+ }
226+ target = node.target;
227+ _operator = TokenType .INDEX .lexeme;
228+ parameterType = node.index.staticType;
229+ if (parameterType == null ) {
230+ return ;
231+ }
232+ if (parent case AssignmentExpression (
233+ : var leftHandSide,
234+ : var rightHandSide,
235+ ) when leftHandSide == node) {
236+ assigningType = rightHandSide.staticType;
237+ indexSetter = true ;
238+ _operator = TokenType .INDEX_EQ .lexeme;
239+ }
240+ case BinaryExpression ():
241+ target = node.leftOperand;
242+ _operator = node.operator .lexeme;
243+ parameterType = node.rightOperand.staticType;
244+ if (parameterType == null ) {
245+ return ;
246+ }
247+ case PrefixExpression ():
248+ target = node.operand;
249+ _operator = node.operator .lexeme;
250+ innerParameter = false ;
251+ }
252+
253+ if (target == null ) {
254+ return ;
255+ }
256+
257+ // We need the type for the extension.
258+ var targetType = target.staticType;
259+ if (targetType == null ||
260+ targetType is DynamicType ||
261+ targetType is InvalidType ) {
262+ return ;
263+ }
264+
265+ DartType returnType;
266+ // If this is an index setter, the return type must be void.
267+ if (indexSetter) {
268+ returnType = VoidTypeImpl .instance;
269+ } else {
270+ // Try to find the return type.
271+ returnType = inferUndefinedExpressionType (node) ?? VoidTypeImpl .instance;
272+ }
273+
274+ void writeMethod (DartEditBuilder builder) {
275+ if (builder.writeType (
276+ returnType,
277+ groupName: 'RETURN_TYPE' ,
278+ methodBeingCopied: methodBeingCopied,
279+ )) {
280+ builder.write (' ' );
281+ }
282+
283+ builder.write ('operator ' );
284+ builder.write (_operator);
285+
286+ builder.write ('(' );
287+ if (innerParameter) {
288+ builder.writeFormalParameter (
289+ indexSetter ? 'index' : 'other' ,
290+ type: parameterType,
291+ methodBeingCopied: methodBeingCopied,
292+ );
293+ }
294+ if (indexSetter) {
295+ builder.write (', ' );
296+ builder.writeFormalParameter (
297+ 'newValue' ,
298+ type: assigningType,
299+ methodBeingCopied: methodBeingCopied,
300+ );
301+ }
302+ builder.write (') {}' );
303+ }
304+
305+ var updatedExisting = await _updateExistingExtension (builder, targetType, (
306+ extension ,
307+ builder,
308+ ) {
309+ builder.insertMethod (extension , (builder) {
310+ writeMethod (builder);
311+ });
312+ });
313+ if (updatedExisting) {
314+ return ;
315+ }
316+
317+ await _addNewExtension (
318+ builder,
319+ targetType,
320+ target,
321+ writeMethod,
322+ involvedTypes: [parameterType, returnType],
323+ );
324+ }
325+ }
326+
169327class CreateExtensionSetter extends _CreateExtensionMember {
170328 String _setterName = '' ;
171329
@@ -205,6 +363,8 @@ class CreateExtensionSetter extends _CreateExtensionMember {
205363 return ;
206364 }
207365
366+ // TODO(FMorschel): We should take into account if the target type contains
367+ // a setter for the same name and stop the fix from being applied.
208368 // We need the type for the extension.
209369 var targetType = target.staticType;
210370 if (targetType == null ||
@@ -223,6 +383,7 @@ class CreateExtensionSetter extends _CreateExtensionMember {
223383 nameGroupName: 'NAME' ,
224384 parameterType: fieldType,
225385 parameterTypeGroupName: 'TYPE' ,
386+ methodBeingCopied: methodBeingCopied,
226387 );
227388 }
228389
@@ -238,7 +399,13 @@ class CreateExtensionSetter extends _CreateExtensionMember {
238399 return ;
239400 }
240401
241- await _addNewExtension (builder, targetType, nameNode, writeSetter);
402+ await _addNewExtension (
403+ builder,
404+ targetType,
405+ nameNode,
406+ writeSetter,
407+ involvedTypes: [fieldType],
408+ );
242409 }
243410}
244411
@@ -251,24 +418,50 @@ abstract class _CreateExtensionMember extends ResolvedCorrectionProducer {
251418 return CorrectionApplicability .singleLocation;
252419 }
253420
421+ ExecutableElement2 ? get methodBeingCopied =>
422+ _enclosingFunction? .declaredFragment? .element;
423+
424+ FunctionDeclaration ? get _enclosingFunction => node.thisOrAncestorOfType ();
425+
426+ /// Creates a change for creating a new extension on the given [targetType] .
427+ ///
428+ /// The new extension should be added after the [nameNode] .
429+ ///
430+ /// The [write] function is used to write the body of the new extension.
431+ /// Meaning a method, getter, setter or operator.
432+ ///
433+ /// The [involvedTypes] are the types that are used in the new extension and
434+ /// it's member. This is used to determine the type parameters of the new
435+ /// extension.
254436 Future <void > _addNewExtension (
255437 ChangeBuilder builder,
256438 DartType targetType,
257- SimpleIdentifier nameNode,
258- void Function (DartEditBuilder builder) write,
259- ) async {
439+ AstNode nameNode,
440+ void Function (DartEditBuilder builder) write, {
441+ List <DartType ?> involvedTypes = const [],
442+ }) async {
260443 // The new extension should be added after it.
261444 var enclosingUnitChild = nameNode.enclosingUnitChild;
262445 if (enclosingUnitChild == null ) {
263446 return ;
264447 }
265448
449+ var extensionTypeParameters = [targetType, ...involvedTypes].typeParameters;
450+
266451 await builder.addDartFileEdit (file, (builder) {
267452 builder.addInsertion (enclosingUnitChild.end, (builder) {
268453 builder.writeln ();
269454 builder.writeln ();
270- builder.write ('extension on ' );
271- builder.writeType (targetType);
455+ builder.write ('extension ' );
456+ if (extensionTypeParameters.isNotEmpty) {
457+ builder.writeTypeParameters (
458+ extensionTypeParameters,
459+ methodBeingCopied: methodBeingCopied,
460+ );
461+ builder.write (' ' );
462+ }
463+ builder.write ('on ' );
464+ builder.writeType (targetType, methodBeingCopied: methodBeingCopied);
272465 builder.writeln (' {' );
273466 builder.write (' ' );
274467 write (builder);
@@ -312,3 +505,50 @@ abstract class _CreateExtensionMember extends ResolvedCorrectionProducer {
312505 return true ;
313506 }
314507}
508+
509+ extension on List <DartType ?> {
510+ /// Returns a list of type parameters that are used in the types.
511+ ///
512+ /// Iterates over every type in the list:
513+ /// - If it is itself a [TypeParameterType] , it is added to the list.
514+ /// - If it is itself a [TypeParameterType] and it has a
515+ /// [TypeParameterType.bound] , we get the type parameters with this getter.
516+ /// - If it is an [InterfaceType] , we get the [InterfaceType.typeArguments]
517+ /// it uses and get any type parameters they use by using this same getter.
518+ ///
519+ /// These types are added internally to a set so that we don't add duplicates.
520+ List <TypeParameterElement2 > get typeParameters =>
521+ {
522+ for (var type in whereType <TypeParameterType >()) ...[
523+ type.element3,
524+ ...[type.bound].typeParameters,
525+ ],
526+ for (var type in whereType <InterfaceType >())
527+ ...type.typeArguments.typeParameters,
528+ }.toList ();
529+ }
530+
531+ extension on Element2 ? {
532+ bool get declaresIndex {
533+ var element = this ;
534+ if (element is ! InterfaceElement2 ) {
535+ return false ;
536+ }
537+ var inheritanceManager3 = InheritanceManager3 ();
538+ var interface = inheritanceManager3.getInterface2 (element);
539+ var bracesGetter =
540+ interface .map2[Name .forLibrary (
541+ element.library2,
542+ TokenType .INDEX .lexeme,
543+ )];
544+ if (bracesGetter != null ) {
545+ return true ;
546+ }
547+ var bracesSetter =
548+ interface .map2[Name .forLibrary (
549+ element.library2,
550+ TokenType .INDEX_EQ .lexeme,
551+ )];
552+ return bracesSetter != null ;
553+ }
554+ }
0 commit comments