New rule: Performance/Count

This commit is contained in:
Vitalii Elenhaupt 2018-09-03 01:02:51 +03:00
parent f4680a75f4
commit 446f557c23
No known key found for this signature in database
GPG key ID: 7558EF3A4056C706
2 changed files with 115 additions and 0 deletions

View file

@ -0,0 +1,67 @@
require "../../../spec_helper"
module Ameba::Rule::Performance
subject = Count.new
describe Count do
it "passes if there is no potential performance improvements" do
source = Source.new %(
[1, 2, 3].select { |e| e > 2 }
[1, 2, 3].reject { |e| e < 2 }
[1, 2, 3].count { |e| e > 2 && e.odd? }
[1, 2, 3].count { |e| e < 2 && e.even? }
User.select("field AS name").count
Company.select(:value).count
)
subject.catch(source).should be_valid
end
it "reports if there is a select followed by size" do
source = Source.new %(
[1, 2, 3].select { |e| e > 2 }.size
)
subject.catch(source).should_not be_valid
end
it "reports if there is a reject followed by size" do
source = Source.new %(
[1, 2, 3].reject { |e| e < 2 }.size
)
subject.catch(source).should_not be_valid
end
it "reports if a block shorthand used" do
source = Source.new %(
[1, 2, 3].reject(&.empty?).size
)
subject.catch(source).should_not be_valid
end
context "properties" do
it "allows to configure object caller names" do
source = Source.new %(
[1, 2, 3].reject(&.empty?).size
)
rule = Rule::Performance::Count.new
rule.object_call_names = %w(select)
rule.catch(source).should be_valid
end
end
it "reports rule, pos and message" do
s = Source.new %(
File.read(path)
.split("\n")
.reject(&.empty?)
.size
), "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:9"
issue.message.should eq "Use `count {...}` instead of `reject {...}.size`."
end
end
end

View file

@ -0,0 +1,48 @@
module Ameba::Rule::Performance
# This rule is used to identify usage of `size` calls that follow to object
# caller names `select` and `reject`.
#
# For example, this is considered invalid:
#
# ```
# [1, 2, 3].select { |e| e > 2 }.size
# [1, 2, 3].reject { |e| e < 2 }.size
# [1, 2, 3].select(&.< 2).size
# [0, 1, 2].reject(&.zero?).size
# ```
#
# And it should be written as this:
#
# ```
# [1, 2, 3].count { |e| e > 2 }
# [1, 2, 3].count { |e| e < 2 }
# [1, 2, 3].count(&.< 2)
# [0, 1, 2].count(&.zero?)
# ```
#
struct Count < Base
SIZE_CALL_NAME = "size"
MSG = "Use `count {...}` instead of `%s {...}.#{SIZE_CALL_NAME}`."
properties do
object_call_names : Array(String) = %w(select reject)
description "Identifies usage of `size` calls that follow to object \
caller names (`select`/`reject` by default)."
end
def test(source)
AST::NodeVisitor.new self, source
end
def test(source, node : Crystal::Call)
return unless node.name == SIZE_CALL_NAME && (obj = node.obj)
if obj.is_a?(Crystal::Call) &&
object_call_names.includes?(obj.name) && !obj.block.nil?
issue_for obj, MSG % obj.name
end
end
end
end