diff --git a/spec/ameba/rule/style/is_a_nil_spec.cr b/spec/ameba/rule/style/is_a_nil_spec.cr new file mode 100644 index 00000000..a3bf0ee6 --- /dev/null +++ b/spec/ameba/rule/style/is_a_nil_spec.cr @@ -0,0 +1,45 @@ +require "../../../spec_helper" + +module Ameba::Rule::Style + describe IsANil do + subject = IsANil.new + + it "doesn't report if there are no is_a?(Nil) calls" do + s = Source.new %( + a = 1 + a.nil? + a.is_a?(NilLiteral) + a.is_a?(Custom::Nil) + ) + subject.catch(s).should be_valid + end + + it "reports if there is a call to is_a?(Nil) without receiver" do + s = Source.new %( + is_a?(Nil) + ) + subject.catch(s).should_not be_valid + end + + it "reports if there is a call to is_a?(Nil) with receiver" do + s = Source.new %( + a.is_a?(Nil) + ) + subject.catch(s).should_not be_valid + end + + it "reports rule, location and message" do + s = Source.new %( + nil.is_a? Nil + ), "source.cr" + subject.catch(s).should_not be_valid + s.issues.size.should eq 1 + + issue = s.issues.first + issue.rule.should_not be_nil + issue.location.to_s.should eq "source.cr:1:11" + issue.end_location.to_s.should eq "source.cr:1:13" + issue.message.should eq IsANil::MSG + end + end +end diff --git a/src/ameba/ast/visitors/node_visitor.cr b/src/ameba/ast/visitors/node_visitor.cr index 2384548b..47bb343d 100644 --- a/src/ameba/ast/visitors/node_visitor.cr +++ b/src/ameba/ast/visitors/node_visitor.cr @@ -4,6 +4,7 @@ module Ameba::AST # List of nodes to be visited by Ameba's rules. NODES = [ Alias, + IsA, Assign, Call, Block, diff --git a/src/ameba/rule/style/is_a_nil.cr b/src/ameba/rule/style/is_a_nil.cr new file mode 100644 index 00000000..18653bbc --- /dev/null +++ b/src/ameba/rule/style/is_a_nil.cr @@ -0,0 +1,38 @@ +module Ameba::Rule::Style + # A rule that disallows calls to `is_a?(Nil)` in favor of `nil?`. + # + # This is considered bad: + # + # ``` + # var.is_a? Nil + # ``` + # + # And needs to be written as: + # + # ``` + # var.nil? + # ``` + # + # YAML configuration example: + # + # ``` + # Style/IsANil: + # Enabled: true + # ``` + # + struct IsANil < Base + properties do + description "Disallows calls to `is_a?(Nil)` in favor of `nil?`" + end + + MSG = "Use `nil?` instead of `is_a?(Nil)`" + PATH_NIL_NAMES = %w(Nil) + + def test(source, node : Crystal::IsA) + return if node.nil_check? + + const = node.const + issue_for const, MSG if const.is_a?(Crystal::Path) && const.names == PATH_NIL_NAMES + end + end +end