diff --git a/spec/ameba/rule/style/redundant_parentheses_spec.cr b/spec/ameba/rule/style/redundant_parentheses_spec.cr new file mode 100644 index 00000000..2968b975 --- /dev/null +++ b/spec/ameba/rule/style/redundant_parentheses_spec.cr @@ -0,0 +1,95 @@ +require "../../../spec_helper" + +module Ameba::Rule::Style + subject = RedundantParentheses.new + + describe RedundantParentheses do + {% for keyword in %w(if unless while until) %} + context "{{ keyword.id }}" do + it "reports if redundant parentheses are found" do + source = expect_issue subject, <<-CRYSTAL, keyword: {{ keyword }} + %{keyword} (foo > 10) + _{keyword} # ^^^^^^^^^^ error: Redundant parentheses + foo + end + CRYSTAL + + expect_correction source, <<-CRYSTAL + {{ keyword.id }} foo > 10 + foo + end + CRYSTAL + end + end + {% end %} + + context "case" do + it "reports if redundant parentheses are found" do + source = expect_issue subject, <<-CRYSTAL + case (foo = @foo) + # ^^^^^^^^^^^^ error: Redundant parentheses + when String then "string" + when Symbol then "symbol" + end + CRYSTAL + + expect_correction source, <<-CRYSTAL + case foo = @foo + when String then "string" + when Symbol then "symbol" + end + CRYSTAL + end + end + + context "properties" do + context "#exclude_ternary=" do + it "skips ternary control expressions by default" do + expect_no_issues subject, <<-CRYSTAL + (foo > bar) ? true : false + CRYSTAL + end + + it "allows to configure assignments" do + rule = Rule::Style::RedundantParentheses.new + rule.exclude_ternary = false + + expect_issue rule, <<-CRYSTAL + (foo > bar) ? true : false + # ^^^^^^^^^ error: Redundant parentheses + CRYSTAL + + expect_no_issues subject, <<-CRYSTAL + (foo && bar) ? true : false + CRYSTAL + + expect_no_issues subject, <<-CRYSTAL + (foo || bar) ? true : false + CRYSTAL + end + end + + context "#exclude_assignments=" do + it "reports assignments by default" do + expect_issue subject, <<-CRYSTAL + if (foo = @foo) + # ^^^^^^^^^^^^ error: Redundant parentheses + foo + end + CRYSTAL + end + + it "allows to configure assignments" do + rule = Rule::Style::RedundantParentheses.new + rule.exclude_assignments = true + + expect_no_issues rule, <<-CRYSTAL + if (foo = @foo) + foo + end + CRYSTAL + end + end + end + end +end diff --git a/src/ameba/rule/style/redundant_parentheses.cr b/src/ameba/rule/style/redundant_parentheses.cr new file mode 100644 index 00000000..15c5bf20 --- /dev/null +++ b/src/ameba/rule/style/redundant_parentheses.cr @@ -0,0 +1,61 @@ +module Ameba::Rule::Style + # A rule that disallows redundant parentheses around control expressions. + # + # For example, this is considered invalid: + # + # ``` + # if (foo == 42) + # do_something + # end + # ``` + # + # And should be replaced by the following: + # + # ``` + # if foo == 42 + # do_something + # end + # ``` + # + # YAML configuration example: + # + # ``` + # Style/RedundantParentheses: + # Enabled: true + # ExcludeTernary: true + # ExcludeAssignments: false + # ``` + class RedundantParentheses < Base + properties do + description "Disallows redundant parentheses around control expressions" + + exclude_ternary true + exclude_assignments false + end + + MSG = "Redundant parentheses" + + def test(source, node : Crystal::If | Crystal::Unless | Crystal::Case | Crystal::While | Crystal::Until) + is_ternary = node.is_a?(Crystal::If) && node.ternary? + + return if exclude_ternary && is_ternary + + return unless (cond = node.cond).is_a?(Crystal::Expressions) + return unless cond.keyword.paren? + + return unless exp = cond.single_expression? + + case exp + when Crystal::BinaryOp + return if is_ternary + when Crystal::Assign, Crystal::OpAssign + return if exclude_assignments + end + + issue_for cond, MSG do |corrector| + corrector.remove_trailing(cond, 1) + corrector.remove_leading(cond, 1) + end + end + end +end