From a1b34eb7be2f481b12666c90ebac99b549c114a1 Mon Sep 17 00:00:00 2001 From: Vitalii Elenhaupt Date: Mon, 3 Sep 2018 22:45:14 +0300 Subject: [PATCH] New rule: Performance/AnyAfterFilter --- .../rule/performance/any_after_filter_spec.cr | 64 +++++++++++++++++++ .../rule/performance/any_after_filter.cr | 50 +++++++++++++++ .../rule/performance/size_after_filter.cr | 3 +- 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 spec/ameba/rule/performance/any_after_filter_spec.cr create mode 100644 src/ameba/rule/performance/any_after_filter.cr diff --git a/spec/ameba/rule/performance/any_after_filter_spec.cr b/spec/ameba/rule/performance/any_after_filter_spec.cr new file mode 100644 index 00000000..b17a7144 --- /dev/null +++ b/spec/ameba/rule/performance/any_after_filter_spec.cr @@ -0,0 +1,64 @@ +require "../../../spec_helper" + +module Ameba::Rule::Performance + subject = AnyAfterFilter.new + + describe AnyAfterFilter do + it "passes if there is no potential performance improvements" do + source = Source.new %( + [1, 2, 3].select { |e| e > 1 }.any?(&.zero?) + [1, 2, 3].reject { |e| e > 1 }.any?(&.zero?) + [1, 2, 3].select { |e| e > 1 } + [1, 2, 3].reject { |e| e > 1 } + [1, 2, 3].any? { |e| e > 1 } + ) + subject.catch(source).should be_valid + end + + it "reports if there is select followed by any? without a block" do + source = Source.new %( + [1, 2, 3].select { |e| e > 2 }.any? + ) + subject.catch(source).should_not be_valid + end + + it "reports if there is reject followed by any? without a block" do + source = Source.new %( + [1, 2, 3].reject { |e| e > 2 }.any? + ) + subject.catch(source).should_not be_valid + end + + it "does not report if any? calls contains a block" do + source = Source.new %( + [1, 2, 3].select { |e| e > 2 }.any?(&.zero?) + [1, 2, 3].reject { |e| e > 2 }.any?(&.zero?) + ) + subject.catch(source).should be_valid + end + + context "properties" do + it "allows to configure object_call_names" do + source = Source.new %( + [1, 2, 3].reject { |e| e > 2 }.any? + ) + rule = Rule::Performance::AnyAfterFilter.new + rule.filter_names = %w(select) + rule.catch(source).should be_valid + end + end + + it "reports rule, pos and message" do + s = Source.new %( + [1, 2, 3].reject { |e| e > 2 }.any? + ), "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:19" + issue.end_location.to_s.should eq "source.cr:2:44" + issue.message.should eq "Use `any? {...}` instead of `reject {...}.any?`" + end + end +end diff --git a/src/ameba/rule/performance/any_after_filter.cr b/src/ameba/rule/performance/any_after_filter.cr new file mode 100644 index 00000000..e041e6de --- /dev/null +++ b/src/ameba/rule/performance/any_after_filter.cr @@ -0,0 +1,50 @@ +module Ameba::Rule::Performance + # This rule is used to identify usage of `any?` calls that follow filters. + # + # For example, this is considered invalid: + # + # ``` + # [1, 2, 3].select { |e| e > 2 }.any? + # [1, 2, 3].reject { |e| e >= 2 }.any? + # ``` + # + # And it should be written as this: + # + # ``` + # [1, 2, 3].any? { |e| e > 2 } + # [1, 2, 3].any? { |e| e < 2 } + # ``` + # + # YAML configuration example: + # + # ``` + # Performance/AnyAfterFilter: + # Enabled: true + # FilterNames: + # - select + # - reject + # ``` + # + struct AnyAfterFilter < Base + ANY_NAME = "any?" + MSG = "Use `#{ANY_NAME} {...}` instead of `%s {...}.#{ANY_NAME}`" + + properties do + filter_names : Array(String) = %w(select reject) + description "Identifies usage of `any?` calls that follow filters." + end + + def test(source) + AST::NodeVisitor.new self, source + end + + def test(source, node : Crystal::Call) + return unless node.name == ANY_NAME && (obj = node.obj) + + if node.block.nil? && obj.is_a?(Crystal::Call) && + filter_names.includes?(obj.name) && !obj.block.nil? + issue_for obj.name_location, node.name_end_location, MSG % obj.name + end + end + end +end diff --git a/src/ameba/rule/performance/size_after_filter.cr b/src/ameba/rule/performance/size_after_filter.cr index e3035d7b..8127284b 100644 --- a/src/ameba/rule/performance/size_after_filter.cr +++ b/src/ameba/rule/performance/size_after_filter.cr @@ -33,7 +33,7 @@ module Ameba::Rule::Performance # struct SizeAfterFilter < Base SIZE_NAME = "size" - MSG = "Use `count {...}` instead of `%s {...}.#{SIZE_NAME}`." + MSG = "Use `count {...}` instead of `%s {...}.#{SIZE_NAME}`." properties do filter_names : Array(String) = %w(select reject) @@ -49,7 +49,6 @@ module Ameba::Rule::Performance if obj.is_a?(Crystal::Call) && filter_names.includes?(obj.name) && !obj.block.nil? - issue_for obj.name_location, node.name_end_location, MSG % obj.name end end