From b7286dc6738d1790fe655254429b63933e11557f Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 18 Jan 2021 18:04:12 +0100 Subject: [PATCH] Add Performance/CompactAfterMap rule --- .../performance/compact_after_map_spec.cr | 50 +++++++++++++++++++ .../rule/performance/compact_after_map.cr | 48 ++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 spec/ameba/rule/performance/compact_after_map_spec.cr create mode 100644 src/ameba/rule/performance/compact_after_map.cr diff --git a/spec/ameba/rule/performance/compact_after_map_spec.cr b/spec/ameba/rule/performance/compact_after_map_spec.cr new file mode 100644 index 00000000..ad34c79d --- /dev/null +++ b/spec/ameba/rule/performance/compact_after_map_spec.cr @@ -0,0 +1,50 @@ +require "../../../spec_helper" + +module Ameba::Rule::Performance + subject = CompactAfterMap.new + + describe CompactAfterMap do + it "passes if there is no potential performance improvements" do + source = Source.new %( + (1..3).compact_map(&.itself) + ) + subject.catch(source).should be_valid + end + + it "passes if there is map followed by a bang call" do + source = Source.new %( + (1..3).map(&.itself).compact! + ) + subject.catch(source).should be_valid + end + + it "reports if there is map followed by compact call" do + source = Source.new %( + (1..3).map(&.itself).compact + ) + subject.catch(source).should_not be_valid + end + + context "macro" do + it "doesn't report in macro scope" do + source = Source.new %( + {{ [1, 2, 3].map(&.to_s).compact }} + ) + subject.catch(source).should be_valid + end + end + + it "reports rule, pos and message" do + s = Source.new %( + (1..3).map(&.itself).compact + ), "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:1:8" + issue.end_location.to_s.should eq "source.cr:1:29" + issue.message.should eq "Use `compact_map {...}` instead of `map {...}.compact`" + end + end +end diff --git a/src/ameba/rule/performance/compact_after_map.cr b/src/ameba/rule/performance/compact_after_map.cr new file mode 100644 index 00000000..f45c3206 --- /dev/null +++ b/src/ameba/rule/performance/compact_after_map.cr @@ -0,0 +1,48 @@ +module Ameba::Rule::Performance + # This rule is used to identify usage of `compact` calls that follow `map`. + # + # For example, this is considered inefficient: + # + # ``` + # %w[Alice Bob].map(&.match(/^A./)).compact + # ``` + # + # And can be written as this: + # + # ``` + # %w[Alice Bob].compact_map(&.match(/^A./)) + # ``` + # + # YAML configuration example: + # + # ``` + # Performance/CompactAfterMap + # Enabled: true + # ``` + struct CompactAfterMap < Base + properties do + description "Identifies usage of `compact` calls that follow `map`." + end + + COMPACT_NAME = "compact" + MAP_NAME = "map" + MSG = "Use `compact_map {...}` instead of `map {...}.compact`" + + def test(source) + AST::NodeVisitor.new self, source, skip: [ + Crystal::Macro, + Crystal::MacroExpression, + Crystal::MacroIf, + Crystal::MacroFor, + ] + end + + def test(source, node : Crystal::Call) + return unless node.name == COMPACT_NAME && (obj = node.obj) + return unless obj.is_a?(Crystal::Call) && obj.block + return unless obj.name == MAP_NAME + + issue_for obj.name_location, node.name_end_location, MSG + end + end +end