From 04757513773f8c3d1af987757b6b61b1e6ed8d3b Mon Sep 17 00:00:00 2001 From: naipaka Date: Mon, 29 Jul 2024 17:35:23 +0900 Subject: [PATCH] feat: Remove `avoid_sliver_to_box_adapter` lint and add `avoid_consecutive_sliver_to_box_adapter` lint --- packages/altive_lints/lib/altive_lints.dart | 4 +- ...oid_consecutive_sliver_to_box_adapter.dart | 108 ++++++++++++++++++ .../lints/avoid_sliver_to_box_adapter.dart | 65 ----------- ...oid_consecutive_sliver_to_box_adapter.dart | 71 ++++++++++++ .../lints/avoid_sliver_to_box_adapter.dart | 24 ---- 5 files changed, 181 insertions(+), 91 deletions(-) create mode 100644 packages/altive_lints/lib/src/lints/avoid_consecutive_sliver_to_box_adapter.dart delete mode 100644 packages/altive_lints/lib/src/lints/avoid_sliver_to_box_adapter.dart create mode 100644 packages/altive_lints/lint_test/lints/avoid_consecutive_sliver_to_box_adapter.dart delete mode 100644 packages/altive_lints/lint_test/lints/avoid_sliver_to_box_adapter.dart diff --git a/packages/altive_lints/lib/altive_lints.dart b/packages/altive_lints/lib/altive_lints.dart index 8d5fc01..33cbeb9 100644 --- a/packages/altive_lints/lib/altive_lints.dart +++ b/packages/altive_lints/lib/altive_lints.dart @@ -1,8 +1,8 @@ import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'src/lints/avoid_consecutive_sliver_to_box_adapter.dart'; import 'src/lints/avoid_hardcoded_japanese.dart'; import 'src/lints/avoid_shrink_wrap_in_list_view.dart'; -import 'src/lints/avoid_sliver_to_box_adapter.dart'; PluginBase createPlugin() => _AltivePlugin(); @@ -11,6 +11,6 @@ class _AltivePlugin extends PluginBase { List getLintRules(CustomLintConfigs configs) => [ const AvoidHardcodedJapanese(), const AvoidShrinkWrapInListView(), - const AvoidSliverToBoxAdapter(), + const AvoidConsecutiveSliverToBoxAdapter(), ]; } diff --git a/packages/altive_lints/lib/src/lints/avoid_consecutive_sliver_to_box_adapter.dart b/packages/altive_lints/lib/src/lints/avoid_consecutive_sliver_to_box_adapter.dart new file mode 100644 index 0000000..9ebe208 --- /dev/null +++ b/packages/altive_lints/lib/src/lints/avoid_consecutive_sliver_to_box_adapter.dart @@ -0,0 +1,108 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; + +/// A `avoid_consecutive_sliver_to_box_adapter` rule that +/// identifies and discourages the use of consecutive +/// `SliverToBoxAdapter` widgets within a list. +/// +/// Consecutive usage of `SliverToBoxAdapter` can lead to +/// inefficient nesting and performance issues in scrollable areas. +/// +/// It suggests using `SliverList.list` or similar consolidated +/// sliver widgets to optimize rendering performance and reduce +/// the complexity of the widget tree. +/// +/// ### Example +/// +/// #### BAD: +/// +/// ```dart +/// CustomScrollView( +/// slivers: [ +/// SliverToBoxAdapter(child: Text('Item 1')), // Consecutive usage +/// SliverToBoxAdapter(child: Text('Item 2')), // LINT +/// ], +/// ); +/// ``` +/// +/// #### GOOD: +/// +/// ```dart +/// CustomScrollView( +/// slivers: [ +/// SliverList.list( +/// children: [ +/// Text('Item 1') +/// Text('Item 2') +/// ], +/// ), +/// ], +/// ); +/// ``` +class AvoidConsecutiveSliverToBoxAdapter extends DartLintRule { + const AvoidConsecutiveSliverToBoxAdapter() : super(code: _code); + + static const _code = LintCode( + name: 'avoid_consecutive_sliver_to_box_adapter', + problemMessage: 'Avoid using consecutive `SliverToBoxAdapter`. ' + 'Consider using `SliverList.list` instead.', + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addListLiteral((node) { + final iterator = node.elements.iterator; + if (!iterator.moveNext()) { + // if there are no elements, there is nothing to check. + return; + } + + var current = iterator.current; + while (iterator.moveNext()) { + final next = iterator.current; + if (_useSliverToBoxAdapter(current) && _useSliverToBoxAdapter(next)) { + reporter.reportErrorForNode(_code, node); + return; + } + current = next; + } + }); + } + + bool _useSliverToBoxAdapter(CollectionElement element) { + if (element is! Expression) { + return false; + } + return _isSliverToBoxAdapter(element) || _hasSliverToBoxAdapter(element); + } + + bool _isSliverToBoxAdapter(Expression expression) { + final typeName = + expression.staticType?.getDisplayString(withNullability: false); + return typeName == 'SliverToBoxAdapter'; + } + + bool _hasSliverToBoxAdapter(Expression element) { + if (element is! InstanceCreationExpression) { + return false; + } + final constructor = element; + final arguments = constructor.argumentList.arguments; + for (final argument in arguments) { + if (argument is NamedExpression && argument.name.label.name == 'sliver') { + final sliverExpression = argument.expression; + final sliverTypeName = sliverExpression.staticType + ?.getDisplayString(withNullability: false); + if (sliverTypeName == 'SliverToBoxAdapter') { + return true; + } + } + } + return false; + } +} diff --git a/packages/altive_lints/lib/src/lints/avoid_sliver_to_box_adapter.dart b/packages/altive_lints/lib/src/lints/avoid_sliver_to_box_adapter.dart deleted file mode 100644 index 2760027..0000000 --- a/packages/altive_lints/lib/src/lints/avoid_sliver_to_box_adapter.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; - -/// An `avoid_sliver_to_box_adapter` rule that discourages using -/// `SliverToBoxAdapter` due to performance inefficiencies. -/// -/// This widget can lead to layout issues and increased -/// re-rendering in scrollable environments. -/// It's recommended to use`CustomScrollView` with `SliverList` or -/// other sliver widgets to optimize performance and ensure smoother scrolling. -/// -/// See more here: https://api.flutter.dev/flutter/widgets/SliverToBoxAdapter-class.html -/// -/// ### Example -/// -/// #### BAD: -/// -/// ```dart -/// CustomScrollView( -/// slivers: [ -/// SliverToBoxAdapter( // LINT -/// child: YourWidget() -/// ), -/// ], -/// ); -/// ``` -/// -/// #### GOOD: -/// -/// ```dart -/// CustomScrollView( -/// slivers: [ -/// SliverList.list( -/// children: [ -/// YourWidget(), -/// ], -/// ), -/// ], -/// ); -/// ``` -class AvoidSliverToBoxAdapter extends DartLintRule { - const AvoidSliverToBoxAdapter() : super(code: _code); - - static const _code = LintCode( - name: 'avoid_sliver_to_box_adapter', - problemMessage: - 'Avoid using `SliverToBoxAdapter` due to performance issues. ' - 'Consider alternatives like `CustomScrollView` with `SliverList`.', - ); - - @override - void run( - CustomLintResolver resolver, - ErrorReporter reporter, - CustomLintContext context, - ) { - context.registry.addInstanceCreationExpression((node) { - final targetType = - node.staticType?.getDisplayString(withNullability: false); - if (targetType == 'SliverToBoxAdapter') { - reporter.reportErrorForNode(_code, node); - } - }); - } -} diff --git a/packages/altive_lints/lint_test/lints/avoid_consecutive_sliver_to_box_adapter.dart b/packages/altive_lints/lint_test/lints/avoid_consecutive_sliver_to_box_adapter.dart new file mode 100644 index 0000000..b21fff3 --- /dev/null +++ b/packages/altive_lints/lint_test/lints/avoid_consecutive_sliver_to_box_adapter.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; + +class ConsecutiveSliverToBoxAdapters extends StatelessWidget { + const ConsecutiveSliverToBoxAdapters({super.key}); + + @override + Widget build(BuildContext context) { + return const CustomScrollView( + // expect_lint: avoid_consecutive_sliver_to_box_adapter + slivers: [ + SliverToBoxAdapter( + child: Text('Hello'), + ), + SliverToBoxAdapter( + child: Text('World'), + ), + ], + ); + } +} + +class ConsecutiveSliverToBoxAdaptersWithSliverPadding extends StatelessWidget { + const ConsecutiveSliverToBoxAdaptersWithSliverPadding({super.key}); + + @override + Widget build(BuildContext context) { + return const CustomScrollView( + // expect_lint: avoid_consecutive_sliver_to_box_adapter + slivers: [ + SliverPadding( + padding: EdgeInsets.zero, + sliver: SliverToBoxAdapter( + child: Text('Hello'), + ), + ), + SliverPadding( + padding: EdgeInsets.zero, + sliver: SliverToBoxAdapter( + child: Text('World'), + ), + ), + ], + ); + } +} + +class NonConsecutiveSliverToBoxAdapters extends StatelessWidget { + const NonConsecutiveSliverToBoxAdapters({super.key}); + + @override + Widget build(BuildContext context) { + return CustomScrollView( + slivers: [ + const SliverToBoxAdapter( + child: Text('Hello'), + ), + SliverList.builder( + itemCount: 10, + itemBuilder: (context, index) { + return const SliverToBoxAdapter( + child: Text('item'), + ); + }, + ), + const SliverToBoxAdapter( + child: Text('World'), + ), + ], + ); + } +} diff --git a/packages/altive_lints/lint_test/lints/avoid_sliver_to_box_adapter.dart b/packages/altive_lints/lint_test/lints/avoid_sliver_to_box_adapter.dart deleted file mode 100644 index f38568c..0000000 --- a/packages/altive_lints/lint_test/lints/avoid_sliver_to_box_adapter.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/material.dart'; - -class MyWidget extends StatelessWidget { - const MyWidget({super.key}); - - @override - Widget build(BuildContext context) { - return const Scaffold( - body: CustomScrollView( - slivers: [ - // expect_lint: avoid_sliver_to_box_adapter - SliverToBoxAdapter( - child: Column( - children: [ - Text('Hello'), - Text('World'), - ], - ), - ), - ], - ), - ); - } -}