compute method
Computes the changes for this producer using builder.
This method should not modify fixKind.
Implementation
@override
Future<void> compute(ChangeBuilder builder) async {
logInfo('Starting assist: Convert to StatelessComponent');
final componentClass = node.thisOrAncestorOfType<ClassDeclaration>();
final superclass = componentClass?.extendsClause?.superclass;
logInfo(
'Found component class: ${componentClass?.name.lexeme}, '
'superclass: ${superclass?.name.lexeme}',
);
if (componentClass == null || superclass == null) {
logError(
' No class declaration or superclass found at offset $selectionOffset',
);
return;
}
// useDeclaringConstructorsAst = true;
final componentClassBody = componentClass.body;
if (componentClassBody is! BlockClassBody) {
logError(
' Component class body is not a '
'block ${componentClassBody.runtimeType}',
);
return;
}
// Don't spam, activate only from the `class` keyword to the class body.
if (selectionOffset < componentClass.classKeyword.offset ||
selectionOffset > componentClassBody.leftBracket.end) {
logError(
' Selection is outside the component class body',
);
return;
}
// Must be a StatefulComponent subclass.
final componentClassFragment = componentClass.declaredFragment!;
final componentClassElement = componentClassFragment.element;
final superType = componentClassElement.supertype;
if (superType == null || !superType.isExactlyStatefulComponentType) {
logError(
' Component class is not a StatefulComponent subclass',
);
return;
}
final createStateMethod = _findCreateStateMethod(componentClass);
if (createStateMethod == null) return;
final stateClass = _findStateClass(componentClassElement);
final stateClassElement = stateClass?.declaredFragment!.element;
if (stateClass == null ||
stateClassElement == null ||
!Identifier.isPrivateName(stateClass.namePart.typeName.lexeme) ||
!_isSameTypeParameters(componentClass, stateClass)) {
logError(' State class is not valid');
return;
}
final verifier = _StatelessVerifier();
final fieldFinder = _FieldFinder();
for (final member in stateClass.members2) {
if (member is ConstructorDeclaration) {
member.accept(fieldFinder);
} else if (member is MethodDeclaration) {
member.accept(verifier);
if (!verifier.canBeStateless) {
logError(
' State class cannot be converted to StatelessComponent',
);
return;
}
}
}
final usageVerifier = _StateUsageVisitor(
componentClassElement,
stateClassElement,
);
unit.visitChildren(usageVerifier);
if (usageVerifier.used) {
logError(
' State class is used in a way that prevents'
' conversion to StatelessComponent',
);
return;
}
final fieldsAssignedInConstructors =
fieldFinder.fieldsAssignedInConstructors;
// Prepare nodes to move.
final nodesToMove = <ClassMember>[];
final elementsToMove = <Element>{};
for (final member in stateClass.members2) {
if (member is FieldDeclaration) {
if (member.isStatic) {
logWarning(
' Static members cannot be moved to StatelessComponent '
' and will be skipped: '
'${member.fields.variables.map((v) => v.name.lexeme).join(', ')}',
);
return;
}
for (final fieldNode in member.fields.variables) {
final fieldElement =
fieldNode.declaredFragment!.element as FieldElement;
if (!fieldsAssignedInConstructors.contains(fieldElement)) {
nodesToMove.add(member);
elementsToMove.add(fieldElement);
final getter = fieldElement.getter;
if (getter != null) {
elementsToMove.add(getter);
}
final setter = fieldElement.setter;
if (setter != null) {
elementsToMove.add(setter);
}
}
}
} else if (member is MethodDeclaration) {
if (member.isStatic) {
logWarning(
' Static members cannot be moved to StatelessComponent and will'
' be skipped: ${member.name.lexeme}',
);
return;
}
if (!_isDefaultOverride(member)) {
nodesToMove.add(member);
elementsToMove.add(member.declaredFragment!.element);
}
}
}
/// Return the code for the [movedNode], so that qualification of the
/// references to the widget (`widget.` or static `MyComponentClass.`)
/// is removed
String rewriteComponentMemberReferences(AstNode movedNode) {
final linesRange = utils.getLinesRange(range.node(movedNode));
final text = utils.getRangeText(linesRange);
// Remove `widget.` before references to the widget instance members.
final visitor = _ReplacementEditBuilder(
componentClassElement,
elementsToMove,
linesRange,
);
movedNode.accept(visitor);
return SourceEdit.applySequence(text, visitor.edits.reversed.toList());
}
final statelessComponentClass = await getNoctermClass(
sessionHelper,
'StatelessComponent',
);
if (statelessComponentClass == null) {
logError(' Could not find StatelessComponent class');
return;
}
await builder.addDartFileEdit(file, (builder) {
builder.addReplacement(range.node(superclass), (builder) {
builder.writeReference(statelessComponentClass);
});
builder.addDeletion(range.deletionRange(stateClass));
var createStateNextToEnd = createStateMethod.endToken.next!;
createStateNextToEnd =
createStateNextToEnd.precedingComments ?? createStateNextToEnd;
final createStateRange = range.startOffsetEndOffset(
utils.getLineContentStart(createStateMethod.offset),
utils.getLineContentStart(createStateNextToEnd.offset),
);
final newLine =
createStateNextToEnd.type != TokenType.CLOSE_CURLY_BRACKET;
builder.addReplacement(createStateRange, (builder) {
for (var i = 0; i < nodesToMove.length; i++) {
final member = nodesToMove[i];
final comments = member.beginToken.precedingComments;
if (comments != null) {
final offset = utils.getLineContentStart(comments.offset);
final length = comments.end - offset;
builder.writeln(utils.getText(offset, length));
}
final text = rewriteComponentMemberReferences(member);
builder.write(text);
if (newLine || i < nodesToMove.length - 1) {
builder.writeln();
}
}
});
});
}