Skip to content

Commit a548083

Browse files
authored
Merge pull request #47 from msalway/heredoc
Refactor heredoc indent calculations
2 parents f0f1240 + f34c6b5 commit a548083

File tree

5 files changed

+229
-11
lines changed

5 files changed

+229
-11
lines changed

lib/puppet-lint/plugins/check_strict_indent.rb

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,19 @@ def match(tokens)
77
RBRACE: :LBRACE,
88
RBRACK: :LBRACK,
99
RPAREN: :LPAREN,
10-
HEREDOC: :HEREDOC_OPEN,
11-
HEREDOC_POST: :HEREDOC_OPEN,
1210
}
1311
open = {
1412
LBRACE: [],
1513
LBRACK: [],
1614
LPAREN: [],
17-
HEREDOC_OPEN: [],
1815
}
1916

2017
matches = {}
2118

2219
tokens.each do |token|
23-
if %i[LBRACE LBRACK LPAREN HEREDOC_OPEN].include?(token.type)
20+
if %i[LBRACE LBRACK LPAREN].include?(token.type)
2421
open[token.type] << token
25-
elsif %i[RBRACE RBRACK RPAREN HEREDOC HEREDOC_POST].include?(token.type)
22+
elsif %i[RBRACE RBRACK RPAREN].include?(token.type)
2623
match = open[opening_token[token.type]].pop
2724
unless match.nil?
2825
matches[token] = match
@@ -138,13 +135,31 @@ def check
138135
end
139136

140137
# get actual indent
141-
actual = 0
142-
actual = if token.next_token.type == :INDENT
138+
actual = case token.next_token.type
139+
when :INDENT
143140
token.next_token.value.length
144-
elsif !token.prev_token.nil? and token.prev_token.type == :HEREDOC
145-
token.prev_token.value.split("\n").last.length
146-
elsif !token.prev_token.nil? and token.prev_token.type == :HEREDOC_OPEN
147-
next_token.prev_token.value.split("\n").last.length
141+
when :HEREDOC
142+
# Lines containing heredocs have no indent token as the indent is consumed by the heredoc token.
143+
# However the last line of the token value is the whitespace before the pipe in the termination line.
144+
# We use the length of this to get the indent.
145+
if token.next_token.value.end_with?("\n")
146+
0
147+
else
148+
token.next_token.value.split("\n").last.length
149+
end
150+
when :HEREDOC_PRE
151+
# For interpolated heredocs the pipe whitespace is in the HEREDOC_POST token so we need scan forward
152+
# to this and get its length.
153+
next_token = token.next_token
154+
while !next_token.nil? and next_token.type != :NEWLINE and next_token.type != :HEREDOC_POST
155+
next_token = next_token.next_token
156+
end
157+
if next_token.type == :HEREDOC_POST
158+
next_token.value.split("\n").last.length
159+
else
160+
# Should never get here return zero if we do
161+
0
162+
end
148163
else
149164
0
150165
end
@@ -164,6 +179,7 @@ def check
164179
column: token.next_token.column,
165180
token: token.next_token,
166181
indent: expected,
182+
actual: actual,
167183
}
168184
end
169185
end
@@ -172,11 +188,50 @@ def fix(problem)
172188
char_for_indent = ' '
173189
if %i[INDENT WHITESPACE].include?(problem[:token].type)
174190
problem[:token].value = char_for_indent * problem[:indent]
191+
elsif problem[:token].type == :HEREDOC
192+
change = problem[:indent] - problem[:actual]
193+
indent_heredoc(problem[:token], change)
194+
elsif problem[:token].type == :HEREDOC_PRE
195+
change = problem[:indent] - problem[:actual]
196+
indent_heredoc(problem[:token], change)
197+
next_token = problem[:token].next_token
198+
while !next_token.nil? and next_token.type != :HEREDOC_POST
199+
indent_heredoc(next_token, change) if next_token.type == :HEREDOC_MID
200+
next_token = next_token.next_token
201+
end
202+
indent_heredoc(next_token, change) if next_token.type == :HEREDOC_POST
175203
else
176204
tokens.insert(
177205
tokens.find_index(problem[:token]),
178206
PuppetLint::Lexer::Token.new(:INDENT, char_for_indent * problem[:indent], problem[:line], problem[:column]),
179207
)
180208
end
181209
end
210+
211+
def map_heredoc_lines(value, change, skip)
212+
char_for_indent = ' '
213+
value.split("\n").map! do |line|
214+
if skip or line.empty?
215+
skip = false
216+
line
217+
elsif change < 0
218+
line[-change..]
219+
else
220+
(char_for_indent * change) + line
221+
end
222+
end.join("\n")
223+
end
224+
225+
def indent_heredoc(token, change)
226+
case token.type
227+
when :HEREDOC
228+
token.raw = map_heredoc_lines(token.raw, change, false)
229+
when :HEREDOC_PRE
230+
token.value = map_heredoc_lines(token.value, change, false)
231+
when :HEREDOC_MID
232+
token.value = map_heredoc_lines(token.value, change, true)
233+
when :HEREDOC_POST
234+
token.raw = map_heredoc_lines(token.raw, change, true)
235+
end
236+
end
182237
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
$variable = @(EOT)
2+
This is a multiline
3+
with a ${variable}
4+
heredoc string
5+
| EOT
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
$variable = @(EOT)
2+
This is a multiline
3+
with a ${variable}
4+
heredoc string
5+
| EOT

spec/fixtures/pass/heredocclass.pp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Tests for heredocs within class
2+
class 'heredocclass' {
3+
file { '/heredoc/file/resource/without/comma':
4+
content => @(HERE)
5+
This is a here doc
6+
with two lines
7+
| HERE
8+
}
9+
10+
file { '/heredoc/file/resource/with/comma':
11+
content => @(HERE),
12+
This is a here doc
13+
with two lines
14+
| HERE
15+
}
16+
17+
file { '/heredoc/in/function/in/file/resource':
18+
content => inlineepp(@(HERE)),
19+
This is a here doc
20+
with two lines
21+
| HERE
22+
}
23+
24+
$variable_with_interpolation = @("EOT")
25+
Another example
26+
${variable}
27+
with
28+
${variable}
29+
with interpolation
30+
| EOT
31+
}

spec/puppet-lint/plugins/check_strict_indent_spec.rb

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,126 @@ class () {}
163163
expect(manifest).to eq fixed
164164
end
165165
end
166+
167+
context 'misaligned heredocs' do
168+
before do
169+
PuppetLint.configuration.fix = true
170+
end
171+
172+
after do
173+
PuppetLint.configuration.fix = false
174+
end
175+
176+
let(:code) do
177+
<<~EOF
178+
$over_indented = @(HERE)
179+
This is a heredoc
180+
that has been
181+
182+
indented 4 spaces
183+
and has some
184+
internal indenting
185+
| HERE
186+
187+
$under_indented = @(HERE)
188+
This is a heredoc
189+
190+
that has no
191+
indent
192+
and has some
193+
internal indenting
194+
| HERE
195+
196+
$over_indented_interpolated = @("HERE")
197+
This is a heredoc
198+
with a ${variable}
199+
that has been
200+
201+
indented 4 spaces
202+
and has some
203+
internal indenting
204+
and ${another} variable
205+
| HERE
206+
207+
$under_indented_interpolated = @("HERE")
208+
This is a heredoc
209+
with a ${variable} with trailing text
210+
that has been
211+
212+
indented 1 space
213+
and has some
214+
internal indenting
215+
and ${another} variable
216+
| HERE
217+
EOF
218+
end
219+
220+
let(:fixed) do
221+
<<~EOF
222+
$over_indented = @(HERE)
223+
This is a heredoc
224+
that has been
225+
226+
indented 4 spaces
227+
and has some
228+
internal indenting
229+
| HERE
230+
231+
$under_indented = @(HERE)
232+
This is a heredoc
233+
234+
that has no
235+
indent
236+
and has some
237+
internal indenting
238+
| HERE
239+
240+
$over_indented_interpolated = @("HERE")
241+
This is a heredoc
242+
with a ${variable}
243+
that has been
244+
245+
indented 4 spaces
246+
and has some
247+
internal indenting
248+
and ${another} variable
249+
| HERE
250+
251+
$under_indented_interpolated = @("HERE")
252+
This is a heredoc
253+
with a ${variable} with trailing text
254+
that has been
255+
256+
indented 1 space
257+
and has some
258+
internal indenting
259+
and ${another} variable
260+
| HERE
261+
EOF
262+
end
263+
264+
it 'detects four problems' do
265+
expect(problems).to have(4).problem
266+
end
267+
268+
it 'fixes the first problem' do
269+
expect(problems).to contain_fixed('indent should be 2 chars and is 4').on_line(2).in_column(1)
270+
end
271+
272+
it 'fixes the second problem' do
273+
expect(problems).to contain_fixed('indent should be 2 chars and is 0').on_line(11).in_column(1)
274+
end
275+
276+
it 'fixes the third problem' do
277+
expect(problems).to contain_fixed('indent should be 2 chars and is 4').on_line(20).in_column(1)
278+
end
279+
280+
it 'fixes the forth problem' do
281+
expect(problems).to contain_fixed('indent should be 2 chars and is 1').on_line(31).in_column(1)
282+
end
283+
284+
it 'moves the heredoc' do
285+
expect(manifest).to eq fixed
286+
end
287+
end
166288
end

0 commit comments

Comments
 (0)