From 9515e624c351798f3923b070b8cf8b87362ce2fe Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 30 Oct 2022 19:49:15 +0100 Subject: [PATCH 1/2] Add `Lint/NotNilWithNoBang` rule --- .../rule/lint/not_nil_with_no_bang_spec.cr | 75 +++++++++++++++++++ src/ameba/rule/lint/not_nil_with_no_bang.cr | 61 +++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 spec/ameba/rule/lint/not_nil_with_no_bang_spec.cr create mode 100644 src/ameba/rule/lint/not_nil_with_no_bang.cr diff --git a/spec/ameba/rule/lint/not_nil_with_no_bang_spec.cr b/spec/ameba/rule/lint/not_nil_with_no_bang_spec.cr new file mode 100644 index 00000000..17ef16eb --- /dev/null +++ b/spec/ameba/rule/lint/not_nil_with_no_bang_spec.cr @@ -0,0 +1,75 @@ +require "../../../spec_helper" + +module Ameba::Rule::Lint + subject = NotNilWithNoBang.new + + describe NotNilWithNoBang do + it "passes for valid cases" do + expect_no_issues subject, <<-CRYSTAL + (1..3).index(1).not_nil!(:foo) + (1..3).index { |i| i > 2 }.not_nil!(:foo) + (1..3).find { |i| i > 2 }.not_nil!(:foo) + CRYSTAL + end + + it "reports if there is an `index` call followed by `not_nil!`" do + source = expect_issue subject, <<-CRYSTAL + (1..3).index(1).not_nil! + # ^^^^^^^^^^^^^^^^^ error: Use `index! {...}` instead of `index {...}.not_nil!` + CRYSTAL + + expect_correction source, <<-CRYSTAL + (1..3).index!(1) + CRYSTAL + end + + it "reports if there is an `index` call with block followed by `not_nil!`" do + source = expect_issue subject, <<-CRYSTAL + (1..3).index { |i| i > 2 }.not_nil! + # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `index! {...}` instead of `index {...}.not_nil!` + CRYSTAL + + expect_correction source, <<-CRYSTAL + (1..3).index! { |i| i > 2 } + CRYSTAL + end + + it "reports if there is a `find` call with block followed by `not_nil!`" do + source = expect_issue subject, <<-CRYSTAL + (1..3).find { |i| i > 2 }.not_nil! + # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find! {...}` instead of `find {...}.not_nil!` + CRYSTAL + + expect_correction source, <<-CRYSTAL + (1..3).find! { |i| i > 2 } + CRYSTAL + end + + it "passes if there is a `find` call without block followed by `not_nil!`" do + expect_no_issues subject, <<-CRYSTAL + (1..3).find(1).not_nil! + CRYSTAL + end + + context "macro" do + it "doesn't report in macro scope" do + expect_no_issues subject, <<-CRYSTAL + {{ [1, 2, 3].index(1).not_nil! }} + CRYSTAL + end + end + + it "reports rule, pos and message" do + s = Source.new %( + (1..3).index(1).not_nil! + ), "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:24" + issue.message.should eq "Use `index! {...}` instead of `index {...}.not_nil!`" + end + end +end diff --git a/src/ameba/rule/lint/not_nil_with_no_bang.cr b/src/ameba/rule/lint/not_nil_with_no_bang.cr new file mode 100644 index 00000000..a1e55a30 --- /dev/null +++ b/src/ameba/rule/lint/not_nil_with_no_bang.cr @@ -0,0 +1,61 @@ +module Ameba::Rule::Lint + # This rule is used to identify usage of `index/find` calls followed by `not_nil!`. + # + # For example, this is considered a code smell: + # + # ``` + # %w[Alice Bob].find(&.match(/^A./)).not_nil! + # ``` + # + # And can be written as this: + # + # ``` + # %w[Alice Bob].find!(&.match(/^A./)) + # ``` + # + # YAML configuration example: + # + # ``` + # Lint/NotNilWithNoBang: + # Enabled: true + # ``` + class NotNilWithNoBang < Base + include AST::Util + + properties do + description "Identifies usage of `index/find` calls followed by `not_nil!`" + end + + BLOCK_CALL_NAMES = %w(index find) + CALL_NAMES = %w(index) + + NOT_NIL_NAME = "not_nil!" + MSG = "Use `%s! {...}` instead of `%s {...}.not_nil!`" + + 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 == NOT_NIL_NAME && node.args.empty? + return unless (obj = node.obj).is_a?(Crystal::Call) + return unless obj.name.in?(obj.block ? BLOCK_CALL_NAMES : CALL_NAMES) + + return unless name_location = obj.name_location + return unless name_location_end = name_end_location(obj) + return unless end_location = name_end_location(node) + + msg = MSG % {obj.name, obj.name} + + issue_for name_location, end_location, msg do |corrector| + corrector.insert_after(name_location_end, '!') + corrector.remove_trailing(node, ".not_nil!".size) + end + end + end +end From 63407c1bd15a8cca2af1642847ca6f941e6dba90 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Tue, 1 Nov 2022 02:13:04 +0100 Subject: [PATCH 2/2] Rename to `Lint/NotNilAfterNoBang` --- ...nil_with_no_bang_spec.cr => not_nil_after_no_bang_spec.cr} | 4 ++-- .../{not_nil_with_no_bang.cr => not_nil_after_no_bang.cr} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename spec/ameba/rule/lint/{not_nil_with_no_bang_spec.cr => not_nil_after_no_bang_spec.cr} (97%) rename src/ameba/rule/lint/{not_nil_with_no_bang.cr => not_nil_after_no_bang.cr} (96%) diff --git a/spec/ameba/rule/lint/not_nil_with_no_bang_spec.cr b/spec/ameba/rule/lint/not_nil_after_no_bang_spec.cr similarity index 97% rename from spec/ameba/rule/lint/not_nil_with_no_bang_spec.cr rename to spec/ameba/rule/lint/not_nil_after_no_bang_spec.cr index 17ef16eb..521b79ef 100644 --- a/spec/ameba/rule/lint/not_nil_with_no_bang_spec.cr +++ b/spec/ameba/rule/lint/not_nil_after_no_bang_spec.cr @@ -1,9 +1,9 @@ require "../../../spec_helper" module Ameba::Rule::Lint - subject = NotNilWithNoBang.new + subject = NotNilAfterNoBang.new - describe NotNilWithNoBang do + describe NotNilAfterNoBang do it "passes for valid cases" do expect_no_issues subject, <<-CRYSTAL (1..3).index(1).not_nil!(:foo) diff --git a/src/ameba/rule/lint/not_nil_with_no_bang.cr b/src/ameba/rule/lint/not_nil_after_no_bang.cr similarity index 96% rename from src/ameba/rule/lint/not_nil_with_no_bang.cr rename to src/ameba/rule/lint/not_nil_after_no_bang.cr index a1e55a30..d222c43e 100644 --- a/src/ameba/rule/lint/not_nil_with_no_bang.cr +++ b/src/ameba/rule/lint/not_nil_after_no_bang.cr @@ -16,10 +16,10 @@ module Ameba::Rule::Lint # YAML configuration example: # # ``` - # Lint/NotNilWithNoBang: + # Lint/NotNilAfterNoBang: # Enabled: true # ``` - class NotNilWithNoBang < Base + class NotNilAfterNoBang < Base include AST::Util properties do