From 25e79689e60a3e7106255cad94c30571a8943baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tru=C8=99c=C4=83=20Daria=20Maria?= <115713732+Dariaa14@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:14:46 +0300 Subject: [PATCH 1/2] Updated prefer_early_return rule and its visitor to use AnalysisRule . --- .../prefer_early_return_rule.dart | 52 +++++------- .../visitors/prefer_early_return_visitor.dart | 84 ++++--------------- 2 files changed, 40 insertions(+), 96 deletions(-) diff --git a/lib/src/lints/prefer_early_return/prefer_early_return_rule.dart b/lib/src/lints/prefer_early_return/prefer_early_return_rule.dart index e4b54b18..234c6a0c 100644 --- a/lib/src/lints/prefer_early_return/prefer_early_return_rule.dart +++ b/lib/src/lints/prefer_early_return/prefer_early_return_rule.dart @@ -1,8 +1,8 @@ -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:analyzer/analysis_rule/analysis_rule.dart'; +import 'package:analyzer/analysis_rule/rule_context.dart'; +import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; +import 'package:analyzer/error/error.dart'; import 'package:solid_lints/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; -import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// A rule which highlights `if` statements that span the entire body, /// and suggests replacing them with a reversed boolean check @@ -31,38 +31,32 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// c; /// } /// ``` -class PreferEarlyReturnRule extends SolidLintRule { - /// This lint rule represents the error if - /// 'if' statements should be reversed +class PreferEarlyReturnRule extends AnalysisRule { + /// The name of the lint static const String lintName = 'prefer_early_return'; - PreferEarlyReturnRule._(super.config); + /// Lint code + static const LintCode _code = LintCode( + lintName, + "Use reverse if reduce nesting", + ); /// Creates a new instance of [PreferEarlyReturnRule] - /// based on the lint configuration. - factory PreferEarlyReturnRule.createRule(CustomLintConfigs configs) { - final rule = RuleConfig( - configs: configs, - name: lintName, - problemMessage: (_) => "Use reverse if to reduce nesting", - ); + PreferEarlyReturnRule() + : super( + name: lintName, + description: 'Use reverse if to reduce nesting', + ); - return PreferEarlyReturnRule._(rule); - } + @override + LintCode get diagnosticCode => _code; @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, ) { - context.registry.addBlockFunctionBody((node) { - final visitor = PreferEarlyReturnVisitor(); - node.accept(visitor); - - for (final element in visitor.nodes) { - reporter.atNode(element, code); - } - }); + final visitor = PreferEarlyReturnVisitor(rule: this); + registry.addBlockFunctionBody(this, visitor); } } diff --git a/lib/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart b/lib/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart index 127de6e8..eb54df35 100644 --- a/lib/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart +++ b/lib/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart @@ -1,81 +1,31 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; -import 'package:solid_lints/src/lints/prefer_early_return/visitors/return_statement_visitor.dart'; -import 'package:solid_lints/src/lints/prefer_early_return/visitors/throw_expression_visitor.dart'; +import 'package:solid_lints/src/lints/prefer_early_return/prefer_early_return_rule.dart'; -/// The AST visitor that will collect all unnecessary if statements +/// Visitor for [PreferEarlyReturnRule]. class PreferEarlyReturnVisitor extends RecursiveAstVisitor { - final _nodes = []; + /// The rule associated with the visitor. + final PreferEarlyReturnRule rule; - /// All unnecessary if statements and conditional expressions. - Iterable get nodes => _nodes; + /// Constructor for [PreferEarlyReturnVisitor]. + PreferEarlyReturnVisitor({ + required this.rule, + }); @override - void visitBlockFunctionBody(BlockFunctionBody node) { - super.visitBlockFunctionBody(node); - - if (node.block.statements.isEmpty) return; - - final (ifStatements, nextStatement) = _getStartIfStatements(node); - if (ifStatements.isEmpty) return; - - // limit visitor to only work with functions - // that don't have a return statement or the return statement is empty - final nextStatementIsEmptyReturn = - nextStatement is ReturnStatement && nextStatement.expression == null; - final nextStatementIsNull = nextStatement == null; - - if (!nextStatementIsEmptyReturn && !nextStatementIsNull) return; - - _handleIfStatement(ifStatements.last); - } - - void _handleIfStatement(IfStatement node) { - if (_isElseIfStatement(node)) return; - if (_hasElseStatement(node)) return; - if (_hasReturnStatement(node)) return; - if (_hasThrowExpression(node)) return; - - _nodes.add(node); - } - -// returns a list of if statements at the start of the function -// and the next statement after it -// examples: -// [if, if, if, return] -> ([if, if, if], return) -// [if, if, if, _doSomething, return] -> ([if, if, if], _doSomething) -// [if, if, if] -> ([if, if, if], null) - (List, Statement?) _getStartIfStatements( - BlockFunctionBody body, - ) { - final List ifStatements = []; - for (final statement in body.block.statements) { - if (statement is IfStatement) { - ifStatements.add(statement); - } else { - return (ifStatements, statement); - } + void visitIfStatement(IfStatement node) { + if (_shouldReport(node)) { + rule.reportAtNode(node); } - return (ifStatements, null); - } - bool _hasElseStatement(IfStatement node) { - return node.elseStatement != null; + super.visitIfStatement(node); } - bool _isElseIfStatement(IfStatement node) { - return node.elseStatement != null && node.elseStatement is IfStatement; - } - - bool _hasReturnStatement(Statement node) { - final visitor = ReturnStatementVisitor(); - node.accept(visitor); - return visitor.nodes.isNotEmpty; - } + bool _shouldReport(IfStatement node) { + final parent = node.parent; - bool _hasThrowExpression(Statement node) { - final visitor = ThrowExpressionVisitor(); - node.accept(visitor); - return visitor.nodes.isNotEmpty; + return parent is Block && + parent.statements.length == 1 && + node.elseStatement == null; } } From 14a63c611370441d3a8ca532d3463faeafd1d8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tru=C8=99c=C4=83=20Daria=20Maria?= <115713732+Dariaa14@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:25:38 +0300 Subject: [PATCH 2/2] Added tests and fixed visitor for prefer early return --- .../prefer_early_return_rule.dart | 2 +- .../visitors/prefer_early_return_visitor.dart | 80 ++- .../prefer_early_return_rule_test.dart | 478 ++++++++++++++++++ 3 files changed, 545 insertions(+), 15 deletions(-) create mode 100644 test/lints/prefer_early_return/prefer_early_return_rule_test.dart diff --git a/lib/src/lints/prefer_early_return/prefer_early_return_rule.dart b/lib/src/lints/prefer_early_return/prefer_early_return_rule.dart index 234c6a0c..5eccc1c7 100644 --- a/lib/src/lints/prefer_early_return/prefer_early_return_rule.dart +++ b/lib/src/lints/prefer_early_return/prefer_early_return_rule.dart @@ -56,7 +56,7 @@ class PreferEarlyReturnRule extends AnalysisRule { RuleVisitorRegistry registry, RuleContext context, ) { - final visitor = PreferEarlyReturnVisitor(rule: this); + final visitor = PreferEarlyReturnVisitor(this); registry.addBlockFunctionBody(this, visitor); } } diff --git a/lib/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart b/lib/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart index eb54df35..a8342ef5 100644 --- a/lib/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart +++ b/lib/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart @@ -1,31 +1,83 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:solid_lints/src/lints/prefer_early_return/prefer_early_return_rule.dart'; +import 'package:solid_lints/src/lints/prefer_early_return/visitors/return_statement_visitor.dart'; +import 'package:solid_lints/src/lints/prefer_early_return/visitors/throw_expression_visitor.dart'; /// Visitor for [PreferEarlyReturnRule]. class PreferEarlyReturnVisitor extends RecursiveAstVisitor { - /// The rule associated with the visitor. + /// The rule associated with this visitor. final PreferEarlyReturnRule rule; - /// Constructor for [PreferEarlyReturnVisitor]. - PreferEarlyReturnVisitor({ - required this.rule, - }); + /// Creates an instance of [PreferEarlyReturnVisitor]. + PreferEarlyReturnVisitor(this.rule); @override - void visitIfStatement(IfStatement node) { - if (_shouldReport(node)) { - rule.reportAtNode(node); + void visitBlockFunctionBody(BlockFunctionBody node) { + super.visitBlockFunctionBody(node); + + if (node.block.statements.isEmpty) return; + + final (ifStatements, nextStatement) = _getStartIfStatements(node); + if (ifStatements.isEmpty) return; + + // limit visitor to only work with functions + // that don't have a return statement or the return statement is empty + final nextStatementIsEmptyReturn = + nextStatement is ReturnStatement && nextStatement.expression == null; + final nextStatementIsNull = nextStatement == null; + + if (!nextStatementIsEmptyReturn && !nextStatementIsNull) return; + + _handleIfStatement(ifStatements.last); + } + + void _handleIfStatement(IfStatement node) { + if (_isElseIfStatement(node)) return; + if (_hasElseStatement(node)) return; + if (_hasReturnStatement(node)) return; + if (_hasThrowExpression(node)) return; + + rule.reportAtNode(node); + } + +// returns a list of if statements at the start of the function +// and the next statement after it +// examples: +// [if, if, if, return] -> ([if, if, if], return) +// [if, if, if, _doSomething, return] -> ([if, if, if], _doSomething) +// [if, if, if] -> ([if, if, if], null) + (List, Statement?) _getStartIfStatements( + BlockFunctionBody body, + ) { + final List ifStatements = []; + for (final statement in body.block.statements) { + if (statement is IfStatement) { + ifStatements.add(statement); + } else { + return (ifStatements, statement); + } } + return (ifStatements, null); + } - super.visitIfStatement(node); + bool _hasElseStatement(IfStatement node) { + return node.elseStatement != null; } - bool _shouldReport(IfStatement node) { - final parent = node.parent; + bool _isElseIfStatement(IfStatement node) { + return node.elseStatement != null && node.elseStatement is IfStatement; + } + + bool _hasReturnStatement(Statement node) { + final visitor = ReturnStatementVisitor(); + node.accept(visitor); + return visitor.nodes.isNotEmpty; + } - return parent is Block && - parent.statements.length == 1 && - node.elseStatement == null; + bool _hasThrowExpression(Statement node) { + final visitor = ThrowExpressionVisitor(); + node.accept(visitor); + return visitor.nodes.isNotEmpty; } } diff --git a/test/lints/prefer_early_return/prefer_early_return_rule_test.dart b/test/lints/prefer_early_return/prefer_early_return_rule_test.dart new file mode 100644 index 00000000..e39dae80 --- /dev/null +++ b/test/lints/prefer_early_return/prefer_early_return_rule_test.dart @@ -0,0 +1,478 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:solid_lints/src/lints/prefer_early_return/prefer_early_return_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(PreferEarlyReturnRuleTest); + }); +} + +@reflectiveTest +class PreferEarlyReturnRuleTest extends AnalysisRuleTest { + @override + void setUp() { + rule = PreferEarlyReturnRule(); + super.setUp(); + } + + @override + String get analysisRule => PreferEarlyReturnRule.lintName; + + void test_reports_if_as_only_statement_in_function() async { + await assertDiagnostics( + r''' +void test(bool a) { + if (a) { + print('hello'); + } +} +''', + [lint(22, 32)], + ); + } + + void test_reports_if_with_return() async { + await assertDiagnostics( + r''' +void test(bool a) { + if (a) { + print('hello'); + } + + return; +} +''', + [lint(22, 32)], + ); + } + + void test_does_not_report_if_with_return_value() async { + await assertNoDiagnostics( + r''' +int test(bool a) { + if (a) { + print('hello'); + } + + return 1; +} +''', + ); + } + + void test_reports_nested_if_as_only_statement() async { + await assertDiagnostics( + r''' +void test(bool a, bool b) { + if (a) { + if (b) { + print('nested'); + } + } +} +''', + [ + lint(30, 54), + ], + ); + } + + void test_does_not_report_nested_if_with_return_value() async { + await assertNoDiagnostics( + r''' +int test(bool a, bool b) { + if (a) { + if (b) { + print('nested'); + } + } + + return 1; +} +''', + ); + } + + void test_reports_nested_3_if_as_only_statement() async { + await assertDiagnostics( + r''' +void test(bool a, bool b, bool c) { + if (a) { + if (b) { + if (c){ + print('nested'); + } + } + } +} +''', + [ + lint(38, 78), + ], + ); + } + + void test_reports_nested_3_with_return() async { + await assertDiagnostics( + r''' +void test(bool a, bool b, bool c) { + if (a) { + if (b) { + if (c){ + print('nested'); + } + } + } + return; +} +''', + [ + lint(38, 78), + ], + ); + } + + void test_does_not_report_if_else() async { + await assertNoDiagnostics( + r''' +void test(bool a) { + if (a) { + print('hello'); + } else { + print('hello'); + } +} +''', + ); + } + + void test_does_not_report_if_else_return() async { + await assertNoDiagnostics( + r''' +void test(bool a) { + if (a) { + print('hello'); + } else { + return; + } +} +''', + ); + } + + void test_does_not_report_nested_if_else() async { + await assertNoDiagnostics( + r''' +void test(bool a, bool b) { + if (a) { + if(b){ + print('hello'); + } + } else { + print('hello'); + } +} +''', + ); + } + + void test_reports_inner_if_else() async { + await assertDiagnostics( + r''' +void test(bool a, bool b) { + if (a) { + if(b){ + print('hello'); + } + else { + print('hello'); + } + } +} +''', + [ + lint(30, 90), + ], + ); + } + + void test_does_not_report_nested_3_with_else_1() async { + await assertNoDiagnostics( + r''' +void test(bool a, bool b, bool c) { + if (a) { + if (b) { + if (c){ + print('nested'); + } + } + } else{ + print('hello'); + } +} +''', + ); + } + + void test_reports_nested_3_with_else_2() async { + await assertDiagnostics( + r''' +void test(bool a, bool b, bool c) { + if (a) { + if (b) { + if (c) { + print('nested'); + } + } else { + print('nested'); + } + } +} +''', + [ + lint(38, 115), + ], + ); + } + + void test_reports_nested_3_with_else_3() async { + await assertDiagnostics( + r''' +void test(bool a, bool b, bool c) { + if (a) { + if (b) { + if (c) { + print('nested'); + } + else { + print('nested'); + } + } + } +} +''', + [ + lint(38, 126), + ], + ); + } + + void test_reports_2_sequencial_if() async { + await assertDiagnostics( + r''' +void test(bool a, bool b) { + if (a) return; + if (b) { + print('gello'); + } +} +''', + [ + lint(47, 32), + ], + ); + } + + void test_does_not_report_2_sequencial_if_with_return() async { + await assertNoDiagnostics( + r''' +void test(bool a, bool b) { + if (a) return; + if (b) return; + + return; +} +''', + ); + } + + void test_reports_2_sequencial_if_with_return_2() async { + await assertDiagnostics( + r''' +void test(bool a, bool b) { + if (a) return; + if (b) { + print('hello'); + } + + return; +} +''', + [ + lint(47, 32), + ], + ); + } + + void test_does_not_report_2_sequencial_with_following_statement() async { + await assertNoDiagnostics( + r''' +void test(bool a, bool b) { + if (a) return; + if (b) { + print('hello'); + } + + print('after'); +} +''', + ); + } + + void test_reports_2_sequencial_if_with_something() async { + await assertDiagnostics( + r''' +void test(bool a, bool b) { + if (a) { + print('hello'); + } + if (b) { + print('hello'); + } +} +''', + [ + lint(65, 32), + ], + ); + } + + void test_reports_3_sequencial_if_with_return() async { + await assertDiagnostics( + r''' +void test(bool a, bool b, bool c) { + if (a) return; + if (b) return; + if (c) { + print('hello'); + } + + return; +} +''', + [ + lint(72, 32), + ], + ); + } + + void test_reports_3_sequencial_if_with_something_2() async { + await assertDiagnostics( + r''' +void test(bool a, bool b, bool c) { + if (a) return; + if (b) { + print('hello'); + } + if (c) { + print('hello'); + } +} +''', + [ + lint(90, 32), + ], + ); + } + + void test_does_not_report_if_throw_with_return() async { + await assertNoDiagnostics( + r''' +void test(bool a) { + if (a) { + throw ''; + } + + return; +} +''', + ); + } + + void test_does_not_report_if_else_throw() async { + await assertNoDiagnostics( + r''' +void test(bool a) { + if (a) { + print('hello'); + } else { + throw ''; + } +} +''', + ); + } + + void test_reports_2_sequencial_if_throw() async { + await assertDiagnostics( + r''' +void test(bool a, bool b) { + if (a) throw ''; + if (b) { + print('hello'); + } +} +''', + [ + lint(49, 32), + ], + ); + } + + void test_reports_2_sequencial_if_throw_with_return() async { + await assertDiagnostics( + r''' +void test(bool a, bool b) { + if (a) throw ''; + if (b) { + print('hello'); + } + + return; +} +''', + [ + lint(49, 32), + ], + ); + } + + void test_reports_3_sequencial_if_throw_with_return() async { + await assertDiagnostics( + r''' +void test(bool a, bool b, bool c) { + if (a) throw ''; + if (b) throw ''; + if (c) { + print('hello'); + } + + return; +} +''', + [ + lint(76, 32), + ], + ); + } + + void test_reports_3_sequencial_if_throw_with_something() async { + await assertDiagnostics( + r''' +void test(bool a, bool b, bool c) { + if (a) throw ''; + if (b) { + print('hello'); + } + if (c) { + print('hello'); + } +} +''', + [ + lint(92, 32), + ], + ); + } +}