Skip to content

Commit

Permalink
feat: Add avoid_consecutive_sliver_to_box_adapter rule (#29)
Browse files Browse the repository at this point in the history
* feat: Add `avoid_shrink_wrap_in_list_view` rule

* feat: Add `avoid_sliver_to_box_adapter` rule

* chore: Remove unnecessary space

* feat: Remove `avoid_sliver_to_box_adapter` lint and add `avoid_consecutive_sliver_to_box_adapter` lint
  • Loading branch information
naipaka authored Jul 29, 2024
1 parent 4cfce85 commit 8a22122
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/altive_lints/lib/altive_lints.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:custom_lint_builder/custom_lint_builder.dart';

import 'src/lints/avoid_consecutive_sliver_to_box_adapter.dart';
import 'src/lints/avoid_hardcoded_color.dart';
import 'src/lints/avoid_hardcoded_japanese.dart';
import 'src/lints/avoid_shrink_wrap_in_list_view.dart';
Expand All @@ -10,6 +11,7 @@ PluginBase createPlugin() => _AltivePlugin();
class _AltivePlugin extends PluginBase {
@override
List<LintRule> getLintRules(CustomLintConfigs configs) => [
const AvoidConsecutiveSliverToBoxAdapter(),
const AvoidHardcodedColor(),
const AvoidHardcodedJapanese(),
const AvoidShrinkWrapInListView(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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: <Widget>[
/// SliverToBoxAdapter(child: Text('Item 1')), // Consecutive usage
/// SliverToBoxAdapter(child: Text('Item 2')), // LINT
/// ],
/// );
/// ```
///
/// #### GOOD:
///
/// ```dart
/// CustomScrollView(
/// slivers: <Widget>[
/// 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;
}
}
Original file line number Diff line number Diff line change
@@ -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'),
),
],
);
}
}

0 comments on commit 8a22122

Please sign in to comment.