Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add conversion assists for various widgets, including Hook-based widgets #2306

Merged
merged 30 commits into from
Apr 8, 2023
Merged
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cd7f967
enable riverpod_lint
K9i-0 Mar 12, 2023
b4fea06
remove unnecessary Consumer
K9i-0 Mar 12, 2023
42f6739
move field
K9i-0 Mar 12, 2023
40f6379
add convert to widget assists
K9i-0 Mar 12, 2023
c8b2e46
implement ConvertToStatelessWidget
K9i-0 Mar 12, 2023
2024145
implement ConvertToStatefulWidget
K9i-0 Mar 12, 2023
38dfc92
fix file name
K9i-0 Mar 12, 2023
6e67631
add current test result
K9i-0 Mar 12, 2023
e08805d
Merge branch 'master' into add_new_lints
K9i-0 Mar 14, 2023
ee9bae7
Implement ConvertToStatefulHookWidget's method
K9i-0 Mar 17, 2023
3ed6c52
Implement ConvertToStatefulHookConsumerWidget's method
K9i-0 Mar 17, 2023
ec624b2
Implement ConvertToHookWidget's method
K9i-0 Mar 17, 2023
6cac1ac
Implement ConvertToHookConsumerWidget's method
K9i-0 Mar 17, 2023
feff63e
Add test
K9i-0 Mar 21, 2023
a29515c
Refactor
K9i-0 Mar 21, 2023
177eda9
remove
K9i-0 Mar 21, 2023
f23df4c
Merge branch 'master' into add_new_lints
K9i-0 Apr 7, 2023
74524df
Display hook-related assists only when there is a dependency on hooks…
K9i-0 Apr 8, 2023
5e50047
Change priorities
K9i-0 Apr 8, 2023
02b77fa
update golden files
K9i-0 Apr 8, 2023
f8f4597
change test file name
K9i-0 Apr 8, 2023
5eb0935
Provide assists for hook-related functions only when there is a depen…
K9i-0 Apr 8, 2023
a224e24
change name
K9i-0 Apr 8, 2023
d9d8a34
Merge branch 'master' of https://github.com/rrousselGit/riverpod into…
rrousselGit Apr 8, 2023
1929cba
Remove dynamic
rrousselGit Apr 8, 2023
0c12926
Update packages/riverpod_lint_flutter_test/test/assists/convert_to_wi…
rrousselGit Apr 8, 2023
c38f878
Use named parameters
K9i-0 Apr 8, 2023
94e67f0
Refactor
rrousselGit Apr 8, 2023
b2c8fb9
Merge branch 'add_new_lints' of https://github.com/k9i-0/riverpod int…
rrousselGit Apr 8, 2023
3975a36
Changelog
rrousselGit Apr 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement ConvertToStatefulHookWidget's method
  • Loading branch information
K9i-0 committed Mar 17, 2023
commit ee9bae784a1771898a3b485492b39d42e2cbbc99
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import 'package:analyzer/dart/ast/ast.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:collection/collection.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';

import '../object_utils.dart';
import '../riverpod_custom_lint.dart';
import 'convert_to_widget_utils.dart';

Expand All @@ -12,6 +14,8 @@ const _convertTarget = ConvertToWidget.statefulHookWidget;
final _statelessBaseType = getStatelessBaseType(excludes: [_convertTarget]);
final _statefulBaseType = getStatefulBaseType(excludes: [_convertTarget]);

const _stateType = TypeChecker.fromName('State', packageName: 'flutter');

class ConvertToStatefulHookWidget extends RiverpodAssist {
ConvertToStatefulHookWidget();

Expand Down Expand Up @@ -53,7 +57,47 @@ class ConvertToStatefulHookWidget extends RiverpodAssist {
message: 'Convert to ${_convertTarget.widgetName}',
priority: _convertTarget.priority,
);
// TODO implement

changeBuilder.addDartFileEdit((builder) {
// Change the extended base class
builder.addSimpleReplacement(
node.superclass.sourceRange,
_convertTarget.widgetName,
);

final widgetClass = node.thisOrAncestorOfType<ClassDeclaration>();
if (widgetClass == null) return;

// Now update "build" to take a "ref" parameter
final buildMethod = node
.thisOrAncestorOfType<ClassDeclaration>()
?.members
.whereType<MethodDeclaration>()
.firstWhereOrNull((element) => element.name.lexeme == 'build');
if (buildMethod == null) return;

final createdStateClassName = '_${widgetClass.name.lexeme}HookState';

// Split the class into two classes right before the build method
builder.addSimpleInsertion(buildMethod.offset, '''
@override
State<${widgetClass.name.lexeme}> createState() => $createdStateClassName();
}

class $createdStateClassName extends State<${widgetClass.name.lexeme}> {
''');

final buildParams = buildMethod.parameters;
// If the build method has a ref, remove it
if (buildParams != null && buildParams.parameters.length == 2) {
builder.addDeletion(
sourceRangeFrom(
start: buildParams.parameters.first.end,
end: buildParams.rightParenthesis.offset,
),
);
}
});
}

void _convertStatefulToStatefulHookWidget(
Expand All @@ -65,6 +109,68 @@ class ConvertToStatefulHookWidget extends RiverpodAssist {
message: 'Convert to ${_convertTarget.widgetName}',
priority: _convertTarget.priority,
);
// TODO implement

changeBuilder.addDartFileEdit((builder) {
// Change the extended base class
builder.addSimpleReplacement(
node.superclass.sourceRange,
_convertTarget.widgetName,
);

final widgetClass = node.thisOrAncestorOfType<ClassDeclaration>();
if (widgetClass == null) return;

final stateClass = _findStateClass(widgetClass);
if (stateClass == null) return;

final createStateMethod = widgetClass.members
.whereType<MethodDeclaration>()
.firstWhereOrNull((element) => element.name.lexeme == 'createState');
if (createStateMethod != null) {
final returnTypeString = createStateMethod.returnType?.toSource() ?? '';
if (returnTypeString != stateClass.name.lexeme) {
// Replace ConsumerState<MyWidget> with State<MyWidget>
builder.addSimpleReplacement(
createStateMethod.returnType!.sourceRange,
'State<${widgetClass.name}>',
);
}
}

final stateExtends = stateClass.extendsClause;
if (stateExtends != null) {
// Replace ConsumerState<MyWidget> with State<MyWidget>
builder.addSimpleReplacement(
stateExtends.superclass.sourceRange,
'State<${widgetClass.name}>',
);
}
});
}

// TODO: standardization
ClassDeclaration? _findStateClass(ClassDeclaration widgetClass) {
final widgetType = widgetClass.declaredElement?.thisType;
if (widgetType == null) return null;

return widgetClass
.thisOrAncestorOfType<CompilationUnit>()
?.declarations
.whereType<ClassDeclaration>()
.where(
// Is the class a state class?
(e) =>
e.extendsClause?.superclass.type
.let(_stateType.isAssignableFromType) ??
false,
)
.firstWhereOrNull((e) {
final stateWidgetType = e
.extendsClause?.superclass.typeArguments?.arguments.firstOrNull?.type;
if (stateWidgetType == null) return false;

final checker = TypeChecker.fromStatic(widgetType);
return checker.isExactlyType(stateWidgetType);
});
}
}