diff --git a/packages/altive_lints/lib/altive_lints.dart b/packages/altive_lints/lib/altive_lints.dart index 6019d11..125801c 100644 --- a/packages/altive_lints/lib/altive_lints.dart +++ b/packages/altive_lints/lib/altive_lints.dart @@ -8,6 +8,7 @@ import 'src/lints/avoid_single_child.dart'; import 'src/lints/prefer_clock_now.dart'; import 'src/lints/prefer_dedicated_media_query_methods.dart'; import 'src/lints/prefer_space_between_elements.dart'; +import 'src/lints/prefer_sliver_prefix.dart'; PluginBase createPlugin() => _AltivePlugin(); @@ -22,5 +23,6 @@ class _AltivePlugin extends PluginBase { const PreferClockNow(), const PreferDedicatedMediaQueryMethods(), const PreferSpaceBetweenElements(), + const PreferSliverPrefix(), ]; } diff --git a/packages/altive_lints/lib/src/lints/prefer_sliver_prefix.dart b/packages/altive_lints/lib/src/lints/prefer_sliver_prefix.dart new file mode 100644 index 0000000..d11b8b3 --- /dev/null +++ b/packages/altive_lints/lib/src/lints/prefer_sliver_prefix.dart @@ -0,0 +1,76 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:collection/collection.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; + +/// A `prefer_sliver_prefix` rule that ensures widgets returning +/// a Sliver-type widget have a "Sliver" prefix in their class names. +/// +/// This naming convention improves code readability and +/// consistency by clearly indicating the widget's functionality and +/// return type through its name. +/// +/// ### Example +/// +/// #### BAD: +/// +/// ```dart +/// class MyCustomList extends StatelessWidget { +/// @override +/// Widget build(BuildContext context) { +/// return SliverList(...); // LINT +/// } +/// } +/// ``` +/// +/// #### GOOD: +/// +/// ```dart +/// class SliverMyCustomList extends StatelessWidget { +/// @override +/// Widget build(BuildContext context) { +/// return SliverList(...); +/// } +/// } +/// ``` +class PreferSliverPrefix extends DartLintRule { + const PreferSliverPrefix() : super(code: _code); + + static const _code = LintCode( + name: 'prefer_sliver_prefix', + problemMessage: 'Widgets returning Sliver should have a "Sliver" prefix.', + correctionMessage: 'Consider adding "Sliver" prefix to the widget name.', + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addClassDeclaration((node) { + final className = node.name.lexeme; + final methodBody = node.members + .whereType() + .firstWhereOrNull((method) => method.name.lexeme == 'build') + ?.body; + + if (methodBody is BlockFunctionBody) { + final returnStatements = + methodBody.block.statements.whereType(); + final returnsSliverWidget = returnStatements.any( + (returnStatement) { + final returnType = returnStatement.expression?.staticType; + final typeName = + returnType?.getDisplayString(withNullability: false); + return typeName?.startsWith('Sliver') ?? false; + }, + ); + + if (returnsSliverWidget && !className.startsWith('Sliver')) { + reporter.reportErrorForNode(_code, node); + } + } + }); + } +} diff --git a/packages/altive_lints/lint_test/lints/prefer_sliver_prefix.dart b/packages/altive_lints/lint_test/lints/prefer_sliver_prefix.dart new file mode 100644 index 0000000..94d792f --- /dev/null +++ b/packages/altive_lints/lint_test/lints/prefer_sliver_prefix.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +// expect_lint: prefer_sliver_prefix +class MyWidget extends StatelessWidget { + const MyWidget({super.key}); + + @override + Widget build(BuildContext context) { + return SliverList.builder( + itemCount: 10, + itemBuilder: (context, index) { + return const Placeholder(); + }, + ); + } +}