diff --git a/spec/ameba/ast/util_spec.cr b/spec/ameba/ast/util_spec.cr index 08bd3497..4576a4ef 100644 --- a/spec/ameba/ast/util_spec.cr +++ b/spec/ameba/ast/util_spec.cr @@ -35,5 +35,15 @@ module Ameba::AST subject.literal?(Crystal::Nop).should be_false end end + + describe "#string_literal?" do + it "returns true if node is a string literal" do + subject.string_literal?(Crystal::StringLiteral.new "").should be_true + end + + it "returns false if node is not a string literal" do + subject.string_literal?(Crystal::Nop.new).should be_false + end + end end end diff --git a/spec/ameba/rules/literal_in_interpolation_spec.cr b/spec/ameba/rules/literal_in_interpolation_spec.cr new file mode 100644 index 00000000..2977845e --- /dev/null +++ b/spec/ameba/rules/literal_in_interpolation_spec.cr @@ -0,0 +1,41 @@ +require "../../spec_helper" + +module Ameba::Rules + subject = LiteralInInterpolation.new + + describe LiteralInInterpolation do + it "passes with good interpolation examples" do + s = Source.new %q( + name = "Ary" + "Hello, #{name}" + + "#{name}" + + "Name size: #{name.size}" + ) + subject.catch(s).valid?.should be_true + end + + it "fails if there is useless interpolation" do + [ + %q("#{:Ary}"), + %q("#{[1, 2, 3]}"), + %q("#{true}"), + %q("#{false}"), + %q("here are #{4} cats"), + ].each do |str| + subject.catch(Source.new str).valid?.should be_false + end + end + + it "reports rule, pos and message" do + s = Source.new %q("#{4}") + subject.catch(s).valid?.should be_false + + error = s.errors.first + error.rule.should_not be_nil + error.pos.should eq 1 + error.message.should eq "Literal value found in interpolation" + end + end +end diff --git a/src/ameba/ast/traverse.cr b/src/ameba/ast/traverse.cr index 6b3554fa..7be3ed5f 100644 --- a/src/ameba/ast/traverse.cr +++ b/src/ameba/ast/traverse.cr @@ -5,6 +5,7 @@ module Ameba::AST Call, Case, If, + StringInterpolation, Unless, ] diff --git a/src/ameba/ast/util.cr b/src/ameba/ast/util.cr index 6e6b4970..9bd93143 100644 --- a/src/ameba/ast/util.cr +++ b/src/ameba/ast/util.cr @@ -2,4 +2,8 @@ module Ameba::AST::Util def literal?(node) node.try &.class.name.ends_with? "Literal" end + + def string_literal?(node) + node.is_a? Crystal::StringLiteral + end end diff --git a/src/ameba/rules/literal_in_interpolation.cr b/src/ameba/rules/literal_in_interpolation.cr new file mode 100644 index 00000000..c3004410 --- /dev/null +++ b/src/ameba/rules/literal_in_interpolation.cr @@ -0,0 +1,30 @@ +module Ameba::Rules + # A rule that disallows useless string interpolations + # that contain a literal value instead of a variable or function. + # + # For example: + # + # ``` + # "Hello, #{:Ary}" + # "The are #{4} cats" + # ``` + # + struct LiteralInInterpolation < Rule + include AST::Util + + def test(source) + AST::StringInterpolationVisitor.new self, source + end + + def test(source, node : Crystal::StringInterpolation) + has_literal = node.expressions.any? do |e| + !string_literal?(e) && literal?(e) + end + + return unless has_literal + + source.error self, node.location.try &.line_number, + "Literal value found in interpolation" + end + end +end