Skip to content

Commit fda7f65

Browse files
Add strict locals linter
1 parent 1cba325 commit fda7f65

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

lib/erb_lint/linters/strict_locals.rb

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen_string_literal: true
2+
3+
module ERBLint
4+
module Linters
5+
# Checks for final newlines at the end of a file.
6+
class StrictLocals < Linter
7+
include LinterRegistry
8+
9+
STRICT_LOCALS_REGEX = /\s+locals:\s+\((.*)\)/
10+
11+
def initialize(file_loader, config)
12+
super
13+
end
14+
15+
def run(processed_source)
16+
file_content = processed_source.file_content
17+
return if file_content.empty?
18+
19+
strict_locals_node = processed_source.ast.descendants(:erb).find do |erb_node|
20+
indicator_node, _, code_node, _ = *erb_node
21+
22+
indicator_node_str = indicator_node&.deconstruct&.last
23+
next unless indicator_node_str == "#"
24+
25+
code_node_str = code_node&.deconstruct&.last
26+
27+
code_node_str.match(STRICT_LOCALS_REGEX)
28+
end
29+
30+
unless strict_locals_node
31+
add_offense(
32+
processed_source.to_source_range(0...processed_source.file_content.size),
33+
<<~EOF.chomp,
34+
Missing strict locals declaration.
35+
Add <%# locals: () %> at the top of the file to enforce strict locals.
36+
EOF
37+
)
38+
end
39+
end
40+
41+
def autocorrect(_processed_source, offense)
42+
lambda do |corrector|
43+
corrector.insert_before(offense.source_range, "<%# locals: () %>\n")
44+
end
45+
end
46+
end
47+
end
48+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
5+
describe ERBLint::Linters::StrictLocals do
6+
let(:linter_config) { described_class.config_schema.new }
7+
let(:file_loader) { ERBLint::FileLoader.new(".") }
8+
let(:linter) { described_class.new(file_loader, linter_config) }
9+
let(:processed_source) { ERBLint::ProcessedSource.new("file.html.erb", file) }
10+
11+
subject { linter.offenses }
12+
before { linter.run(processed_source) }
13+
14+
context "when the ERB contains a strict locals declaration at the top of the file" do
15+
let(:file) { <<~FILE }
16+
<%# locals: (foo: "bar") %>
17+
<div>
18+
<%= foo %>
19+
</div>
20+
FILE
21+
22+
it "does not report any offenses" do
23+
expect(subject.size).to(eq(0))
24+
end
25+
end
26+
27+
context "when the ERB contains a strict locals declaration anywhere else in the file" do
28+
let(:file) { <<~FILE }
29+
<div>
30+
<%= foo %>
31+
</div>
32+
<%# locals: (foo: "bar") %>
33+
FILE
34+
35+
it "does not report any offenses" do
36+
expect(subject.size).to(eq(0))
37+
end
38+
end
39+
40+
context "when the ERB contains an empty strict locals declaration" do
41+
let(:file) { <<~FILE }
42+
<%# locals: () %>
43+
<div>
44+
<%= foo %>
45+
</div>
46+
FILE
47+
48+
it "does not report any offenses" do
49+
expect(subject.size).to(eq(0))
50+
end
51+
end
52+
53+
context "when the ERB does not contain a strict locals declaration" do
54+
let(:file) { <<~FILE }
55+
<div>
56+
<%= foo %>
57+
</div>
58+
FILE
59+
let(:corrector) { ERBLint::Corrector.new(processed_source, subject) }
60+
let(:corrected_content) { corrector.corrected_content }
61+
62+
it "reports an offense" do
63+
expect(subject.size).to(eq(1))
64+
end
65+
66+
it "reports the suggested fix" do
67+
expect(subject.first.message).to(include("Missing strict locals declaration.\nAdd <%# locals: () %> at the top of the file to enforce strict locals."))
68+
end
69+
70+
it "corrects the file" do
71+
expect(corrected_content).to(eq("<%# locals: () %>\n<div>\n <%= foo %>\n</div>\n"))
72+
end
73+
end
74+
end

0 commit comments

Comments
 (0)