Skip to content

Commit a80b13e

Browse files
bwilkersoncommit-bot@chromium.org
authored andcommitted
Add an assist to convert from a class to a mixin
Change-Id: Id47de8ffe8c381973c5a4ca53f7459782e62d37f Reviewed-on: https://dart-review.googlesource.com/74963 Commit-Queue: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
1 parent 0f45b2e commit a80b13e

File tree

5 files changed

+487
-5
lines changed

5 files changed

+487
-5
lines changed

pkg/analysis_server/lib/src/services/correction/assist.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class DartAssistKind {
1212
'dart.assist.addTypeAnnotation', 30, "Add type annotation");
1313
static const ASSIGN_TO_LOCAL_VARIABLE = const AssistKind(
1414
'dart.assist.assignToVariable', 30, "Assign value to new local variable");
15+
static const CONVERT_CLASS_TO_MIXIN = const AssistKind(
16+
'dart.assist.convert.classToMixin', 30, "Convert class to a mixin");
1517
static const CONVERT_DOCUMENTATION_INTO_BLOCK = const AssistKind(
1618
'dart.assist.convert.blockComment',
1719
30,

pkg/analysis_server/lib/src/services/correction/assist_internal.dart

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ class AssistProcessor {
127127
await _addProposal_addTypeAnnotation_SimpleFormalParameter();
128128
await _addProposal_addTypeAnnotation_VariableDeclaration();
129129
await _addProposal_assignToLocalVariable();
130+
await _addProposal_convertClassToMixin();
130131
await _addProposal_convertIntoFinalField();
131132
await _addProposal_convertIntoGetter();
132133
await _addProposal_convertDocumentationIntoBlock();
@@ -431,6 +432,60 @@ class AssistProcessor {
431432
}
432433
}
433434

435+
Future<void> _addProposal_convertClassToMixin() async {
436+
ClassDeclaration classDeclaration =
437+
node.getAncestor((n) => n is ClassDeclaration);
438+
if (classDeclaration == null) {
439+
return;
440+
}
441+
if (selectionOffset > classDeclaration.name.end ||
442+
selectionEnd < classDeclaration.classKeyword.offset) {
443+
return;
444+
}
445+
if (classDeclaration.members
446+
.any((member) => member is ConstructorDeclaration)) {
447+
return;
448+
}
449+
_SuperclassReferenceFinder finder = new _SuperclassReferenceFinder();
450+
classDeclaration.accept(finder);
451+
List<ClassElement> referencedClasses = finder.referencedClasses;
452+
List<InterfaceType> superclassConstraints = <InterfaceType>[];
453+
List<InterfaceType> interfaces = <InterfaceType>[];
454+
455+
ClassElement classElement = classDeclaration.declaredElement;
456+
for (InterfaceType type in classElement.mixins) {
457+
if (referencedClasses.contains(type.element)) {
458+
superclassConstraints.add(type);
459+
} else {
460+
interfaces.add(type);
461+
}
462+
}
463+
ExtendsClause extendsClause = classDeclaration.extendsClause;
464+
if (extendsClause != null) {
465+
if (referencedClasses.length > superclassConstraints.length) {
466+
superclassConstraints.insert(0, classElement.supertype);
467+
} else {
468+
interfaces.insert(0, classElement.supertype);
469+
}
470+
}
471+
interfaces.addAll(classElement.interfaces);
472+
473+
DartChangeBuilder changeBuilder = new DartChangeBuilder(session);
474+
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
475+
builder.addReplacement(
476+
range.startStart(
477+
classDeclaration.classKeyword, classDeclaration.leftBracket),
478+
(DartEditBuilder builder) {
479+
builder.write('mixin ');
480+
builder.write(classDeclaration.name.name);
481+
builder.writeTypes(superclassConstraints, prefix: ' on ');
482+
builder.writeTypes(interfaces, prefix: ' implements ');
483+
builder.write(' ');
484+
});
485+
});
486+
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_CLASS_TO_MIXIN);
487+
}
488+
434489
Future<void> _addProposal_convertDocumentationIntoBlock() async {
435490
// TODO(brianwilkerson) Determine whether this await is necessary.
436491
await null;
@@ -3386,3 +3441,43 @@ class _SimpleIdentifierRecursiveAstVisitor extends RecursiveAstVisitor {
33863441
visitor(node);
33873442
}
33883443
}
3444+
3445+
/**
3446+
* A visitor used to find all of the classes that define members referenced via
3447+
* `super`.
3448+
*/
3449+
class _SuperclassReferenceFinder extends RecursiveAstVisitor {
3450+
final List<ClassElement> referencedClasses = <ClassElement>[];
3451+
3452+
_SuperclassReferenceFinder();
3453+
3454+
@override
3455+
visitSuperExpression(SuperExpression node) {
3456+
AstNode parent = node.parent;
3457+
if (parent is BinaryExpression) {
3458+
_addElement(parent.staticElement);
3459+
} else if (parent is IndexExpression) {
3460+
_addElement(parent.staticElement);
3461+
} else if (parent is MethodInvocation) {
3462+
_addIdentifier(parent.methodName);
3463+
} else if (parent is PrefixedIdentifier) {
3464+
_addIdentifier(parent.identifier);
3465+
} else if (parent is PropertyAccess) {
3466+
_addIdentifier(parent.propertyName);
3467+
}
3468+
return super.visitSuperExpression(node);
3469+
}
3470+
3471+
void _addElement(Element element) {
3472+
if (element is ExecutableElement) {
3473+
Element enclosingElement = element.enclosingElement;
3474+
if (enclosingElement is ClassElement) {
3475+
referencedClasses.add(enclosingElement);
3476+
}
3477+
}
3478+
}
3479+
3480+
void _addIdentifier(SimpleIdentifier identifier) {
3481+
_addElement(identifier.staticElement);
3482+
}
3483+
}

0 commit comments

Comments
 (0)