| // Copyright (c) 2020, 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:analysis_server/src/services/correction/util.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.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 CreateConstructor extends CorrectionProducer { |
| /// The name of the constructor being created. |
| /// TODO(migration) We set this node when we have the change. |
| late String _constructorName; |
| |
| @override |
| List<Object> get fixArguments => [_constructorName]; |
| |
| @override |
| FixKind get fixKind => DartFixKind.CREATE_CONSTRUCTOR; |
| |
| @override |
| Future<void> compute(ChangeBuilder builder) async { |
| var node = this.node; |
| final argumentList = node.parent is ArgumentList ? node.parent : node; |
| if (argumentList is ArgumentList) { |
| var instanceCreation = argumentList.parent; |
| if (instanceCreation is InstanceCreationExpression) { |
| await _proposeFromInstanceCreation(builder, instanceCreation); |
| } |
| } else { |
| if (node is SimpleIdentifier) { |
| var parent = node.parent; |
| if (parent is ConstructorName) { |
| await _proposeFromConstructorName(builder, node, parent); |
| return; |
| } |
| } |
| var parent = node.thisOrAncestorOfType<EnumConstantDeclaration>(); |
| if (parent != null) { |
| await _proposeFromEnumConstantDeclaration(builder, parent.name, parent); |
| } |
| } |
| } |
| |
| Future<void> _proposeFromConstructorName(ChangeBuilder builder, |
| SimpleIdentifier name, ConstructorName constructorName) async { |
| InstanceCreationExpression? instanceCreation; |
| _constructorName = constructorName.toSource(); |
| if (constructorName.name == name) { |
| var grandParent = constructorName.parent; |
| // Type.name |
| if (grandParent is InstanceCreationExpression) { |
| instanceCreation = grandParent; |
| // new Type.name() |
| if (grandParent.constructorName != constructorName) { |
| return; |
| } |
| } |
| } |
| |
| // do we have enough information? |
| if (instanceCreation == null) { |
| return; |
| } |
| |
| // prepare target interface type |
| var targetType = constructorName.type.type; |
| if (targetType is! InterfaceType) { |
| return; |
| } |
| |
| // prepare target ClassDeclaration |
| var targetElement = targetType.element; |
| var targetResult = await sessionHelper.getElementDeclaration(targetElement); |
| if (targetResult == null) { |
| return; |
| } |
| var targetNode = targetResult.node; |
| if (targetNode is! ClassDeclaration) { |
| return; |
| } |
| |
| var targetUnit = targetResult.resolvedUnit; |
| if (targetUnit == null) { |
| return; |
| } |
| |
| // prepare location |
| var targetLocation = CorrectionUtils(targetUnit) |
| .prepareNewConstructorLocation(resolvedResult.session, targetNode); |
| if (targetLocation == null) { |
| return; |
| } |
| |
| await _write(builder, name, targetElement, targetLocation, |
| constructorName: name, argumentList: instanceCreation.argumentList); |
| } |
| |
| Future<void> _proposeFromEnumConstantDeclaration(ChangeBuilder builder, |
| SimpleIdentifier name, EnumConstantDeclaration parent) async { |
| var grandParent = parent.parent; |
| if (grandParent is! EnumDeclaration) { |
| return; |
| } |
| var targetElement = grandParent.declaredElement; |
| if (targetElement == null) { |
| return; |
| } |
| |
| // prepare target interface type |
| var targetResult = await sessionHelper.getElementDeclaration(targetElement); |
| if (targetResult == null) { |
| return; |
| } |
| |
| var targetNode = targetResult.node; |
| if (targetNode is! EnumDeclaration) { |
| return; |
| } |
| |
| var targetUnit = targetResult.resolvedUnit; |
| if (targetUnit == null) { |
| return; |
| } |
| |
| // prepare location |
| var targetLocation = CorrectionUtils(targetUnit) |
| .prepareEnumNewConstructorLocation(targetNode); |
| if (targetLocation == null) { |
| return; |
| } |
| |
| var arguments = parent.arguments; |
| _constructorName = |
| '${targetNode.name}${arguments?.constructorSelector ?? ''}'; |
| |
| await _write( |
| builder, |
| name, |
| targetElement, |
| targetLocation, |
| isConst: true, |
| constructorName: arguments?.constructorSelector?.name, |
| argumentList: arguments?.argumentList, |
| ); |
| } |
| |
| Future<void> _proposeFromInstanceCreation(ChangeBuilder builder, |
| InstanceCreationExpression instanceCreation) async { |
| var constructorName = instanceCreation.constructorName; |
| _constructorName = constructorName.toSource(); |
| // should be synthetic default constructor |
| var constructorElement = constructorName.staticElement; |
| if (constructorElement == null || |
| !constructorElement.isDefaultConstructor || |
| !constructorElement.isSynthetic) { |
| return; |
| } |
| |
| // prepare target ClassDeclaration |
| var targetElement = constructorElement.enclosingElement; |
| var targetResult = await sessionHelper.getElementDeclaration(targetElement); |
| if (targetResult == null) { |
| return; |
| } |
| var targetNode = targetResult.node; |
| if (targetNode is! ClassDeclaration) { |
| return; |
| } |
| |
| var targetUnit = targetResult.resolvedUnit; |
| if (targetUnit == null) { |
| return; |
| } |
| |
| // prepare location |
| var targetLocation = CorrectionUtils(targetUnit) |
| .prepareNewConstructorLocation(resolvedResult.session, targetNode); |
| if (targetLocation == null) { |
| return; |
| } |
| |
| var targetSource = targetElement.source; |
| var targetFile = targetSource.fullName; |
| await builder.addDartFileEdit(targetFile, (builder) { |
| builder.addInsertion(targetLocation.offset, (builder) { |
| builder.write(targetLocation.prefix); |
| builder.writeConstructorDeclaration(targetElement.name, |
| argumentList: instanceCreation.argumentList); |
| builder.write(targetLocation.suffix); |
| }); |
| }); |
| } |
| |
| Future<void> _write( |
| ChangeBuilder builder, |
| SimpleIdentifier name, |
| ClassElement targetElement, |
| InsertionLocation targetLocation, { |
| SimpleIdentifier? constructorName, |
| bool isConst = false, |
| ArgumentList? argumentList, |
| }) async { |
| var targetFile = targetElement.source.fullName; |
| await builder.addDartFileEdit(targetFile, (builder) { |
| builder.addInsertion(targetLocation.offset, (builder) { |
| builder.write(targetLocation.prefix); |
| builder.writeConstructorDeclaration(targetElement.name, |
| isConst: isConst, |
| argumentList: argumentList, |
| constructorName: constructorName, |
| constructorNameGroupName: 'NAME'); |
| builder.write(targetLocation.suffix); |
| }); |
| if (targetFile == file) { |
| builder.addLinkedPosition(range.node(name), 'NAME'); |
| } |
| }); |
| } |
| } |