-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a rule
prefer_guard_clause
for reversing nested if statements (#…
…154) * reverse_if_to_avoid_nesting wip * change name to prefer_guard_close and improve tests * prefer_guard_clause wip * fix test name for prefer_guard_clause * changed to detecting nested if statements * fix pr comments * adapt to two sequential if statements * remove (.length <= 2) limit * fix alphabetic ordering of imports * refactor and fix prefer_early_return_visitor.dart to handle nested if statements correctly * refactor prefer_early_return --------- Co-authored-by: alexiuk.genius <alexiuk.genius@gmail.com>
- Loading branch information
Showing
9 changed files
with
400 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
lib/src/lints/prefer_early_return/prefer_early_return_rule.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import 'package:analyzer/error/listener.dart'; | ||
import 'package:custom_lint_builder/custom_lint_builder.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 | ||
/// with an early return. | ||
/// | ||
/// ### Example | ||
/// | ||
/// #### BAD: | ||
/// | ||
/// ```dart | ||
/// void func() { | ||
/// if (a) { //LINT | ||
/// if (b) { //LINT | ||
/// c; | ||
/// } | ||
/// } | ||
/// } | ||
/// ``` | ||
/// | ||
/// #### GOOD: | ||
/// | ||
/// ```dart | ||
/// void func() { | ||
/// if (!a) return; | ||
/// if (!b) return; | ||
/// c; | ||
/// } | ||
/// ``` | ||
class PreferEarlyReturnRule extends SolidLintRule { | ||
/// The [LintCode] of this lint rule that represents the error if | ||
/// 'if' statements should be reversed | ||
static const String lintName = 'prefer_early_return'; | ||
|
||
PreferEarlyReturnRule._(super.config); | ||
|
||
/// 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", | ||
); | ||
|
||
return PreferEarlyReturnRule._(rule); | ||
} | ||
|
||
@override | ||
void run( | ||
CustomLintResolver resolver, | ||
ErrorReporter reporter, | ||
CustomLintContext context, | ||
) { | ||
context.registry.addBlockFunctionBody((node) { | ||
final visitor = PreferEarlyReturnVisitor(); | ||
node.accept(visitor); | ||
|
||
for (final element in visitor.nodes) { | ||
reporter.reportErrorForNode(code, element); | ||
} | ||
}); | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
lib/src/lints/prefer_early_return/visitors/prefer_early_return_visitor.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
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'; | ||
|
||
/// The AST visitor that will collect all unnecessary if statements | ||
class PreferEarlyReturnVisitor extends RecursiveAstVisitor<void> { | ||
final _nodes = <AstNode>[]; | ||
|
||
/// All unnecessary if statements and conditional expressions. | ||
Iterable<AstNode> get nodes => _nodes; | ||
|
||
@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; | ||
|
||
_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<IfStatement>, Statement?) _getStartIfStatements( | ||
BlockFunctionBody body, | ||
) { | ||
final List<IfStatement> ifStatements = []; | ||
for (final statement in body.block.statements) { | ||
if (statement is IfStatement) { | ||
ifStatements.add(statement); | ||
} else { | ||
return (ifStatements, statement); | ||
} | ||
} | ||
return (ifStatements, null); | ||
} | ||
|
||
bool _hasElseStatement(IfStatement node) { | ||
return node.elseStatement != null; | ||
} | ||
|
||
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; | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
lib/src/lints/prefer_early_return/visitors/return_statement_visitor.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import 'package:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer/dart/ast/visitor.dart'; | ||
|
||
/// The AST visitor that will collect every Return statement | ||
class ReturnStatementVisitor extends RecursiveAstVisitor<void> { | ||
final _nodes = <ReturnStatement>[]; | ||
|
||
/// All unnecessary return statements | ||
Iterable<ReturnStatement> get nodes => _nodes; | ||
|
||
@override | ||
void visitReturnStatement(ReturnStatement node) { | ||
super.visitReturnStatement(node); | ||
_nodes.add(node); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.