Skip to content

Commit

Permalink
Add strict locals linter
Browse files Browse the repository at this point in the history
  • Loading branch information
simonlevasseur committed Jan 17, 2025
1 parent 1cba325 commit 9c88a47
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 0 deletions.
48 changes: 48 additions & 0 deletions lib/erb_lint/linters/strict_locals.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

module ERBLint
module Linters
# Checks for final newlines at the end of a file.
class StrictLocals < Linter
include LinterRegistry

STRICT_LOCALS_REGEX = /\s+locals:\s+\((.*)\)/

def initialize(file_loader, config)
super
end

def run(processed_source)
file_content = processed_source.file_content
return if file_content.empty?

strict_locals_node = processed_source.ast.descendants(:erb).find do |erb_node|
indicator_node, _, code_node, _ = *erb_node

indicator_node_str = indicator_node&.deconstruct&.last
next unless indicator_node_str == "#"

code_node_str = code_node&.deconstruct&.last

code_node_str.match(STRICT_LOCALS_REGEX)
end

unless strict_locals_node
add_offense(
processed_source.to_source_range(0...processed_source.file_content.size),
<<~EOF.chomp,
Missing strict locals declaration.
Add <%# locals: () %> at the top of the file to enforce strict locals.
EOF
)
end
end

def autocorrect(_processed_source, offense)
lambda do |corrector|
corrector.insert_before(offense.source_range, "<%# locals: () %>\n")
end
end
end
end
end
75 changes: 75 additions & 0 deletions spec/erb_lint/linters/strict_locals_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

require "spec_helper"

describe ERBLint::Linters::StrictLocals do
let(:linter_config) { described_class.config_schema.new }
let(:file_loader) { ERBLint::FileLoader.new(".") }
let(:linter) { described_class.new(file_loader, linter_config) }
let(:processed_source) { ERBLint::ProcessedSource.new("file.html.erb", file) }

subject { linter.offenses }
before { linter.run(processed_source) }

context "when the ERB contains a strict locals declaration at the top of the file" do
let(:file) { <<~FILE }
<%# locals: (foo: "bar") %>
<div>
<%= foo %>
</div>
FILE

it "does not report any offenses" do
expect(subject.size).to(eq(0))
end
end

context "when the ERB contains a strict locals declaration anywhere else in the file" do
let(:file) { <<~FILE }
<div>
<%= foo %>
</div>
<%# locals: (foo: "bar") %>
FILE

it "does not report any offenses" do
expect(subject.size).to(eq(0))
end
end

context "when the ERB contains an empty strict locals declaration" do
let(:file) { <<~FILE }
<%# locals: () %>
<div>
<%= foo %>
</div>
FILE

it "does not report any offenses" do
expect(subject.size).to(eq(0))
end
end

context "when the ERB does not contain a strict locals declaration" do
let(:file) { <<~FILE }
<div>
<%= foo %>
</div>
FILE
let(:corrector) { ERBLint::Corrector.new(processed_source, subject) }
let(:corrected_content) { corrector.corrected_content }

it "reports an offense" do
expect(subject.size).to(eq(1))
end

it "reports the suggested fix" do
expect(subject.first.message).to(include("Missing strict locals declaration.\nAdd <%# locals: () %> \
at the top of the file to enforce strict locals."))
end

it "corrects the file" do
expect(corrected_content).to(eq("<%# locals: () %>\n<div>\n <%= foo %>\n</div>\n"))
end
end
end

0 comments on commit 9c88a47

Please sign in to comment.