diff --git a/spec/ameba/rule/duplicated_when_spec.cr b/spec/ameba/rule/duplicated_when_spec.cr new file mode 100644 index 00000000..eef96b7e --- /dev/null +++ b/spec/ameba/rule/duplicated_when_spec.cr @@ -0,0 +1,118 @@ +require "../../spec_helper" + +module Ameba::Rule + describe DuplicatedWhen do + subject = DuplicatedWhen.new + + it "passes if there are no duplicated when conditions in the case" do + s = Source.new %( + case x + when "first" + do_something + when "second" + do_something_else + end + + case x + when Integer then :one + when Int32, String then :two + end + + case {value1, value2} + when {0, _} + when {_, 0} + end + + case x + when .odd? + when .even? + end + + case + when Integer then :one + when Integer | String then :two + end + + case x + when :one + when "one" + end + + case {value1, value2} + when {String, String} + end + ) + subject.catch(s).should be_valid + end + + it "fails if there is a duplicated when condition in the case" do + s = Source.new %( + case x + when .starts_with?("pattern1") + do_something1 + when .starts_with?("pattern2") + do_something2 + when .starts_with?("pattern1") + do_something3 + end + ) + subject.catch(s).should_not be_valid + end + + it "fails if there are multiple when conditions with nil" do + s = Source.new %( + case x + when nil + say_hello + when nil + say_nothing + end + ) + subject.catch(s).should_not be_valid + end + + it "fails if there are multiple whens with is_a? conditions" do + s = Source.new %( + case x + when String then say_hello + when Integer then say_nothing + when String then blah + end + ) + subject.catch(s).should_not be_valid + end + + it "fails if there are duplicated whens but in difference order" do + s = Source.new %( + case x + when String, Integer then something + when Integer then blah + end + ) + subject.catch(s).should_not be_valid + end + + it "fails if there are duplicated conditions in one when" do + s = Source.new %( + case x + when String, String then something + end + ) + subject.catch(s).should_not be_valid + end + + it "reports rule, location and message" do + s = Source.new %q( + case x + when "first" + when "first" + end + ), "source.cr" + subject.catch(s).should_not be_valid + error = s.errors.first + error.rule.should_not be_nil + error.location.to_s.should eq "source.cr:2:9" + error.message.should eq "Duplicated when conditions in case." + end + end +end diff --git a/src/ameba/rule/duplicated_when.cr b/src/ameba/rule/duplicated_when.cr new file mode 100644 index 00000000..fb87209e --- /dev/null +++ b/src/ameba/rule/duplicated_when.cr @@ -0,0 +1,51 @@ +module Ameba::Rule + # A rule that disallows duplicated when conditions in case. + # + # This is considered invalid: + # + # ``` + # case a + # when "first" + # do_something + # when "first" + # do_somehting_else + # end + # ``` + # + # And it should be written as follows: + # + # ``` + # case a + # when "first" + # do_something + # when "second" + # do_somehting_else + # end + # ``` + # + # YAML configuration example: + # + # ``` + # DuplicatedWhen: + # Enabled: true + # ``` + # + struct DuplicatedWhen < Base + def test(source) + AST::Visitor.new self, source + end + + def test(source, node : Crystal::Case) + return unless duplicated_whens?(node.whens) + + source.error self, node.location, "Duplicated when conditions in case." + end + + private def duplicated_whens?(whens) + whens.map(&.conds.map &.to_s) + .flatten + .group_by(&.itself) + .any? { |_, v| v.size > 1 } + end + end +end