|
| 1 | +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| 2 | +// for details. All rights reserved. Use of this source code is governed by a |
| 3 | +// BSD-style license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +import 'package:analysis_server/src/services/correction/fix.dart'; |
| 6 | +import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; |
| 7 | +import 'package:analyzer/dart/analysis/results.dart'; |
| 8 | +import 'package:analyzer/dart/ast/token.dart'; |
| 9 | +import 'package:analyzer/dart/element/element.dart'; |
| 10 | +import 'package:analyzer/dart/element/type.dart'; |
| 11 | +import 'package:analyzer/src/dart/ast/ast.dart'; |
| 12 | +import 'package:analyzer/src/dart/element/type.dart'; |
| 13 | +import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; |
| 14 | +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; |
| 15 | + |
| 16 | +class CreateOperator extends ResolvedCorrectionProducer { |
| 17 | + String _operator = ''; |
| 18 | + |
| 19 | + CreateOperator({required super.context}); |
| 20 | + |
| 21 | + @override |
| 22 | + CorrectionApplicability get applicability { |
| 23 | + // Not predictably the correct action. |
| 24 | + return CorrectionApplicability.singleLocation; |
| 25 | + } |
| 26 | + |
| 27 | + @override |
| 28 | + List<String>? get fixArguments => [_operator]; |
| 29 | + |
| 30 | + @override |
| 31 | + FixKind get fixKind => DartFixKind.CREATE_OPERATOR; |
| 32 | + |
| 33 | + @override |
| 34 | + Future<void> compute(ChangeBuilder builder) async { |
| 35 | + bool indexSetter = false; |
| 36 | + bool innerParameter = true; |
| 37 | + var node = this.node; |
| 38 | + if (node is! Expression) { |
| 39 | + return; |
| 40 | + } |
| 41 | + |
| 42 | + Expression? target; |
| 43 | + DartType? parameterType; |
| 44 | + Fragment? targetFragment; |
| 45 | + DartType? assigningType; |
| 46 | + CompilationUnitMember? targetNode; |
| 47 | + |
| 48 | + switch (node) { |
| 49 | + case IndexExpression(:var parent): |
| 50 | + target = node.target; |
| 51 | + _operator = TokenType.INDEX.lexeme; |
| 52 | + parameterType = node.index.staticType; |
| 53 | + if (parameterType == null) { |
| 54 | + return; |
| 55 | + } |
| 56 | + if (parent case AssignmentExpression( |
| 57 | + :var leftHandSide, |
| 58 | + :var rightHandSide, |
| 59 | + ) when leftHandSide == node) { |
| 60 | + assigningType = rightHandSide.staticType; |
| 61 | + indexSetter = true; |
| 62 | + _operator = TokenType.INDEX_EQ.lexeme; |
| 63 | + } |
| 64 | + case BinaryExpression(): |
| 65 | + target = node.leftOperand; |
| 66 | + _operator = node.operator.lexeme; |
| 67 | + parameterType = node.rightOperand.staticType; |
| 68 | + if (parameterType == null) { |
| 69 | + return; |
| 70 | + } |
| 71 | + case PrefixExpression(): |
| 72 | + target = node.operand; |
| 73 | + _operator = node.operator.lexeme; |
| 74 | + innerParameter = false; |
| 75 | + } |
| 76 | + |
| 77 | + if (target == null) { |
| 78 | + return; |
| 79 | + } |
| 80 | + |
| 81 | + // We need the type for the extension. |
| 82 | + var targetType = target.staticType; |
| 83 | + if (targetType == null || |
| 84 | + targetType is DynamicType || |
| 85 | + targetType is InvalidType) { |
| 86 | + return; |
| 87 | + } |
| 88 | + |
| 89 | + DartType returnType; |
| 90 | + // If this is an index setter, the return type must be void. |
| 91 | + if (indexSetter) { |
| 92 | + returnType = VoidTypeImpl.instance; |
| 93 | + } else { |
| 94 | + // Try to find the return type. |
| 95 | + returnType = inferUndefinedExpressionType(node) ?? VoidTypeImpl.instance; |
| 96 | + } |
| 97 | + |
| 98 | + var targetClassElement = getTargetInterfaceElement(target); |
| 99 | + if (targetClassElement == null) { |
| 100 | + return; |
| 101 | + } |
| 102 | + targetFragment = targetClassElement.firstFragment; |
| 103 | + if (targetClassElement.library2.isInSdk) { |
| 104 | + return; |
| 105 | + } |
| 106 | + // Prepare target ClassDeclaration. |
| 107 | + if (targetClassElement is MixinElement) { |
| 108 | + var fragment = targetClassElement.firstFragment; |
| 109 | + targetNode = await getMixinDeclaration(fragment); |
| 110 | + } else if (targetClassElement is ClassElement) { |
| 111 | + var fragment = targetClassElement.firstFragment; |
| 112 | + targetNode = await getClassDeclaration(fragment); |
| 113 | + } else if (targetClassElement is ExtensionTypeElement) { |
| 114 | + var fragment = targetClassElement.firstFragment; |
| 115 | + targetNode = await getExtensionTypeDeclaration(fragment); |
| 116 | + } else if (targetClassElement is EnumElement) { |
| 117 | + var fragment = targetClassElement.firstFragment; |
| 118 | + targetNode = await getEnumDeclaration(fragment); |
| 119 | + } |
| 120 | + if (targetNode == null) { |
| 121 | + return; |
| 122 | + } |
| 123 | + // Use different utils. |
| 124 | + var targetPath = targetFragment.libraryFragment!.source.fullName; |
| 125 | + var targetResolveResult = await unitResult.session.getResolvedUnit( |
| 126 | + targetPath, |
| 127 | + ); |
| 128 | + if (targetResolveResult is! ResolvedUnitResult) { |
| 129 | + return; |
| 130 | + } |
| 131 | + var targetSource = targetFragment.libraryFragment!.source; |
| 132 | + var targetFile = targetSource.fullName; |
| 133 | + |
| 134 | + var writeReturnType = |
| 135 | + getCodeStyleOptions(unitResult.file).specifyReturnTypes; |
| 136 | + |
| 137 | + if (returnType is TypeParameterType) { |
| 138 | + returnType = returnType.bound; |
| 139 | + } |
| 140 | + |
| 141 | + await builder.addDartFileEdit(targetFile, (builder) { |
| 142 | + if (targetNode == null) { |
| 143 | + return; |
| 144 | + } |
| 145 | + builder.insertMethod(targetNode, (builder) { |
| 146 | + // Append return type. |
| 147 | + builder.writeType(returnType, shouldWriteDynamic: writeReturnType); |
| 148 | + if ((returnType is! DynamicType && returnType is! InvalidType) || |
| 149 | + writeReturnType) { |
| 150 | + builder.write(' '); |
| 151 | + } |
| 152 | + builder.write('operator '); |
| 153 | + builder.write(_operator); |
| 154 | + // Append parameters. |
| 155 | + if (innerParameter) { |
| 156 | + builder.write('('); |
| 157 | + builder.writeParameter('other', type: parameterType); |
| 158 | + if (assigningType != null) { |
| 159 | + builder.write(', '); |
| 160 | + builder.writeParameter('value', type: assigningType); |
| 161 | + } |
| 162 | + builder.write(')'); |
| 163 | + } else { |
| 164 | + builder.write('()'); |
| 165 | + } |
| 166 | + if (returnType.isDartAsyncFuture) { |
| 167 | + builder.write(' async'); |
| 168 | + } |
| 169 | + builder.write(' {}'); |
| 170 | + }); |
| 171 | + }); |
| 172 | + } |
| 173 | +} |
0 commit comments