blob: 00a5ef9f6573dc016cd1cb32e11a6ad680c05b31 [file] [log] [blame]
// 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/assist.dart';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.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/range_factory.dart';
class ConvertClassToMixin extends CorrectionProducer {
@override
AssistKind get assistKind => DartAssistKind.CONVERT_CLASS_TO_MIXIN;
@override
Future<void> compute(ChangeBuilder builder) async {
var classDeclaration = node.thisOrAncestorOfType<ClassDeclaration>();
if (classDeclaration == null) {
return;
}
if (selectionOffset > classDeclaration.name.end ||
selectionEnd < classDeclaration.classKeyword.offset) {
return;
}
if (classDeclaration.members
.any((member) => member is ConstructorDeclaration)) {
return;
}
var finder = _SuperclassReferenceFinder();
classDeclaration.accept(finder);
var referencedClasses = finder.referencedClasses;
var superclassConstraints = <InterfaceType>[];
var interfaces = <InterfaceType>[];
var classElement = classDeclaration.declaredElement;
for (var type in classElement.mixins) {
if (referencedClasses.contains(type.element)) {
superclassConstraints.add(type);
} else {
interfaces.add(type);
}
}
var extendsClause = classDeclaration.extendsClause;
if (extendsClause != null) {
if (referencedClasses.length > superclassConstraints.length) {
superclassConstraints.insert(0, classElement.supertype);
} else {
interfaces.insert(0, classElement.supertype);
}
}
interfaces.addAll(classElement.interfaces);
await builder.addDartFileEdit(file, (builder) {
builder.addReplacement(
range.startStart(
classDeclaration.abstractKeyword ?? classDeclaration.classKeyword,
classDeclaration.leftBracket), (builder) {
builder.write('mixin ');
builder.write(classDeclaration.name.name);
builder.writeTypeParameters(
classDeclaration.declaredElement.typeParameters);
builder.writeTypes(superclassConstraints, prefix: ' on ');
builder.writeTypes(interfaces, prefix: ' implements ');
builder.write(' ');
});
});
}
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
static ConvertClassToMixin newInstance() => ConvertClassToMixin();
}
/// A visitor used to find all of the classes that define members referenced via
/// `super`.
class _SuperclassReferenceFinder extends RecursiveAstVisitor<void> {
final List<ClassElement> referencedClasses = <ClassElement>[];
_SuperclassReferenceFinder();
@override
void visitSuperExpression(SuperExpression node) {
var parent = node.parent;
if (parent is BinaryExpression) {
_addElement(parent.staticElement);
} else if (parent is IndexExpression) {
_addElement(parent.staticElement);
} else if (parent is MethodInvocation) {
_addIdentifier(parent.methodName);
} else if (parent is PrefixedIdentifier) {
_addIdentifier(parent.identifier);
} else if (parent is PropertyAccess) {
_addIdentifier(parent.propertyName);
}
return super.visitSuperExpression(node);
}
void _addElement(Element element) {
if (element is ExecutableElement) {
var enclosingElement = element.enclosingElement;
if (enclosingElement is ClassElement) {
referencedClasses.add(enclosingElement);
}
}
}
void _addIdentifier(SimpleIdentifier identifier) {
_addElement(identifier.staticElement);
}
}