diff --git a/lib/lints/no_equal_then_else/no_equal_then_else_rule.dart b/lib/lints/no_equal_then_else/no_equal_then_else_rule.dart new file mode 100644 index 00000000..067ed50f --- /dev/null +++ b/lib/lints/no_equal_then_else/no_equal_then_else_rule.dart @@ -0,0 +1,45 @@ +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:solid_lints/lints/no_equal_then_else/no_equal_then_else_visitor.dart'; +import 'package:solid_lints/models/rule_config.dart'; +import 'package:solid_lints/models/solid_lint_rule.dart'; + +// Inspired by PVS-Studio (https://www.viva64.com/en/w/v6004/) + +/// A `no-equal-then-else` rule which warns about +/// unnecessary if statements and conditional expressions +class NoEqualThenElseRule extends SolidLintRule { + /// The [LintCode] of this lint rule that represents the error if + /// 'if' statements or conditional expression is redundant + static const String lintName = 'no-equal-then-else'; + + NoEqualThenElseRule._(super.config); + + /// Creates a new instance of [NoEqualThenElseRule] + /// based on the lint configuration. + factory NoEqualThenElseRule.createRule(CustomLintConfigs configs) { + final rule = RuleConfig( + configs: configs, + name: lintName, + problemMessage: (value) => "Then and else branches are equal.", + ); + + return NoEqualThenElseRule._(rule); + } + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addCompilationUnit((node) { + final visitor = NoEqualThenElseVisitor(); + node.accept(visitor); + + for (final element in visitor.nodes) { + reporter.reportErrorForNode(code, element); + } + }); + } +} diff --git a/lib/lints/no_equal_then_else/no_equal_then_else_visitor.dart b/lib/lints/no_equal_then_else/no_equal_then_else_visitor.dart new file mode 100644 index 00000000..0ec18307 --- /dev/null +++ b/lib/lints/no_equal_then_else/no_equal_then_else_visitor.dart @@ -0,0 +1,54 @@ +// MIT License +// +// Copyright (c) 2020-2021 Dart Code Checker team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +/// The AST visitor that will collect all unnecessary if statements and +/// conditional expressions. +class NoEqualThenElseVisitor extends RecursiveAstVisitor { + final _nodes = []; + + /// All unnecessary if statements and conditional expressions. + Iterable get nodes => _nodes; + + @override + void visitIfStatement(IfStatement node) { + super.visitIfStatement(node); + + if (node.elseStatement != null && + node.elseStatement is! IfStatement && + node.thenStatement.toString() == node.elseStatement.toString()) { + _nodes.add(node); + } + } + + @override + void visitConditionalExpression(ConditionalExpression node) { + super.visitConditionalExpression(node); + + if (node.thenExpression.toString() == node.elseExpression.toString()) { + _nodes.add(node); + } + } +} diff --git a/lib/solid_lints.dart b/lib/solid_lints.dart index 13960c6d..eb09549c 100644 --- a/lib/solid_lints.dart +++ b/lib/solid_lints.dart @@ -14,6 +14,7 @@ import 'package:solid_lints/lints/double_literal_format/double_literal_format_ru import 'package:solid_lints/lints/function_lines_of_code/function_lines_of_code_metric.dart'; import 'package:solid_lints/lints/newline_before_return/newline_before_return_rule.dart'; import 'package:solid_lints/lints/no_empty_block/no_empty_block_rule.dart'; +import 'package:solid_lints/lints/no_equal_then_else/no_equal_then_else_rule.dart'; import 'package:solid_lints/lints/number_of_parameters/number_of_parameters_metric.dart'; import 'package:solid_lints/models/solid_lint_rule.dart'; @@ -39,6 +40,7 @@ class _SolidLints extends PluginBase { AvoidUnrelatedTypeAssertionsRule.createRule(configs), NewlineBeforeReturnRule.createRule(configs), NoEmptyBlockRule.createRule(configs), + NoEqualThenElseRule.createRule(configs), ]; // Return only enabled rules diff --git a/lint_test/analysis_options.yaml b/lint_test/analysis_options.yaml index 6d666af3..89028572 100644 --- a/lint_test/analysis_options.yaml +++ b/lint_test/analysis_options.yaml @@ -21,3 +21,4 @@ custom_lint: - avoid-unrelated-type-assertions - newline-before-return - no-empty-block + - no-equal-then-else diff --git a/lint_test/no_equal_then_else_test.dart b/lint_test/no_equal_then_else_test.dart new file mode 100644 index 00000000..74930dfd --- /dev/null +++ b/lint_test/no_equal_then_else_test.dart @@ -0,0 +1,28 @@ +// ignore_for_file: unused_local_variable +// ignore_for_file: cyclomatic_complexity + +/// Check the `no-equal-then-else` rule +void fun() { + final _valueA = 1; + final _valueB = 2; + + int _result = 0; + + // expect_lint: no-equal-then-else + if (_valueA == 1) { + _result = _valueA; + } else { + _result = _valueA; + } + + if (_valueA == 1) { + _result = _valueA; + } else { + _result = _valueB; + } + + // expect_lint: no-equal-then-else + _result = _valueA == 2 ? _valueA : _valueA; + + _result = _valueA == 2 ? _valueA : _valueB; +}