Skip to content

Commit

Permalink
Migrate widget field when convert Stateless-based and Stateful-based …
Browse files Browse the repository at this point in the history
…to each other. (#3275)

In the official Dart `analysis_server` , when converting between
Flutter's `StatefulWidget` and `StatelessWidget`, the `widget` field
used in `StatefulWidget` is automatically added or removed.
However, this was not supported in the Assists within riverpod_lint.

In this PR, I have improved it to include the migration of that field in
the conversion.
  • Loading branch information
Kurogoma4D committed Feb 3, 2024
1 parent 4621be5 commit 07d4b86
Show file tree
Hide file tree
Showing 14 changed files with 1,417 additions and 290 deletions.
1 change: 1 addition & 0 deletions packages/riverpod_lint/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- Fix `async_value_nullable_pattern` false positive when used with generics
that have non-nullable type constrains.
- Add migration widget field when convert Stateless-based and Stateful-based to each other.

## 2.3.7 - 2023-11-27

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
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/element/element.dart';
import 'package:analyzer/source/source_range.dart';
// ignore: implementation_imports, somehow not exported by analyzer
import 'package:analyzer/src/generated/source.dart' show Source;
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:collection/collection.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';

Expand Down Expand Up @@ -83,6 +87,51 @@ class ConvertToStatefulBaseWidget extends RiverpodAssist {
final widgetClass = node.thisOrAncestorOfType<ClassDeclaration>();
if (widgetClass == null) return;

final nodesToMove = <ClassMember>{};
final elementsToMove = <Element>{};
final visitor = _FieldFinder();
for (final member in widgetClass.members) {
if (member is ConstructorDeclaration) {
member.accept(visitor);
}
}
final fieldsAssignedInConstructors = visitor.fieldsAssignedInConstructors;

for (final member in widgetClass.members) {
if (member is FieldDeclaration && !member.isStatic) {
for (final fieldNode in member.fields.variables) {
final fieldElement = fieldNode.declaredElement as FieldElement?;
if (fieldElement == null) continue;
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 && !member.isStatic) {
nodesToMove.add(member);
elementsToMove.add(member.declaredElement!);
}
}

for (final node in nodesToMove) {
final visitor = _ReplacementEditBuilder(
widgetClass.declaredElement!,
elementsToMove,
builder,
);
node.accept(visitor);
}

final buildMethod = node
.thisOrAncestorOfType<ClassDeclaration>()
?.members
Expand Down Expand Up @@ -186,3 +235,78 @@ class $createdStateClassName extends $baseStateName<${widgetClass.name}> {
});
}
}

// Original implemenation in
// package:analysis_server/lib/src/services/correction/dart/flutter_convert_to_stateful_widget.dart
class _FieldFinder extends RecursiveAstVisitor<void> {
Set<FieldElement> fieldsAssignedInConstructors = {};

@override
void visitFieldFormalParameter(FieldFormalParameter node) {
final element = node.declaredElement;
if (element is FieldFormalParameterElement) {
final field = element.field;
if (field != null) {
fieldsAssignedInConstructors.add(field);
}
}

super.visitFieldFormalParameter(node);
}

@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.parent is ConstructorFieldInitializer) {
final element = node.staticElement;
if (element is FieldElement) {
fieldsAssignedInConstructors.add(element);
}
}
if (node.inSetterContext()) {
final element = node.writeOrReadElement;
if (element is PropertyAccessorElement) {
final field = element.variable;
if (field is FieldElement) {
fieldsAssignedInConstructors.add(field);
}
}
}
}
}

class _ReplacementEditBuilder extends RecursiveAstVisitor<void> {
_ReplacementEditBuilder(
this.widgetClassElement,
this.elementsToMove,
this.builder,
);

final ClassElement widgetClassElement;
final Set<Element> elementsToMove;
final DartFileEditBuilder builder;

@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.inDeclarationContext()) {
return;
}
final element = node.staticElement;
if (element is ExecutableElement &&
element.enclosingElement == widgetClassElement &&
!elementsToMove.contains(element)) {
final offset = node.offset;
final qualifier =
element.isStatic ? widgetClassElement.displayName : 'widget';

final parent = node.parent;
if (parent is InterpolationExpression &&
parent.leftBracket.type ==
TokenType.STRING_INTERPOLATION_IDENTIFIER) {
builder.addSimpleInsertion(offset, '{$qualifier.');
builder.addSimpleInsertion(offset + node.length, '}');
} else {
builder.addSimpleInsertion(offset, '$qualifier.');
}
}
}
}
Loading

0 comments on commit 07d4b86

Please sign in to comment.