| // Copyright (c) 2022, 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/assist.dart'; |
| 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:analysis_server/src/utilities/extensions/range_factory.dart'; |
| import 'package:analyzer/dart/analysis/features.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/constant/value.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer_plugin/utilities/assist/assist.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:analyzer_plugin/utilities/range_factory.dart'; |
| import 'package:collection/collection.dart'; |
| |
| class ConvertClassToEnum extends CorrectionProducer { |
| @override |
| AssistKind get assistKind => DartAssistKind.CONVERT_CLASS_TO_ENUM; |
| |
| @override |
| bool get canBeAppliedInBulk => true; |
| |
| @override |
| bool get canBeAppliedToFile => true; |
| |
| @override |
| FixKind get fixKind => DartFixKind.CONVERT_CLASS_TO_ENUM; |
| |
| @override |
| FixKind get multiFixKind => DartFixKind.CONVERT_CLASS_TO_ENUM_MULTI; |
| |
| @override |
| Future<void> compute(ChangeBuilder builder) async { |
| if (!libraryElement.featureSet.isEnabled(Feature.enhanced_enums)) { |
| // If the library doesn't support enhanced_enums then the class can't be |
| // converted. |
| return; |
| } |
| if (libraryElement.units.length > 1) { |
| // If the library has any part files, then the class can't be converted |
| // because we don't currently have a performant way to access the ASTs for |
| // the parts to check for invocations of the constructors or subclasses of |
| // the class. |
| return; |
| } |
| var node = this.node; |
| if (node is! SimpleIdentifier) { |
| return; |
| } |
| var parent = node.parent; |
| if (parent is ClassDeclaration && parent.name == node) { |
| var description = _EnumDescription.fromClass(parent); |
| if (description != null) { |
| await builder.addDartFileEdit(file, (builder) { |
| description.applyChanges(builder, utils); |
| }); |
| } |
| } |
| } |
| } |
| |
| /// A superclass for the [_EnumVisitor] and [_NonEnumVisitor]. |
| class _BaseVisitor extends RecursiveAstVisitor<void> { |
| /// The element representing the enum declaration that's being visited. |
| final ClassElement classElement; |
| |
| _BaseVisitor(this.classElement); |
| |
| /// Return `true` if the given [node] is an invocation of a generative |
| /// constructor from the class being converted. |
| bool invokesGenerativeConstructor(InstanceCreationExpression node) { |
| var constructorElement = node.constructorName.staticElement; |
| return constructorElement != null && |
| !constructorElement.isFactory && |
| constructorElement.enclosingElement == classElement; |
| } |
| } |
| |
| /// An exception thrown by the visitors if a condition is found that prevents |
| /// the class from being converted. |
| class _CannotConvertException implements Exception { |
| final String message; |
| |
| _CannotConvertException(this.message); |
| } |
| |
| /// A representation of a static field in the class being converted that will be |
| /// replaced by an enum constant. |
| class _ConstantField extends _Field { |
| /// The element representing the constructor used to initialize the field. |
| ConstructorElement constructorElement; |
| |
| /// The invocation of the constructor. |
| final InstanceCreationExpression instanceCreation; |
| |
| /// The value of the index field. |
| final int indexValue; |
| |
| _ConstantField( |
| super.element, |
| super.declaration, |
| super.declarationList, |
| super.fieldDeclaration, |
| this.instanceCreation, |
| this.constructorElement, |
| this.indexValue); |
| } |
| |
| /// Information about a single constructor in the class being converted. |
| class _Constructor { |
| /// The declaration of the constructor. |
| final ConstructorDeclaration declaration; |
| |
| /// The element representing the constructor. |
| final ConstructorElement element; |
| |
| _Constructor(this.declaration, this.element); |
| } |
| |
| /// Information about the constructors in the class being converted. |
| class _Constructors { |
| /// A map from elements to constructors. |
| final Map<ConstructorElement, _Constructor> byElement = {}; |
| |
| _Constructors(); |
| |
| /// Return the constructors in this collection. |
| Iterable<_Constructor> get constructors => byElement.values; |
| |
| /// Add the given [constructor] to this collection. |
| void add(_Constructor constructor) { |
| byElement[constructor.element] = constructor; |
| } |
| |
| /// Return the constructor with the given [element]. |
| _Constructor? forElement(ConstructorElement element) { |
| return byElement[element]; |
| } |
| } |
| |
| /// A description of how to convert the class to an enum. |
| class _EnumDescription { |
| /// The class declaration being converted. |
| final ClassDeclaration classDeclaration; |
| |
| /// A map from constructor declarations to information about the parameter |
| /// corresponding to the index field. The map is `null` if there is no index |
| /// field. |
| final Map<_Constructor, _Parameter>? constructorMap; |
| |
| /// A list of the declarations to be converted into enum constants. |
| final _Fields fields; |
| |
| /// A list of the indexes of members that need to be deleted. |
| final List<int> membersToDelete; |
| |
| _EnumDescription({ |
| required this.classDeclaration, |
| required this.constructorMap, |
| required this.fields, |
| required this.membersToDelete, |
| }); |
| |
| /// Return the offset immediately following the opening brace for the class |
| /// body. |
| int get bodyOffset => classDeclaration.leftBracket.end; |
| |
| /// Use the [builder] and correction [utils] to apply the change necessary to |
| /// convert the class to an enum. |
| void applyChanges(DartFileEditBuilder builder, CorrectionUtils utils) { |
| // Replace the keyword. |
| builder.addSimpleReplacement( |
| range.token(classDeclaration.classKeyword), 'enum'); |
| |
| // Remove the extends clause if there is one. |
| final extendsClause = classDeclaration.extendsClause; |
| if (extendsClause != null) { |
| var followingToken = extendsClause.endToken.next!; |
| builder.addDeletion(range.startStart(extendsClause, followingToken)); |
| } |
| |
| // Compute the declarations of the enum constants and delete the fields |
| // being converted. |
| var members = classDeclaration.members; |
| var indent = utils.getIndent(1); |
| var eol = utils.endOfLine; |
| var constantsBuffer = StringBuffer(); |
| var fieldsToConvert = fields.fieldsToConvert; |
| fieldsToConvert |
| .sort((first, second) => first.indexValue.compareTo(second.indexValue)); |
| for (var field in fieldsToConvert) { |
| // Compute the declaration of the corresponding enum constant. |
| if (constantsBuffer.isNotEmpty) { |
| constantsBuffer.write(',$eol$indent'); |
| } |
| constantsBuffer.write(field.name); |
| var invocation = field.instanceCreation; |
| var constructorNameNode = invocation.constructorName; |
| var invokedConstructorElement = field.constructorElement; |
| var invokedConstructor = constructorMap?.keys.firstWhere( |
| (constructor) => constructor.element == invokedConstructorElement); |
| var parameterData = constructorMap?[invokedConstructor]; |
| var typeArguments = constructorNameNode.type.typeArguments; |
| if (typeArguments != null) { |
| constantsBuffer.write(utils.getNodeText(typeArguments)); |
| } |
| var constructorName = constructorNameNode.name?.name; |
| if (constructorName != null) { |
| constantsBuffer.write('.$constructorName'); |
| } |
| var argumentList = invocation.argumentList; |
| var arguments = argumentList.arguments; |
| var argumentCount = arguments.length - (parameterData == null ? 0 : 1); |
| if (argumentCount == 0) { |
| if (typeArguments != null || constructorName != null) { |
| constantsBuffer.write('()'); |
| } |
| } else if (parameterData == null) { |
| constantsBuffer.write(utils.getNodeText(argumentList)); |
| } else { |
| constantsBuffer.write('('); |
| var index = parameterData.index; |
| var last = arguments.length - 1; |
| if (index == 0) { |
| var offset = arguments[1].offset; |
| var length = arguments[last].end - offset; |
| constantsBuffer.write(utils.getText(offset, length)); |
| } else if (index == last) { |
| var offset = arguments[0].offset; |
| int length; |
| if (arguments[last].endToken.next?.type == TokenType.COMMA) { |
| length = arguments[last].offset - offset; |
| } else { |
| length = arguments[last - 1].end - offset; |
| } |
| constantsBuffer.write(utils.getText(offset, length)); |
| } else { |
| var offset = arguments[0].offset; |
| var length = arguments[index].offset - offset; |
| constantsBuffer.write(utils.getText(offset, length)); |
| |
| offset = arguments[index + 1].offset; |
| length = argumentList.endToken.offset - offset; |
| constantsBuffer.write(utils.getText(offset, length)); |
| } |
| constantsBuffer.write(')'); |
| } |
| |
| // Delete the static field that was converted to an enum constant. |
| _deleteField(builder, field, members); |
| } |
| |
| // Remove the index field. |
| var indexField = fields.indexField; |
| if (indexField != null) { |
| _deleteField(builder, indexField, members); |
| } |
| |
| // Update the constructors. |
| var removedConstructor = _removeUnnamedConstructor(); |
| _transformConstructors(builder, removedConstructor); |
| |
| // Special case replacing all of the members. |
| if (membersToDelete.length == members.length) { |
| builder.addSimpleReplacement(range.startEnd(members.first, members.last), |
| constantsBuffer.toString()); |
| return; |
| } |
| |
| // Insert the declarations of the enum constants. |
| var semicolon = ';'; |
| var prefix = '$eol$indent'; |
| var suffix = '$semicolon$eol'; |
| builder.addSimpleInsertion(bodyOffset, '$prefix$constantsBuffer$suffix'); |
| |
| // Delete any members that are no longer needed. |
| membersToDelete.sort(); |
| for (var range in range.nodesInList(members, membersToDelete)) { |
| builder.addDeletion(range); |
| } |
| } |
| |
| /// Use the [builder] to delete the [field]. |
| void _deleteField(DartFileEditBuilder builder, _Field field, |
| NodeList<ClassMember> members) { |
| var variableList = field.declarationList; |
| if (variableList.variables.length == 1) { |
| membersToDelete.add(members.indexOf(field.fieldDeclaration)); |
| } else { |
| builder.addDeletion( |
| range.nodeInList(variableList.variables, field.declaration)); |
| } |
| } |
| |
| /// If the unnamed constructor is the only constructor, and if it has no |
| /// parameters other than potentially the index field, then remove it. |
| ConstructorDeclaration? _removeUnnamedConstructor() { |
| var members = classDeclaration.members; |
| var constructors = members.whereType<ConstructorDeclaration>().toList(); |
| if (constructors.length != 1) { |
| return null; |
| } |
| var constructor = constructors[0]; |
| var name = constructor.name?.name; |
| if (name != null && name != 'new') { |
| return null; |
| } |
| var parameters = constructor.parameters.parameters; |
| // If there's only one constructor, then there can only be one entry in the |
| // constructor map. |
| var parameterData = constructorMap?.entries.first.value; |
| // `parameterData` should only be `null` if there is no index field. |
| var updatedParameterCount = |
| parameters.length - (parameterData == null ? 0 : 1); |
| if (updatedParameterCount != 0) { |
| return null; |
| } |
| membersToDelete.add(members.indexOf(constructor)); |
| return constructor; |
| } |
| |
| /// Transform the used constructors by removing the parameter corresponding to |
| /// the index field. |
| void _transformConstructors( |
| DartFileEditBuilder builder, ConstructorDeclaration? removedConstructor) { |
| final constructorMap = this.constructorMap; |
| if (constructorMap == null) { |
| return; |
| } |
| for (var constructor in constructorMap.keys) { |
| if (constructor.declaration != removedConstructor) { |
| var parameterData = constructorMap[constructor]; |
| if (parameterData != null) { |
| var parameters = constructor.declaration.parameters.parameters; |
| builder.addDeletion( |
| range.nodeInList(parameters, parameters[parameterData.index])); |
| } |
| } |
| } |
| } |
| |
| /// If the given [node] can be converted into an enum, then return a |
| /// description of the conversion work to be done. Otherwise, return `null`. |
| static _EnumDescription? fromClass(ClassDeclaration node) { |
| // The class must be a concrete class. |
| var classElement = node.declaredElement; |
| if (classElement == null || classElement.isAbstract) { |
| return null; |
| } |
| |
| // The class must be a subclass of Object, whether implicitly or explicitly. |
| var extendsClause = node.extendsClause; |
| if (extendsClause != null && |
| extendsClause.superclass.type?.isDartCoreObject == false) { |
| return null; |
| } |
| |
| // The class must either be private or must only have private constructors. |
| var constructors = _validateConstructors(node, classElement); |
| if (constructors == null) { |
| return null; |
| } |
| |
| // The class must not override either `==` or `hashCode`. |
| if (!_validateMethods(node)) { |
| return null; |
| } |
| |
| // There must be at least one static field that can be converted into an |
| // enum constant. |
| // |
| // The instance fields must all be final. |
| var fields = _validateFields(node, classElement); |
| if (fields == null || fields.fieldsToConvert.isEmpty) { |
| return null; |
| } |
| |
| var visitor = _EnumVisitor(classElement, fields.fieldsToConvert); |
| try { |
| node.accept(visitor); |
| } on _CannotConvertException { |
| return null; |
| } |
| |
| // Within the defining library, |
| // - there can't be any subclasses of the class to be converted, |
| // - there can't be any invocations of any constructor from that class. |
| try { |
| node.root.accept(_NonEnumVisitor(classElement)); |
| } on _CannotConvertException { |
| return null; |
| } |
| |
| var usedConstructors = _computeUsedConstructors(constructors, fields); |
| var constructorMap = _indexFieldData(usedConstructors, fields); |
| if (fields.indexField != null && constructorMap == null) { |
| return null; |
| } |
| |
| var membersToDelete = <int>[]; |
| return _EnumDescription( |
| classDeclaration: node, |
| constructorMap: constructorMap, |
| fields: fields, |
| membersToDelete: membersToDelete, |
| ); |
| } |
| |
| /// Return the subset of [constructors] that are invoked by the [fields] to be |
| /// converted. |
| static _Constructors _computeUsedConstructors( |
| _Constructors constructors, _Fields fields) { |
| var usedElements = <ConstructorElement>{}; |
| for (var field in fields.fieldsToConvert) { |
| usedElements.add(field.constructorElement); |
| } |
| var usedConstructors = _Constructors(); |
| for (var element in usedElements) { |
| var constructor = constructors.forElement(element); |
| if (constructor != null) { |
| usedConstructors.add(constructor); |
| } |
| } |
| return usedConstructors; |
| } |
| |
| /// If the index field can be removed, return a map describing the changes |
| /// that need to be made to both the constructors and the invocations of those |
| /// constructors. Otherwise, return `null`. |
| static Map<_Constructor, _Parameter>? _indexFieldData( |
| _Constructors usedConstructors, _Fields fields) { |
| var indexField = fields.indexField; |
| if (indexField == null) { |
| return null; |
| } |
| // Ensure that the index field has a corresponding field formal initializer |
| // in each of the used constructors. |
| var constructorMap = <_Constructor, _Parameter>{}; |
| for (var constructor in usedConstructors.constructors) { |
| var parameterData = _indexParameter(constructor, indexField); |
| if (parameterData == null) { |
| return null; |
| } |
| constructorMap[constructor] = parameterData; |
| } |
| |
| var fieldsToConvert = fields.fieldsToConvert; |
| var values = <int>{}; |
| for (var field in fieldsToConvert) { |
| var constructorElement = field.constructorElement; |
| var constructor = usedConstructors.forElement(constructorElement); |
| if (constructor == null) { |
| // We should never reach this point. |
| return null; |
| } |
| var parameterData = constructorMap[constructor]; |
| if (parameterData == null) { |
| // We should never reach this point. |
| return null; |
| } |
| var arguments = field.instanceCreation.argumentList.arguments; |
| var argument = parameterData.getArgument(arguments); |
| if (argument is! IntegerLiteral) { |
| return null; |
| } |
| var value = argument.value; |
| if (value == null) { |
| return null; |
| } |
| if (!values.add(value)) { |
| // Duplicate value. |
| return null; |
| } |
| } |
| var sortedValues = values.toList()..sort(); |
| if (sortedValues.length == fieldsToConvert.length && |
| sortedValues.first == 0 && |
| sortedValues.last == fieldsToConvert.length - 1) { |
| return constructorMap; |
| } |
| return null; |
| } |
| |
| static _Parameter? _indexParameter( |
| _Constructor constructor, _Field? indexField) { |
| if (indexField == null) { |
| return null; |
| } |
| var parameters = constructor.declaration.parameters.parameters; |
| var indexFieldElement = indexField.element; |
| for (var i = 0; i < parameters.length; i++) { |
| var element = parameters[i].declaredElement; |
| if (element is FieldFormalParameterElement) { |
| if (element.field == indexFieldElement) { |
| if (element.isPositional) { |
| return _Parameter(i, element); |
| } else { |
| return _Parameter(i, element); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /// Return a representation of all of the constructors declared by the |
| /// [classDeclaration], or `null` if the class can't be converted. |
| /// |
| /// The [classElement] must be the element declared by the [classDeclaration]. |
| static _Constructors? _validateConstructors( |
| ClassDeclaration classDeclaration, ClassElement classElement) { |
| var constructors = _Constructors(); |
| for (var member in classDeclaration.members) { |
| if (member is ConstructorDeclaration) { |
| var constructor = member.declaredElement; |
| if (constructor is ConstructorElement) { |
| if (!classElement.isPrivate && !constructor.isPrivate) { |
| // Public constructor in public enum. |
| return null; |
| } else if (!constructor.isFactory && !constructor.isConst) { |
| // Non-const constructor. |
| return null; |
| } |
| constructors.add(_Constructor(member, constructor)); |
| } else { |
| // Not resolved. |
| return null; |
| } |
| } |
| } |
| return constructors; |
| } |
| |
| /// Return a representation of all of the constructors declared by the |
| /// [classDeclaration], or `null` if the class can't be converted. |
| /// |
| /// The [classElement] must be the element declared by the [classDeclaration]. |
| static _Fields? _validateFields( |
| ClassDeclaration classDeclaration, ClassElement classElement) { |
| var potentialFieldsToConvert = <DartObject, List<_ConstantField>>{}; |
| _Field? indexField; |
| |
| for (var member in classDeclaration.members) { |
| if (member is FieldDeclaration) { |
| var fieldList = member.fields; |
| var fields = fieldList.variables; |
| if (member.isStatic) { |
| for (var field in fields) { |
| var fieldElement = field.declaredElement; |
| if (fieldElement is FieldElement) { |
| var fieldType = fieldElement.type; |
| // The field can be converted to be an enum constant if it |
| // - is a const field, |
| // - has a type equal to the type of the class, and |
| // - is initialized by an instance creation expression defined in this |
| // class. |
| if (fieldElement.isConst && |
| fieldType is InterfaceType && |
| fieldType.element == classElement) { |
| var initializer = field.initializer; |
| if (initializer is InstanceCreationExpression) { |
| var constructorElement = |
| initializer.constructorName.staticElement; |
| if (constructorElement != null && |
| !constructorElement.isFactory && |
| constructorElement.enclosingElement == classElement) { |
| var fieldValue = fieldElement.computeConstantValue(); |
| if (fieldValue != null) { |
| if (fieldList.variables.length != 1) { |
| // Too many constants in the field declaration. |
| return null; |
| } |
| potentialFieldsToConvert |
| .putIfAbsent(fieldValue, () => []) |
| .add(_ConstantField( |
| fieldElement, |
| field, |
| fieldList, |
| member, |
| initializer, |
| constructorElement, |
| fieldValue.getField('index')?.toIntValue() ?? |
| -1)); |
| } |
| } |
| } |
| } |
| } |
| } |
| } else { |
| for (var field in fields) { |
| if (!field.isFinal) { |
| // Non-final instance field. |
| return null; |
| } |
| var fieldElement = field.declaredElement; |
| if (fieldElement is FieldElement) { |
| var fieldType = fieldElement.type; |
| if (fieldElement.name == 'index' && fieldType.isDartCoreInt) { |
| indexField = _Field(fieldElement, field, fieldList, member); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| var fieldsToConvert = <_ConstantField>[]; |
| for (var list in potentialFieldsToConvert.values) { |
| if (list.length == 1) { |
| fieldsToConvert.add(list[0]); |
| } else { |
| // TODO(brianwilkerson) We could potentially handle the case where |
| // there's only one non-deprecated field in the list. We'd need to |
| // change the return type for this method so that we could return two |
| // lists: the list of fields to convert and the list of fields whose |
| // initializer needs to be updated to refer to the constant. |
| return null; |
| } |
| } |
| return _Fields(fieldsToConvert, indexField); |
| } |
| |
| /// Return `true` if the [classDeclaration] does not contain any methods that |
| /// prevent it from being converted. |
| static bool _validateMethods(ClassDeclaration classDeclaration) { |
| for (var member in classDeclaration.members) { |
| if (member is MethodDeclaration) { |
| if (member.name.name == '==' || member.name.name == 'hashCode') { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| } |
| |
| /// A visitor used to visit the class being converted. This visitor throws an |
| /// exception if a constructor for the class is invoked anywhere other than the |
| /// top-level expression of an initializer for one of the fields being converted. |
| class _EnumVisitor extends _BaseVisitor { |
| /// The declarations of the fields that are to be converted. |
| final List<VariableDeclaration> fieldsToConvert; |
| |
| /// A flag indicating whether we are currently visiting the children of a |
| /// field declaration that will be converted to be a constant. |
| bool inConstantDeclaration = false; |
| |
| /// Initialize a newly created visitor to visit the class declaration |
| /// corresponding to the given [classElement]. |
| _EnumVisitor(super.classElement, List<_ConstantField> fieldsToConvert) |
| : fieldsToConvert = |
| fieldsToConvert.map((field) => field.declaration).toList(); |
| |
| @override |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| if (!inConstantDeclaration) { |
| if (invokesGenerativeConstructor(node)) { |
| throw _CannotConvertException( |
| 'Constructor used outside constant initializer'); |
| } |
| } |
| inConstantDeclaration = false; |
| super.visitInstanceCreationExpression(node); |
| } |
| |
| @override |
| void visitVariableDeclaration(VariableDeclaration node) { |
| if (fieldsToConvert.contains(node)) { |
| inConstantDeclaration = true; |
| } |
| super.visitVariableDeclaration(node); |
| inConstantDeclaration = false; |
| } |
| } |
| |
| /// A representation of a field of interest in the class being converted. |
| class _Field { |
| /// The element representing the field. |
| final FieldElement element; |
| |
| /// The declaration of the field. |
| final VariableDeclaration declaration; |
| |
| /// The list containing the [declaration] |
| final VariableDeclarationList declarationList; |
| |
| /// The field declaration containing the [declarationList]. |
| final FieldDeclaration fieldDeclaration; |
| |
| _Field(this.element, this.declaration, this.declarationList, |
| this.fieldDeclaration); |
| |
| /// Return the name of the field. |
| String get name => declaration.name.name; |
| } |
| |
| /// A representation of all the fields of interest in the class being converted. |
| class _Fields { |
| /// The fields to be converted into enum constants. |
| List<_ConstantField> fieldsToConvert; |
| |
| /// The index field, or `null` if there is no index field. |
| _Field? indexField; |
| |
| _Fields(this.fieldsToConvert, this.indexField); |
| } |
| |
| /// A visitor that visits everything in the library other than the class being |
| /// converted. This visitor throws an exception if the class can't be converted |
| /// because |
| /// - there is a subclass of the class, or |
| /// - there is an invocation of one of the constructors of the class. |
| class _NonEnumVisitor extends _BaseVisitor { |
| /// Initialize a newly created visitor to visit everything except the class |
| /// declaration corresponding to the given [classElement]. |
| _NonEnumVisitor(super.classElement); |
| |
| @override |
| void visitClassDeclaration(ClassDeclaration node) { |
| var element = node.declaredElement; |
| if (element == null) { |
| throw _CannotConvertException('Unresolved'); |
| } |
| if (element != classElement) { |
| if (element.supertype?.element == classElement) { |
| throw _CannotConvertException('Class is extended'); |
| } else if (element.interfaces |
| .map((e) => e.element) |
| .contains(classElement)) { |
| throw _CannotConvertException('Class is implemented'); |
| } else if (element.mixins.map((e) => e.element).contains(classElement)) { |
| // This case won't occur unless there's an error in the source code, but |
| // it's easier to check for the condition than it is to check for the |
| // diagnostic. |
| throw _CannotConvertException('Class is mixed in'); |
| } |
| super.visitClassDeclaration(node); |
| } |
| } |
| |
| @override |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| if (invokesGenerativeConstructor(node)) { |
| throw _CannotConvertException( |
| 'Constructor used outside class being converted'); |
| } |
| super.visitInstanceCreationExpression(node); |
| } |
| } |
| |
| /// An object used to access information about a specific parameter, including |
| /// its index in the parameter list as well as any associated argument in an |
| /// argument list. |
| class _Parameter { |
| /// The index of this parameter in the enclosing constructor's parameter list. |
| final int index; |
| |
| /// The element associated with the parameter. |
| final ParameterElement element; |
| |
| _Parameter(this.index, this.element); |
| |
| /// Return the expression representing the argument associated with this |
| /// parameter, or `null` if there is no such argument. |
| Expression? getArgument(NodeList<Expression> arguments) { |
| return arguments.firstWhereOrNull( |
| (argument) => argument.staticParameterElement == element); |
| } |
| } |