diff --git a/lib/super_slim/linter/rules.rb b/lib/super_slim/linter/rules.rb index bc7f5d7..432ef58 100644 --- a/lib/super_slim/linter/rules.rb +++ b/lib/super_slim/linter/rules.rb @@ -319,7 +319,10 @@ def first_variable_by_line(code) return variable_by_line unless code.include?("@") Prism.lex(code).value.each do |token,| - variable_by_line[token.location.start_line] ||= token.value if token.type == :INSTANCE_VARIABLE + # An `@` that no name follows is lexed as one too: `@<%= name %>` in a generator's template. + next unless token.type == :INSTANCE_VARIABLE && token.value != "@" + + variable_by_line[token.location.start_line] ||= token.value end variable_by_line end diff --git a/test/linter_test.rb b/test/linter_test.rb index 0a33503..d29fdee 100644 --- a/test/linter_test.rb +++ b/test/linter_test.rb @@ -119,6 +119,14 @@ def test_instance_variables_wherever_a_partial_holds_ruby "12: @event", "14: @title"], messages end + def test_an_at_sign_without_a_name_is_no_instance_variable + source = "= form_for(@<%= singular_table_name %>) do |form|\n = form.input @field\n" + messages = SuperSlim::Linter.new.call("_form.html.slim", source).select { _1.rule == "InstanceVariables" } + .map { |offense| "#{offense.lineno}: #{offense.message}" } + + assert_equal ["2: Pass `@field` to the partial as a local"], messages + end + def test_unused_locals assert_offenses ["UnusedLocals:1"], "/# locals: (post:, form:)\n\nh2 = post.title\n", path: "_card.html.slim" read_everywhere = <<~SLIM