diff --git a/spec/ameba/rule/lint/redundant_with_object_spec.cr b/spec/ameba/rule/lint/redundant_with_object_spec.cr new file mode 100644 index 00000000..f6adc130 --- /dev/null +++ b/spec/ameba/rule/lint/redundant_with_object_spec.cr @@ -0,0 +1,83 @@ +require "../../../spec_helper" + +module Ameba::Rule::Lint + describe RedundantWithObject do + subject = RedundantWithObject.new + + it "does not report if there is index argument" do + s = Source.new %( + collection.each_with_object(0) do |e, obj| + obj += i + end + ) + subject.catch(s).should be_valid + end + + it "reports if there is not index argument" do + s = Source.new %( + collection.each_with_object(0) do |e| + e += 1 + end + ) + subject.catch(s).should_not be_valid + end + + it "reports if there is underscored index argument" do + s = Source.new %( + collection.each_with_object(0) do |e, _| + e += 1 + end + ) + subject.catch(s).should_not be_valid + end + + it "reports if there is no args" do + s = Source.new %( + collection.each_with_object(0) do + puts :nothing + end + ) + subject.catch(s).should_not be_valid + end + + it "does not report if there is no block" do + s = Source.new %( + collection.each_with_object(0) + ) + subject.catch(s).should be_valid + end + + it "does not report if first argument is underscored" do + s = Source.new %( + collection.each_with_object(0) do |_, obj| + puts i + end + ) + subject.catch(s).should be_valid + end + + it "does not report if there are more than 2 args" do + s = Source.new %( + tup.each_with_object(0) do |key, value, obj| + puts i + end + ) + subject.catch(s).should be_valid + end + + it "reports rule, location and message" do + s = Source.new %( + def valid? + collection.each_with_object(0) do |e| + end + end + ), "source.cr" + subject.catch(s).should_not be_valid + issue = s.issues.first + issue.rule.should_not be_nil + issue.location.to_s.should eq "source.cr:2:14" + issue.end_location.to_s.should eq "source.cr:2:30" + issue.message.should eq "Use each instead of each_with_object" + end + end +end diff --git a/src/ameba/rule/lint/redundant_with_object.cr b/src/ameba/rule/lint/redundant_with_object.cr new file mode 100644 index 00000000..8d10f94c --- /dev/null +++ b/src/ameba/rule/lint/redundant_with_object.cr @@ -0,0 +1,55 @@ +module Ameba::Rule::Lint + # A rule that disallows redundant `each_with_object` calls. + # + # For example, this is considered invalid: + # + # ``` + # collection.each_with_object(0) do |e| + # # ... + # end + # + # collection.each_with_object(0) do |e, _| + # # ... + # end + # ``` + # + # and it should be written as follows: + # + # ``` + # collection.each do |e| + # # ... + # end + # ``` + # + # YAML configuration example: + # + # ``` + # Lint/RedundantWithObject: + # Enabled: true + # ``` + # + struct RedundantWithObject < Base + properties do + description "Disallows redundant `with_object` calls" + end + + def test(source) + AST::NodeVisitor.new self, source + end + + def test(source, node : Crystal::Call) + return if node.name != "each_with_object" || + node.args.size != 1 || + node.block.nil? || + with_index_arg?(node.block.not_nil!) + + issue_for node.name_location, + node.name_end_location, + "Use each instead of each_with_object" + end + + private def with_index_arg?(block : Crystal::Block) + block.args.size >= 2 && block.args.last.name != "_" + end + end +end