From 9f0d7e9b80cd6166634f2e015d4543fe062b578d Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Thu, 25 Jun 2026 16:13:35 +0300 Subject: [PATCH 1/2] refactor: migrate prefer_conditional_expressions --- lib/main.dart | 11 ++ .../prefer_conditional_expressions_fix.dart | 102 +++++----- ...er_conditional_expressions_parameters.dart | 7 + .../prefer_conditional_expressions_rule.dart | 80 ++++---- ...refer_conditional_expressions_visitor.dart | 138 +++++++------- .../analysis_options.yaml | 8 - ...tional_expressions_ignore_nested_test.dart | 17 -- .../prefer_conditional_expressions_test.dart | 47 ----- ...fer_conditional_expressions_rule_test.dart | 176 ++++++++++++++++++ 9 files changed, 345 insertions(+), 241 deletions(-) delete mode 100644 lint_test/prefer_conditional_expressions_ignore_nested_test/analysis_options.yaml delete mode 100644 lint_test/prefer_conditional_expressions_ignore_nested_test/prefer_conditional_expressions_ignore_nested_test.dart delete mode 100644 lint_test/prefer_conditional_expressions_test.dart create mode 100644 test/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule_test.dart diff --git a/lib/main.dart b/lib/main.dart index d9d338a8..a5a92c6c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,6 +14,8 @@ import 'package:solid_lints/src/lints/cyclomatic_complexity/cyclomatic_complexit import 'package:solid_lints/src/lints/double_literal_format/double_literal_format_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/fixes/double_literal_format_fix.dart'; import 'package:solid_lints/src/lints/function_lines_of_code/function_lines_of_code_rule.dart'; +import 'package:solid_lints/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart'; +import 'package:solid_lints/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart'; import 'package:solid_lints/src/lints/prefer_first/fixes/prefer_first_fix.dart'; import 'package:solid_lints/src/lints/prefer_first/prefer_first_rule.dart'; import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart'; @@ -42,6 +44,9 @@ class SolidLintsPlugin extends Plugin { AvoidUnnecessaryTypeAssertionsRule(); final doubleLiteralFormatRule = DoubleLiteralFormatRule(); final preferFirstRule = PreferFirstRule(); + final preferConditionalExpressionsRule = PreferConditionalExpressionsRule( + analysisOptionsLoader: analysisLoader, + ); final lintRules = [ AvoidFinalWithGetterRule(), @@ -65,6 +70,7 @@ class SolidLintsPlugin extends Plugin { ), UseNearestContextRule(), preferFirstRule, + preferConditionalExpressionsRule, // TODO: Add more lint rules and use analysisLoader // for rules that need parameters // For example: `CyclomaticComplexityRule(analysisLoader)` @@ -97,5 +103,10 @@ class SolidLintsPlugin extends Plugin { preferFirstRule.diagnosticCode, PreferFirstFix.new, ); + + registry.registerFixForRule( + preferConditionalExpressionsRule.diagnosticCode, + PreferConditionalExpressionsFix.new, + ); } } diff --git a/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart b/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart index 8cbf3033..63d94587 100644 --- a/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart +++ b/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart @@ -1,56 +1,55 @@ +import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; +import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/token.dart'; -import 'package:analyzer/diagnostic/diagnostic.dart'; -import 'package:analyzer/source/source_range.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:solid_lints/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart'; import 'package:solid_lints/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart'; /// A Quick fix for `prefer_conditional_expressions` rule -/// Suggests to remove unnecessary assertions -class PreferConditionalExpressionsFix extends DartFix { - final Expando _diagnosticsInfoExpando; +/// Suggests to convert simple if statements to conditional expressions +class PreferConditionalExpressionsFix extends ResolvedCorrectionProducer { + static const _fixComment = "Convert to conditional expression."; - /// A Quick fix for `prefer_conditional_expressions` rule - /// Suggests to remove unnecessary assertions - PreferConditionalExpressionsFix(this._diagnosticsInfoExpando); + /// Creates a new instance of [PreferConditionalExpressionsFix] + PreferConditionalExpressionsFix({required super.context}); @override - void run( - CustomLintResolver resolver, - ChangeReporter reporter, - CustomLintContext context, - Diagnostic diagnostic, - List others, - ) { - context.registry.addIfStatement((node) { - if (!diagnostic.sourceRange.intersects(node.sourceRange)) return; + FixKind get fixKind => const FixKind( + 'solid_lints.fix.${PreferConditionalExpressionsRule.lintName}', + DartFixKindPriority.standard, + _fixComment, + ); - final statementInfo = _diagnosticsInfoExpando[diagnostic]; - if (statementInfo == null) return; + @override + FixKind get multiFixKind => const FixKind( + 'solid_lints.fix.multi.${PreferConditionalExpressionsRule.lintName}', + DartFixKindPriority.standard, + '$_fixComment across files', + ); - final correction = _createCorrection(statementInfo); - if (correction == null) return; + @override + CorrectionApplicability get applicability => + CorrectionApplicability.automatically; - _addReplacement(reporter, statementInfo.statement, correction); - }); - } + @override + Future compute(ChangeBuilder builder) async { + final statement = node.thisOrAncestorOfType(); + if (statement == null) return; - void _addReplacement( - ChangeReporter reporter, - IfStatement node, - String correction, - ) { - final changeBuilder = reporter.createChangeBuilder( - message: "Convert to conditional expression.", - priority: 1, - ); + final statementInfo = StatementInfo.fromIfStatement(statement); + if (statementInfo == null) return; + + final correction = _createCorrection(statementInfo); + if (correction == null) return; - changeBuilder.addDartFileEdit((builder) { - builder.addSimpleReplacement( - SourceRange(node.offset, node.length), + await builder.addDartFileEdit( + file, + (builder) => builder.addSimpleReplacement( + statement.sourceRange, correction, - ); - }); + ), + ); } String? _createCorrection(StatementInfo info) { @@ -64,20 +63,7 @@ class PreferConditionalExpressionsFix extends DartFix { final target = thenStatement.leftHandSide; final firstExpression = thenStatement.rightHandSide; final secondExpression = elseStatement.rightHandSide; - - final thenStatementOperator = thenStatement.operator.type; - final elseStatementOperator = elseStatement.operator.type; - - if (_isAssignmentOperatorNotEq(thenStatementOperator) && - _isAssignmentOperatorNotEq(elseStatementOperator)) { - final prefix = thenStatement.leftHandSide; - final thenPart = - '$prefix ${thenStatementOperator.stringValue} $firstExpression'; - final elsePart = - '$prefix ${elseStatementOperator.stringValue} $secondExpression;'; - - return '$condition ? $thenPart : $elsePart'; - } + final op = thenStatement.operator.lexeme; final correctionForLiterals = _createCorrectionForLiterals( condition, @@ -85,7 +71,7 @@ class PreferConditionalExpressionsFix extends DartFix { secondExpression, ); - return '$target = $correctionForLiterals'; + return '$target $op $correctionForLiterals'; } if (thenStatement is ReturnStatement && elseStatement is ReturnStatement) { @@ -110,6 +96,9 @@ class PreferConditionalExpressionsFix extends DartFix { ) { if (firstExpression is BooleanLiteral && secondExpression is BooleanLiteral) { + if (firstExpression.value == secondExpression.value) { + return '${firstExpression.value};'; + } final isInverted = !firstExpression.value && secondExpression.value; return '${isInverted ? "!" : ""}$condition;'; @@ -117,7 +106,4 @@ class PreferConditionalExpressionsFix extends DartFix { return '$condition ? $firstExpression : $secondExpression;'; } - - bool _isAssignmentOperatorNotEq(TokenType token) => - token.isAssignmentOperator && token != TokenType.EQ; } diff --git a/lib/src/lints/prefer_conditional_expressions/models/prefer_conditional_expressions_parameters.dart b/lib/src/lints/prefer_conditional_expressions/models/prefer_conditional_expressions_parameters.dart index 4524e700..d4a3af58 100644 --- a/lib/src/lints/prefer_conditional_expressions/models/prefer_conditional_expressions_parameters.dart +++ b/lib/src/lints/prefer_conditional_expressions/models/prefer_conditional_expressions_parameters.dart @@ -20,6 +20,12 @@ class PreferConditionalExpressionsParameters { required this.ignoreNested, }); + /// Empty [PreferConditionalExpressionsParameters] model. + factory PreferConditionalExpressionsParameters.empty() => + const PreferConditionalExpressionsParameters( + ignoreNested: false, + ); + /// Method for creating from json data factory PreferConditionalExpressionsParameters.fromJson( Map json, @@ -28,3 +34,4 @@ class PreferConditionalExpressionsParameters { ignoreNested: json[_ignoreNestedConfig] as bool? ?? false, ); } + diff --git a/lib/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart b/lib/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart index 134f2250..b7d4cbd0 100644 --- a/lib/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart +++ b/lib/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart @@ -1,9 +1,8 @@ -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:solid_lints/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.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_conditional_expressions/models/prefer_conditional_expressions_parameters.dart'; import 'package:solid_lints/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; import 'package:solid_lints/src/models/solid_lint_rule.dart'; // Inspired by TSLint (https://palantir.github.io/tslint/rules/prefer-conditional-expression/) @@ -14,10 +13,11 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// ### Example config: /// /// ```yaml -/// custom_lint: -/// rules: -/// - prefer_conditional_expressions: -/// ignore_nested: true +/// plugins: +/// solid_lints: +/// diagnostics: +/// prefer_conditional_expressions: +/// ignore_nested: true /// ``` /// /// ### Example @@ -57,51 +57,43 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// ``` class PreferConditionalExpressionsRule extends SolidLintRule { - /// This lint rule represents the error if number of - /// parameters reaches the maximum value. + /// This lint rule represents the error when an if-else statement + /// can be simplified to a conditional expression. static const lintName = 'prefer_conditional_expressions'; - final _diagnosticsInfoExpando = Expando(); + static const _code = LintCode( + lintName, + 'Prefer conditional expression.', + ); - PreferConditionalExpressionsRule._(super.config); + @override + LintCode get diagnosticCode => _code; /// Creates a new instance of [PreferConditionalExpressionsRule] - /// based on the lint configuration. - factory PreferConditionalExpressionsRule.createRule( - CustomLintConfigs configs, - ) { - final config = RuleConfig( - configs: configs, - name: lintName, - paramsParser: PreferConditionalExpressionsParameters.fromJson, - problemMessage: (value) => 'Prefer conditional expression.', - ); - - return PreferConditionalExpressionsRule._(config); - } + PreferConditionalExpressionsRule({ + required super.analysisOptionsLoader, + }) : super.withParameters( + name: _code.lowerCaseName, + description: _code.problemMessage, + parametersParser: PreferConditionalExpressionsParameters.fromJson, + ); @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, ) { - context.registry.addCompilationUnit((node) { - final visitor = PreferConditionalExpressionsVisitor( - ignoreNested: config.parameters.ignoreNested, - ); - node.accept(visitor); + super.registerNodeProcessors(registry, context); - for (final element in visitor.statementsInfo) { - final diagnostic = reporter.atNode(element.statement, code); + final parameters = + getParametersForContext(context) ?? + PreferConditionalExpressionsParameters.empty(); - _diagnosticsInfoExpando[diagnostic] = element; - } - }); - } + final visitor = PreferConditionalExpressionsVisitor( + rule: this, + ignoreNested: parameters.ignoreNested, + ); - @override - List getFixes() => [ - PreferConditionalExpressionsFix(_diagnosticsInfoExpando), - ]; + registry.addCompilationUnit(this, visitor); + } } diff --git a/lib/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart b/lib/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart index c4df3ff4..06cc943c 100644 --- a/lib/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart +++ b/lib/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart @@ -23,21 +23,20 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:solid_lints/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart'; /// The AST visitor that will collect all if statements that can be simplified /// into conditional expressions. class PreferConditionalExpressionsVisitor extends RecursiveAstVisitor { - final _statementsInfo = []; - + final PreferConditionalExpressionsRule _rule; final bool _ignoreNested; - /// List of statement info that represents all simple if statements - Iterable get statementsInfo => _statementsInfo; - /// Creates instance of [PreferConditionalExpressionsVisitor] PreferConditionalExpressionsVisitor({ + required PreferConditionalExpressionsRule rule, required bool ignoreNested, - }) : _ignoreNested = ignoreNested; + }) : _rule = rule, + _ignoreNested = ignoreNested; @override void visitIfStatement(IfStatement node) { @@ -45,39 +44,89 @@ class PreferConditionalExpressionsVisitor extends RecursiveAstVisitor { if (_ignoreNested) { final visitor = _ConditionalsVisitor(); - node.visitChildren(visitor); + node.thenStatement.accept(visitor); + node.elseStatement?.accept(visitor); if (visitor.hasInnerConditionals) { return; } } - if (node.parent is! IfStatement && - node.elseStatement != null && - node.elseStatement is! IfStatement) { - _checkBothAssignment(node); - _checkBothReturn(node); + final info = StatementInfo.fromIfStatement(node); + if (info != null) { + _rule.reportAtNode(node); } } +} + +class _ConditionalsVisitor extends RecursiveAstVisitor { + bool hasInnerConditionals = false; + + @override + void visitConditionalExpression(ConditionalExpression node) { + hasInnerConditionals = true; + } +} + +/// Data class contains info required for fix +class StatementInfo { + /// If statement node + final IfStatement statement; + + /// Contents of if block + final AstNode unwrappedThenStatement; + + /// Contents of else block + final AstNode unwrappedElseStatement; + + /// Creates instance of an [StatementInfo] + const StatementInfo({ + required this.statement, + required this.unwrappedThenStatement, + required this.unwrappedElseStatement, + }); + + /// Factory constructor to create [StatementInfo] from [IfStatement] if it + /// can be simplified. + static StatementInfo? fromIfStatement(IfStatement statement) { + if (statement.parent is IfStatement || + statement.elseStatement == null || + statement.elseStatement is IfStatement) { + return null; + } - void _checkBothAssignment(IfStatement statement) { final thenAssignment = _getAssignmentExpression(statement.thenStatement); final elseAssignment = _getAssignmentExpression(statement.elseStatement); if (thenAssignment != null && elseAssignment != null && + thenAssignment.operator.type == elseAssignment.operator.type && _haveEqualNames(thenAssignment, elseAssignment)) { - _statementsInfo.add( - StatementInfo( - statement: statement, - unwrappedThenStatement: thenAssignment, - unwrappedElseStatement: elseAssignment, - ), + return StatementInfo( + statement: statement, + unwrappedThenStatement: thenAssignment, + unwrappedElseStatement: elseAssignment, + ); + } + + final thenReturn = _getReturnStatement(statement.thenStatement); + final elseReturn = _getReturnStatement(statement.elseStatement); + + if (thenReturn != null && + elseReturn != null && + thenReturn.expression != null && + elseReturn.expression != null) { + return StatementInfo( + statement: statement, + unwrappedThenStatement: thenReturn, + unwrappedElseStatement: elseReturn, ); } + + return null; } - AssignmentExpression? _getAssignmentExpression(Statement? statement) { + static AssignmentExpression? _getAssignmentExpression(Statement? statement) { if (statement is ExpressionStatement && statement.expression is AssignmentExpression) { return statement.expression as AssignmentExpression; @@ -90,7 +139,7 @@ class PreferConditionalExpressionsVisitor extends RecursiveAstVisitor { return null; } - bool _haveEqualNames( + static bool _haveEqualNames( AssignmentExpression thenAssignment, AssignmentExpression elseAssignment, ) => @@ -99,22 +148,7 @@ class PreferConditionalExpressionsVisitor extends RecursiveAstVisitor { (thenAssignment.leftHandSide as Identifier).name == (elseAssignment.leftHandSide as Identifier).name; - void _checkBothReturn(IfStatement statement) { - final thenReturn = _getReturnStatement(statement.thenStatement); - final elseReturn = _getReturnStatement(statement.elseStatement); - - if (thenReturn != null && elseReturn != null) { - _statementsInfo.add( - StatementInfo( - statement: statement, - unwrappedThenStatement: thenReturn, - unwrappedElseStatement: elseReturn, - ), - ); - } - } - - ReturnStatement? _getReturnStatement(Statement? statement) { + static ReturnStatement? _getReturnStatement(Statement? statement) { if (statement is ReturnStatement) { return statement; } @@ -126,33 +160,3 @@ class PreferConditionalExpressionsVisitor extends RecursiveAstVisitor { return null; } } - -class _ConditionalsVisitor extends RecursiveAstVisitor { - bool hasInnerConditionals = false; - - @override - void visitConditionalExpression(ConditionalExpression node) { - hasInnerConditionals = true; - - super.visitConditionalExpression(node); - } -} - -/// Data class contains info required for fix -class StatementInfo { - /// If statement node - final IfStatement statement; - - /// Contents of if block - final AstNode unwrappedThenStatement; - - /// Contents of else block - final AstNode unwrappedElseStatement; - - /// Creates instance of an [StatementInfo] - const StatementInfo({ - required this.statement, - required this.unwrappedThenStatement, - required this.unwrappedElseStatement, - }); -} diff --git a/lint_test/prefer_conditional_expressions_ignore_nested_test/analysis_options.yaml b/lint_test/prefer_conditional_expressions_ignore_nested_test/analysis_options.yaml deleted file mode 100644 index 750be4b4..00000000 --- a/lint_test/prefer_conditional_expressions_ignore_nested_test/analysis_options.yaml +++ /dev/null @@ -1,8 +0,0 @@ -analyzer: - plugins: - - ../custom_lint - -custom_lint: - rules: - - prefer_conditional_expressions: - ignore_nested: true diff --git a/lint_test/prefer_conditional_expressions_ignore_nested_test/prefer_conditional_expressions_ignore_nested_test.dart b/lint_test/prefer_conditional_expressions_ignore_nested_test/prefer_conditional_expressions_ignore_nested_test.dart deleted file mode 100644 index 150b9e4b..00000000 --- a/lint_test/prefer_conditional_expressions_ignore_nested_test/prefer_conditional_expressions_ignore_nested_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -// ignore_for_file: unused_local_variable -// ignore_for_file: cyclomatic_complexity -// ignore_for_file: no_equal_then_else - -/// Check the `prefer_conditional_expressions` rule ignore_nested option -void fun() { - int _result = 0; - - // Allowed because ignore_nested flag is enabled - if (1 > 0) { - if (true) { - _result = 1 > 0 ? 0 : 1; - } - } else { - _result = 0; - } -} diff --git a/lint_test/prefer_conditional_expressions_test.dart b/lint_test/prefer_conditional_expressions_test.dart deleted file mode 100644 index ee60f237..00000000 --- a/lint_test/prefer_conditional_expressions_test.dart +++ /dev/null @@ -1,47 +0,0 @@ -// ignore_for_file: unused_local_variable -// ignore_for_file: cyclomatic_complexity -// ignore_for_file: no_equal_then_else -// ignore_for_file: dead_code -// ignore_for_file: no_magic_number - -/// Check the `prefer_conditional_expressions` rule -void fun() { - int _result = 0; - - // expect_lint: prefer_conditional_expressions - if (true) { - _result = 1; - } else { - _result = 2; - } - - // expect_lint: prefer_conditional_expressions - if (1 > 0) - _result = 1; - else - _result = 2; - - // expect_lint: prefer_conditional_expressions - if (1 > 0) { - _result = 1 > 2 ? 2 : 1; - } else { - _result = 0; - } -} - -int someFun() { - // expect_lint: prefer_conditional_expressions - if (1 == 1) { - return 0; - } else { - return 1; - } -} - -int anotherFun() { - // expect_lint: prefer_conditional_expressions - if (1 > 0) - return 1; - else - return 2; -} diff --git a/test/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule_test.dart b/test/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule_test.dart new file mode 100644 index 00000000..6842643b --- /dev/null +++ b/test/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule_test.dart @@ -0,0 +1,176 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:analyzer_testing/utilities/utilities.dart'; +import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; +import 'package:solid_lints/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../../lints/auto_test_lint_offsets.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(PreferConditionalExpressionsRuleTest); + }); +} + +@reflectiveTest +class PreferConditionalExpressionsRuleTest extends AnalysisRuleTest + with AutoTestLintOffsets { + @override + void setUp() { + rule = PreferConditionalExpressionsRule( + analysisOptionsLoader: AnalysisOptionsLoader( + resourceProvider: resourceProvider, + ), + ); + super.setUp(); + } + + Future test_reports_on_simple_assignment() async { + await assertAutoDiagnostics(''' +void main() { + var x = 0; + ${expectLint('''if (x > 0) { + x = 1; + } else { + x = 2; + }''')} +} +'''); + } + + Future test_reports_on_simple_return() async { + await assertAutoDiagnostics(''' +int getVal(int x) { + ${expectLint('''if (x > 0) { + return 1; + } else { + return 2; + }''')} +} +'''); + } + + Future test_does_not_report_when_different_variables_assigned() async { + await assertNoDiagnostics(''' +void main() { + var x = 0; + var y = 0; + if (x > 0) { + x = 1; + } else { + y = 2; + } +} +'''); + } + + Future test_does_not_report_when_no_else_clause() async { + await assertNoDiagnostics(''' +void main() { + var x = 0; + if (x > 0) { + x = 1; + } +} +'''); + } + + Future test_reports_on_same_compound_assignment() async { + await assertAutoDiagnostics(''' +void main() { + var x = 0; + ${expectLint('''if (x > 0) { + x += 1; + } else { + x += 2; + }''')} +} +'''); + } + + Future test_does_not_report_on_different_compound_assignment() async { + await assertNoDiagnostics(''' +void main() { + var x = 0; + if (x > 0) { + x += 1; + } else { + x -= 2; + } +} +'''); + } + + Future test_does_not_report_on_mixed_assignment_operators() async { + await assertNoDiagnostics(''' +void main() { + var x = 0; + if (x > 0) { + x += 1; + } else { + x = 2; + } +} +'''); + } + + Future test_does_not_report_on_empty_returns() async { + await assertNoDiagnostics(''' +void test(bool c) { + if (c) { + return; + } else { + return; + } +} +'''); + } + + Future test_does_not_report_on_mixed_empty_return() async { + await assertNoDiagnostics(''' +dynamic test(bool c) { + if (c) { + return 1; + } else { + return; + } +} +'''); + } + + Future test_reports_on_nested_by_default() async { + await assertAutoDiagnostics(''' +void main() { + var x = 0; + ${expectLint('''if (x > 0) { + x = x > 2 ? 3 : 4; + } else { + x = 2; + }''')} +} +'''); + } + + Future test_does_not_report_on_nested_when_ignore_nested_true() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + '''${analysisOptionsContent(rules: [rule.name])} +plugins: + solid_lints: + diagnostics: + prefer_conditional_expressions: + ignore_nested: true +''', + ); + await assertNoDiagnostics(''' +void main() { + var x = 0; + if (x > 0) { + x = x > 2 ? 3 : 4; + } else { + x = 2; + } +} +'''); + } +} From b3feda76d55efe5b5e16bb938b653f1e36ff8b3e Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Thu, 25 Jun 2026 16:31:56 +0300 Subject: [PATCH 2/2] fix: add parentheses to inverted conditional expressions for complex conditions and update tests --- .../fixes/prefer_conditional_expressions_fix.dart | 11 ++++++++++- .../prefer_conditional_expressions_rule_test.dart | 12 ++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart b/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart index 63d94587..6717ee3f 100644 --- a/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart +++ b/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart @@ -100,8 +100,17 @@ class PreferConditionalExpressionsFix extends ResolvedCorrectionProducer { return '${firstExpression.value};'; } final isInverted = !firstExpression.value && secondExpression.value; + if (isInverted) { + final useParentheses = + condition is! Identifier && + condition is! PropertyAccess && + condition is! MethodInvocation && + condition is! IndexExpression && + condition is! ParenthesizedExpression; + return '${useParentheses ? '!($condition)' : '!$condition'};'; + } - return '${isInverted ? "!" : ""}$condition;'; + return '$condition;'; } return '$condition ? $firstExpression : $secondExpression;'; diff --git a/test/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule_test.dart b/test/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule_test.dart index 6842643b..724e0747 100644 --- a/test/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule_test.dart +++ b/test/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule_test.dart @@ -171,6 +171,18 @@ void main() { x = 2; } } +'''); + } + + Future test_reports_on_complex_condition_inversion() async { + await assertAutoDiagnostics(''' +bool test(int x, int y) { + ${expectLint('''if (x > 0 || y < 1) { + return false; + } else { + return true; + }''')} +} '''); } }