puppet-lint 2.3.6 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +238 -87
  3. data/README.md +18 -0
  4. data/lib/puppet-lint.rb +1 -1
  5. data/lib/puppet-lint/data.rb +26 -11
  6. data/lib/puppet-lint/lexer.rb +97 -200
  7. data/lib/puppet-lint/lexer/string_slurper.rb +173 -0
  8. data/lib/puppet-lint/lexer/token.rb +8 -0
  9. data/lib/puppet-lint/optparser.rb +4 -5
  10. data/lib/puppet-lint/plugins/check_classes/parameter_order.rb +12 -1
  11. data/lib/puppet-lint/plugins/check_conditionals/case_without_default.rb +15 -1
  12. data/lib/puppet-lint/plugins/check_documentation/documentation.rb +4 -0
  13. data/lib/puppet-lint/plugins/check_resources/ensure_first_param.rb +5 -2
  14. data/lib/puppet-lint/plugins/check_strings/quoted_booleans.rb +1 -0
  15. data/lib/puppet-lint/plugins/check_strings/variables_not_enclosed.rb +71 -0
  16. data/lib/puppet-lint/plugins/check_whitespace/arrow_alignment.rb +1 -1
  17. data/lib/puppet-lint/tasks/puppet-lint.rb +14 -0
  18. data/lib/puppet-lint/tasks/release_test.rb +3 -1
  19. data/lib/puppet-lint/version.rb +1 -1
  20. data/spec/fixtures/test/manifests/two_warnings.pp +5 -0
  21. data/spec/puppet-lint/bin_spec.rb +47 -6
  22. data/spec/puppet-lint/data_spec.rb +12 -0
  23. data/spec/puppet-lint/lexer/string_slurper_spec.rb +473 -0
  24. data/spec/puppet-lint/lexer_spec.rb +1153 -590
  25. data/spec/puppet-lint/plugins/check_classes/parameter_order_spec.rb +18 -0
  26. data/spec/puppet-lint/plugins/check_classes/variable_scope_spec.rb +15 -1
  27. data/spec/puppet-lint/plugins/check_conditionals/case_without_default_spec.rb +39 -0
  28. data/spec/puppet-lint/plugins/check_documentation/documentation_spec.rb +18 -0
  29. data/spec/puppet-lint/plugins/check_resources/ensure_first_param_spec.rb +16 -0
  30. data/spec/puppet-lint/plugins/check_strings/double_quoted_strings_spec.rb +5 -5
  31. data/spec/puppet-lint/plugins/check_strings/only_variable_string_spec.rb +6 -6
  32. data/spec/puppet-lint/plugins/check_strings/variables_not_enclosed_spec.rb +32 -0
  33. data/spec/puppet-lint/plugins/check_variables/variable_is_lowercase_spec.rb +28 -0
  34. data/spec/spec_helper.rb +7 -5
  35. metadata +14 -17
  36. data/.gitignore +0 -12
  37. data/.rspec +0 -2
  38. data/.rubocop.yml +0 -74
  39. data/.rubocop_todo.yml +0 -89
  40. data/.travis.yml +0 -24
  41. data/Gemfile +0 -40
  42. data/Rakefile +0 -42
  43. data/appveyor.yml +0 -33
  44. data/puppet-lint.gemspec +0 -19
data/README.md CHANGED
@@ -120,6 +120,18 @@ Or to specify a whitelist of allowed checks, include a line like:
120
120
  --only-checks=trailing_whitespace,hard_tabs,duplicate_params,double_quoted_strings,unquoted_file_mode,only_variable_string,variables_not_enclosed,single_quote_string_with_variables,variable_contains_dash,ensure_not_symlink_target,unquoted_resource_title,relative_classname_inclusion,file_mode,resource_reference_without_title_capital,leading_zero,arrow_alignment,variable_is_lowercase,ensure_first_param,resource_reference_without_whitespace,file_ensure,trailing_comma,leading_zero
121
121
  ```
122
122
 
123
+ Please note that there is an important difference between reading options from the command line and reading options from a configuration file: In the former case the shell interprets one level of quotes. That does not happen in the latter case. So, it would make sense to quote some configuration values on the command line, like so:
124
+
125
+ ```
126
+ $ puppet-lint --ignore-paths 'modules/stdlib/*' modules/
127
+ ```
128
+
129
+ When reading from a configuration file those quotes would be passed on to the option parser -- probably not giving the expected result. Instead the line should read
130
+
131
+ ```
132
+ --ignore-paths=modules/stdlib/*
133
+ ```
134
+
123
135
  ## Testing with Puppet Lint as a Rake task
124
136
 
125
137
  To test your entire Puppet manifest directory, add `require 'puppet-lint/tasks/puppet-lint'` to your Rakefile and then run:
@@ -195,6 +207,12 @@ You can also disable checks when running Puppet Lint through the supplied Rake t
195
207
  PuppetLint.configuration.pattern = "modules"
196
208
  ```
197
209
 
210
+ ## Testing with Puppet Lint as a GitHub Action
211
+
212
+ There is a GitHub Actions action available to get linter feedback in workflows:
213
+
214
+ * [puppet-lint-action](https://github.com/marketplace/actions/puppet-lint-action)
215
+
198
216
  ## Options
199
217
 
200
218
  See `puppet-lint --help` for a full list of command line options and checks.
data/lib/puppet-lint.rb CHANGED
@@ -100,9 +100,9 @@ class PuppetLint
100
100
  # Returns a format String to be used with String#%.
101
101
  def log_format
102
102
  if configuration.log_format.nil? || configuration.log_format.empty?
103
- ## recreate previous old log format as far as thats possible.
104
103
  format = '%{KIND}: %{message} on line %{line}'
105
104
  format.prepend('%{path} - ') if configuration.with_filename
105
+ format.concat(' (check: %{check})')
106
106
  configuration.log_format = format
107
107
  end
108
108
 
@@ -171,21 +171,21 @@ class PuppetLint::Data
171
171
  tokens.select { |t| t.type == :COLON }.each do |colon_token|
172
172
  next unless colon_token.next_code_token && colon_token.next_code_token.type != :LBRACE
173
173
 
174
- start_idx = tokens.index(colon_token)
175
- next if start_idx < marker
174
+ rel_start_idx = tokens[marker..-1].index(colon_token)
175
+ break if rel_start_idx.nil?
176
+ start_idx = rel_start_idx + marker
176
177
  end_token = colon_token.next_token_of([:SEMIC, :RBRACE])
177
- end_idx = tokens.index(end_token)
178
-
179
- raise PuppetLint::SyntaxError, colon_token if end_idx.nil?
178
+ rel_end_idx = tokens[start_idx..-1].index(end_token)
179
+ raise PuppetLint::SyntaxError, colon_token if rel_end_idx.nil?
180
+ marker = rel_end_idx + start_idx
180
181
 
181
182
  result << {
182
183
  :start => start_idx + 1,
183
- :end => end_idx,
184
- :tokens => tokens[start_idx..end_idx],
184
+ :end => marker,
185
+ :tokens => tokens[start_idx..marker],
185
186
  :type => find_resource_type_token(start_idx),
186
- :param_tokens => find_resource_param_tokens(tokens[start_idx..end_idx]),
187
+ :param_tokens => find_resource_param_tokens(tokens[start_idx..marker]),
187
188
  }
188
- marker = end_idx
189
189
  end
190
190
  result
191
191
  end
@@ -201,6 +201,9 @@ class PuppetLint::Data
201
201
  lbrace_idx = tokens[0..index].rindex do |token|
202
202
  token.type == :LBRACE && token.prev_code_token.type != :QMARK
203
203
  end
204
+
205
+ raise PuppetLint::SyntaxError, tokens[index] if lbrace_idx.nil?
206
+
204
207
  tokens[lbrace_idx].prev_code_token
205
208
  end
206
209
 
@@ -212,9 +215,21 @@ class PuppetLint::Data
212
215
  #
213
216
  # Returns an Array of Token objects.
214
217
  def find_resource_param_tokens(resource_tokens)
215
- resource_tokens.select do |token|
216
- token.type == :NAME && token.next_code_token.type == :FARROW
218
+ param_tokens = []
219
+
220
+ iter_token = resource_tokens.first.prev_token
221
+
222
+ until iter_token.nil?
223
+ iter_token = iter_token.next_token_of(:NAME)
224
+
225
+ break unless resource_tokens.include?(iter_token)
226
+
227
+ if iter_token && iter_token.next_code_token.type == :FARROW
228
+ param_tokens << iter_token
229
+ end
217
230
  end
231
+
232
+ param_tokens
218
233
  end
219
234
 
220
235
  # Internal: Calculate the positions of all class definitions within the
@@ -2,8 +2,9 @@
2
2
 
3
3
  require 'pp'
4
4
  require 'strscan'
5
- require 'puppet-lint/lexer/token'
6
5
  require 'set'
6
+ require 'puppet-lint/lexer/token'
7
+ require 'puppet-lint/lexer/string_slurper'
7
8
 
8
9
  class PuppetLint
9
10
  # Internal: A generic error thrown by the lexer when it encounters something
@@ -28,11 +29,15 @@ class PuppetLint
28
29
  @column = column
29
30
  @reason = reason
30
31
  end
32
+
33
+ def to_s
34
+ "PuppetLint::LexerError: Line:#{line_no} Column: #{column} Reason: #{reason}"
35
+ end
31
36
  end
32
37
 
33
38
  # Internal: The puppet-lint lexer. Converts your manifest into its tokenised
34
39
  # form.
35
- class Lexer # rubocop:disable Metrics/ClassLength
40
+ class Lexer
36
41
  def initialize
37
42
  @line_no = 1
38
43
  @column = 1
@@ -99,18 +104,32 @@ class PuppetLint
99
104
  :IF => true,
100
105
  :ELSIF => true,
101
106
  :LPAREN => true,
107
+ :EQUALS => true,
102
108
  }.freeze
103
109
 
110
+ # Internal: some commonly used regular expressions
111
+ # \t == tab
112
+ # \v == vertical tab
113
+ # \f == form feed
114
+ # \p{Zs} == ASCII + Unicode non-linebreaking whitespace
115
+ WHITESPACE_RE = RUBY_VERSION == '1.8.7' ? %r{[\t\v\f ]} : %r{[\t\v\f\p{Zs}]}
116
+
117
+ LINE_END_RE = %r{(?:\r\n|\r|\n)}
118
+
119
+ NAME_RE = %r{\A((?:(?:::)?[_a-z0-9][-\w]*)(?:::[a-z0-9][-\w]*)*)}
120
+
104
121
  # Internal: An Array of Arrays containing tokens that can be described by
105
122
  # a single regular expression. Each sub-Array contains 2 elements, the
106
123
  # name of the token as a Symbol and a regular expression describing the
107
124
  # value of the token.
108
- NAME_RE = %r{\A(((::)?[_a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*)}
109
125
  KNOWN_TOKENS = [
110
- [:TYPE, %r{\A(Integer|Float|Boolean|Regexp|String|Array|Hash|Resource|Class|Collection|Scalar|Numeric|CatalogEntry|Data|Tuple|Struct|Optional|NotUndef|Variant|Enum|Pattern|Any|Callable|Type|Runtime|Undef|Default)\b}],
126
+ [:WHITESPACE, %r{\A(#{WHITESPACE_RE}+)}],
127
+ # FIXME: Future breaking change, the following :TYPE tokens conflict with
128
+ # the :TYPE keyword token.
129
+ [:TYPE, %r{\A(Integer|Float|Boolean|Regexp|String|Array|Hash|Resource|Class|Collection|Scalar|Numeric|CatalogEntry|Data|Tuple|Struct|Optional|NotUndef|Variant|Enum|Pattern|Any|Callable|Type|Runtime|Undef|Default|Sensitive)\b}], # rubocop:disable Metrics/LineLength
111
130
  [:CLASSREF, %r{\A(((::){0,1}[A-Z][-\w]*)+)}],
112
131
  [:NUMBER, %r{\A\b((?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?))\b}],
113
- [:FUNCTION_NAME, %r{#{NAME_RE}\(}],
132
+ [:FUNCTION_NAME, %r{#{NAME_RE}(?=\()}],
114
133
  [:NAME, NAME_RE],
115
134
  [:LBRACK, %r{\A(\[)}],
116
135
  [:RBRACK, %r{\A(\])}],
@@ -166,14 +185,6 @@ class PuppetLint
166
185
  :INDENT => true,
167
186
  }.freeze
168
187
 
169
- # \t == tab
170
- # \v == vertical tab
171
- # \f == form feed
172
- # \p{Zs} == ASCII + Unicode non-linebreaking whitespace
173
- WHITESPACE_RE = RUBY_VERSION == '1.8.7' ? %r{[\t\v\f ]} : %r{[\t\v\f\p{Zs}]}
174
-
175
- LINE_END_RE = %r{(?:\r\n|\r|\n)}
176
-
177
188
  # Internal: Access the internal token storage.
178
189
  #
179
190
  # Returns an Array of PuppetLint::Lexer::Toxen objects.
@@ -200,13 +211,12 @@ class PuppetLint
200
211
  value = chunk[regex, 1]
201
212
  next if value.nil?
202
213
 
203
- length = value.size
214
+ i += value.size
204
215
  tokens << if type == :NAME && KEYWORDS.include?(value)
205
216
  new_token(value.upcase.to_sym, value)
206
217
  else
207
218
  new_token(type, value)
208
219
  end
209
- i += length
210
220
  found = true
211
221
  break
212
222
  end
@@ -215,7 +225,12 @@ class PuppetLint
215
225
 
216
226
  if var_name = chunk[%r{\A\$((::)?(\w+(-\w+)*::)*\w+(-\w+)*(\[.+?\])*)}, 1]
217
227
  length = var_name.size + 1
218
- tokens << new_token(:VARIABLE, var_name)
228
+ opts = if chunk.start_with?('$')
229
+ { :raw => "$#{var_name}" }
230
+ else
231
+ {}
232
+ end
233
+ tokens << new_token(:VARIABLE, var_name, opts)
219
234
 
220
235
  elsif chunk =~ %r{\A'.*?'}m
221
236
  str_content = StringScanner.new(code[i + 1..-1]).scan_until(%r{(\A|[^\\])(\\\\)*'}m)
@@ -223,12 +238,16 @@ class PuppetLint
223
238
  tokens << new_token(:SSTRING, str_content[0..-2])
224
239
 
225
240
  elsif chunk.start_with?('"')
226
- str_contents = slurp_string(code[i + 1..-1])
227
- lines_parsed = code[0..i].split(LINE_END_RE)
228
- interpolate_string(str_contents, lines_parsed.count, lines_parsed.last.length)
229
- length = str_contents.size + 1
241
+ slurper = PuppetLint::Lexer::StringSlurper.new(code[i + 1..-1])
242
+ begin
243
+ string_segments = slurper.parse
244
+ process_string_segments(string_segments)
245
+ length = slurper.consumed_chars + 1
246
+ rescue PuppetLint::Lexer::StringSlurper::UnterminatedStringError
247
+ raise PuppetLint::LexerError.new(@line_no, @column, 'unterminated string')
248
+ end
230
249
 
231
- elsif heredoc_name = chunk[%r{\A@\(("?.+?"?(:.+?)?(/.*?)?)\)}, 1]
250
+ elsif heredoc_name = chunk[%r{\A@\(("?.+?"?(:.+?)?#{WHITESPACE_RE}*(/.*?)?)\)}, 1]
232
251
  heredoc_queue << heredoc_name
233
252
  tokens << new_token(:HEREDOC_OPEN, heredoc_name)
234
253
  length = heredoc_name.size + 3
@@ -251,7 +270,7 @@ class PuppetLint
251
270
  mlcomment.gsub!(%r{^ *\*}, '')
252
271
  tokens << new_token(:MLCOMMENT, mlcomment, :raw => mlcomment_raw)
253
272
 
254
- elsif chunk.match(%r{\A/.*?/}) && possible_regex?
273
+ elsif chunk.match(%r{\A/.*?/}m) && possible_regex?
255
274
  str_content = StringScanner.new(code[i + 1..-1]).scan_until(%r{(\A|[^\\])(\\\\)*/}m)
256
275
  length = str_content.size + 1
257
276
  tokens << new_token(:REGEX, str_content[0..-2])
@@ -267,27 +286,22 @@ class PuppetLint
267
286
  length += indent.size
268
287
  else
269
288
  heredoc_tag = heredoc_queue.shift
270
- heredoc_name = heredoc_tag[%r{\A"?(.+?)"?(:.+?)?(/.*)?\Z}, 1]
271
- str_contents = StringScanner.new(code[(i + length)..-1]).scan_until(%r{\|?\s*-?\s*#{heredoc_name}})
272
- interpolate_heredoc(str_contents, heredoc_tag)
273
- length += str_contents.size
289
+ slurper = PuppetLint::Lexer::StringSlurper.new(code[i + length..-1])
290
+ heredoc_segments = slurper.parse_heredoc(heredoc_tag)
291
+ process_heredoc_segments(heredoc_segments)
292
+ length += slurper.consumed_chars
274
293
  end
275
294
 
276
- elsif whitespace = chunk[%r{\A(#{WHITESPACE_RE}+)}, 1]
277
- length = whitespace.size
278
- tokens << new_token(:WHITESPACE, whitespace)
279
-
280
295
  elsif eol = chunk[%r{\A(#{LINE_END_RE})}, 1]
281
296
  length = eol.size
282
297
  tokens << new_token(:NEWLINE, eol)
283
298
 
284
299
  unless heredoc_queue.empty?
285
300
  heredoc_tag = heredoc_queue.shift
286
- heredoc_name = heredoc_tag[%r{\A"?(.+?)"?(:.+?)?(/.*)?\Z}, 1]
287
- str_contents = StringScanner.new(code[(i + length)..-1]).scan_until(%r{\|?\s*-?\s*#{heredoc_name}})
288
- _ = code[0..(i + length)].split(LINE_END_RE)
289
- interpolate_heredoc(str_contents, heredoc_tag)
290
- length += str_contents.size
301
+ slurper = PuppetLint::Lexer::StringSlurper.new(code[i + length..-1])
302
+ heredoc_segments = slurper.parse_heredoc(heredoc_tag)
303
+ process_heredoc_segments(heredoc_segments)
304
+ length += slurper.consumed_chars
291
305
  end
292
306
 
293
307
  elsif chunk.start_with?('/')
@@ -308,22 +322,6 @@ class PuppetLint
308
322
  tokens
309
323
  end
310
324
 
311
- def slurp_string(string)
312
- dq_str_regexp = %r{(\$\{|(\A|[^\\])(\\\\)*")}m
313
- scanner = StringScanner.new(string)
314
- contents = scanner.scan_until(dq_str_regexp)
315
-
316
- if scanner.matched.nil?
317
- raise LexerError.new(@line_no, @column, 'Double quoted string missing closing quote')
318
- end
319
-
320
- until scanner.matched.end_with?('"')
321
- contents += scanner.scan_until(%r{\}}m)
322
- contents += scanner.scan_until(dq_str_regexp)
323
- end
324
- contents
325
- end
326
-
327
325
  # Internal: Given the tokens already processed, determine if the next token
328
326
  # could be a regular expression.
329
327
  #
@@ -403,167 +401,66 @@ class PuppetLint
403
401
  token
404
402
  end
405
403
 
406
- # Internal: Split a string on multiple terminators, excluding escaped
407
- # terminators.
408
- #
409
- # string - The String to be split.
410
- # terminators - The String of terminators that the String should be split
411
- # on.
412
- #
413
- # Returns an Array consisting of two Strings, the String up to the first
414
- # terminator and the terminator that was found.
415
- def get_string_segment(string, terminators)
416
- str = string.scan_until(%r{([^\\]|^|[^\\])([\\]{2})*[#{terminators}]+})
417
- begin
418
- [str[0..-2], str[-1, 1]]
419
- rescue
420
- [nil, nil]
404
+ def process_string_segments(segments)
405
+ return if segments.empty?
406
+
407
+ if segments.length == 1
408
+ tokens << new_token(:STRING, segments[0][1])
409
+ return
421
410
  end
422
- end
423
411
 
424
- # Internal: Tokenise the contents of a double quoted string.
425
- #
426
- # string - The String to be tokenised.
427
- # line - The Integer line number of the start of the passed string.
428
- # column - The Integer column number of the start of the passed string.
429
- #
430
- # Returns nothing.
431
- def interpolate_string(string, line, column)
432
- ss = StringScanner.new(string)
433
- first = true
434
- value, terminator = get_string_segment(ss, '"$')
435
- until value.nil?
436
- if terminator == '"'
437
- if first
438
- tokens << new_token(:STRING, value, :line => line, :column => column)
439
- first = false
440
- else
441
- token_column = column + (ss.pos - value.size)
442
- tokens << new_token(:DQPOST, value, :line => line, :column => token_column)
443
- line += value.scan(LINE_END_RE).size
444
- @column = column + ss.pos + 1
445
- @line_no = line
412
+ pre_segment = segments.delete_at(0)
413
+ post_segment = segments.delete_at(-1)
414
+
415
+ tokens << new_token(:DQPRE, pre_segment[1])
416
+ segments.each do |segment|
417
+ case segment[0]
418
+ when :INTERP
419
+ lexer = PuppetLint::Lexer.new
420
+ lexer.tokenise(segment[1])
421
+ lexer.tokens.each_with_index do |t, i|
422
+ type = i.zero? && t.interpolated_variable? ? :VARIABLE : t.type
423
+ tokens << new_token(type, t.value, :raw => t.raw)
446
424
  end
425
+ when :UNENC_VAR
426
+ tokens << new_token(:UNENC_VARIABLE, segment[1].gsub(%r{\A\$}, ''))
447
427
  else
448
- if first
449
- tokens << new_token(:DQPRE, value, :line => line, :column => column)
450
- first = false
451
- else
452
- token_column = column + (ss.pos - value.size)
453
- tokens << new_token(:DQMID, value, :line => line, :column => token_column)
454
- line += value.scan(LINE_END_RE).size
455
- end
456
- if ss.scan(%r{\{}).nil?
457
- var_name = ss.scan(%r{(::)?(\w+(-\w+)*::)*\w+(-\w+)*})
458
- if var_name.nil?
459
- token_column = column + ss.pos - 1
460
- tokens << new_token(:DQMID, '$', :line => line, :column => token_column)
461
- else
462
- token_column = column + (ss.pos - var_name.size)
463
- tokens << new_token(:UNENC_VARIABLE, var_name, :line => line, :column => token_column)
464
- end
465
- else
466
- line += value.scan(LINE_END_RE).size
467
- contents = ss.scan_until(%r{\}})[0..-2]
468
- raw = contents.dup
469
- if contents.match(%r{\A(::)?([\w-]+::)*[\w-]+(\[.+?\])*}) && !contents.match(%r{\A\w+\(})
470
- contents = "$#{contents}"
471
- end
472
- lexer = PuppetLint::Lexer.new
473
- lexer.tokenise(contents)
474
- lexer.tokens.each do |token|
475
- tok_col = column + token.column + (ss.pos - contents.size - 1)
476
- tok_line = token.line + line - 1
477
- tokens << new_token(token.type, token.value, :line => tok_line, :column => tok_col)
478
- end
479
- if lexer.tokens.length == 1 && lexer.tokens[0].type == :VARIABLE
480
- tokens.last.raw = raw
481
- end
482
- end
428
+ tokens << new_token(:DQMID, segment[1])
483
429
  end
484
- value, terminator = get_string_segment(ss, '"$')
485
430
  end
431
+ tokens << new_token(:DQPOST, post_segment[1])
486
432
  end
487
433
 
488
- # Internal: Tokenise the contents of a heredoc.
489
- #
490
- # string - The String to be tokenised.
491
- # name - The String name/endtext of the heredoc.
492
- #
493
- # Returns nothing.
494
- def interpolate_heredoc(string, name)
495
- ss = StringScanner.new(string)
496
- eos_text = name[%r{\A"?(.+?)"?(:.+?)?(/.*)?\Z}, 1]
497
- first = true
498
- interpolate = name.start_with?('"')
499
- value, terminator = get_heredoc_segment(ss, eos_text, interpolate)
500
- until value.nil?
501
- if terminator =~ %r{\A\|?\s*-?\s*#{Regexp.escape(eos_text)}}
502
- if first
503
- tokens << new_token(:HEREDOC, value, :raw => "#{value}#{terminator}")
504
- first = false
505
- else
506
- tokens << new_token(:HEREDOC_POST, value, :raw => "#{value}#{terminator}")
434
+ def process_heredoc_segments(segments)
435
+ return if segments.empty?
436
+
437
+ end_tag = segments.delete_at(-1)
438
+
439
+ if segments.length == 1
440
+ tokens << new_token(:HEREDOC, segments[0][1], :raw => "#{segments[0][1]}#{end_tag[1]}")
441
+ return
442
+ end
443
+
444
+ pre_segment = segments.delete_at(0)
445
+ post_segment = segments.delete_at(-1)
446
+
447
+ tokens << new_token(:HEREDOC_PRE, pre_segment[1])
448
+ segments.each do |segment|
449
+ case segment[0]
450
+ when :INTERP
451
+ lexer = PuppetLint::Lexer.new
452
+ lexer.tokenise(segment[1])
453
+ lexer.tokens.each_with_index do |t, i|
454
+ type = i.zero? && t.interpolated_variable? ? :VARIABLE : t.type
455
+ tokens << new_token(type, t.value, :raw => t.raw)
507
456
  end
457
+ when :UNENC_VAR
458
+ tokens << new_token(:UNENC_VARIABLE, segment[1].gsub(%r{\A\$}, ''))
508
459
  else
509
- if first
510
- tokens << new_token(:HEREDOC_PRE, value)
511
- first = false
512
- else
513
- tokens << new_token(:HEREDOC_MID, value)
514
- end
515
- if ss.scan(%r{\{}).nil?
516
- var_name = ss.scan(%r{(::)?(\w+(-\w+)*::)*\w+(-\w+)*})
517
- tokens << if var_name.nil?
518
- new_token(:HEREDOC_MID, '$')
519
- else
520
- new_token(:UNENC_VARIABLE, var_name)
521
- end
522
- else
523
- contents = ss.scan_until(%r{\}})[0..-2]
524
- raw = contents.dup
525
- if contents.match(%r{\A(::)?([\w-]+::)*[\w-]|(\[.+?\])*}) && !contents.match(%r{\A\w+\(})
526
- contents = "$#{contents}" unless contents.start_with?('$')
527
- end
528
-
529
- lexer = PuppetLint::Lexer.new
530
- lexer.tokenise(contents)
531
- lexer.tokens.each do |token|
532
- tokens << new_token(token.type, token.value)
533
- end
534
- if lexer.tokens.length == 1 && lexer.tokens[0].type == :VARIABLE
535
- tokens.last.raw = raw
536
- end
537
- end
460
+ tokens << new_token(:HEREDOC_MID, segment[1])
538
461
  end
539
- value, terminator = get_heredoc_segment(ss, eos_text, interpolate)
540
- end
541
- end
542
-
543
- # Internal: Splits a heredoc String into segments if it is to be
544
- # interpolated.
545
- #
546
- # string - The String heredoc.
547
- # eos_text - The String endtext for the heredoc.
548
- # interpolate - A Boolean that specifies whether this heredoc can contain
549
- # interpolated values (defaults to True).
550
- #
551
- # Returns an Array consisting of two Strings, the String up to the first
552
- # terminator and the terminator that was found.
553
- def get_heredoc_segment(string, eos_text, interpolate = true)
554
- regexp = if interpolate
555
- %r{(([^\\]|^|[^\\])([\\]{2})*[$]+|\|?\s*-?#{Regexp.escape(eos_text)})}
556
- else
557
- %r{\|?\s*-?#{Regexp.escape(eos_text)}}
558
- end
559
-
560
- str = string.scan_until(regexp)
561
- begin
562
- str =~ %r{\A(.*?)([$]+|\|?\s*-?#{Regexp.escape(eos_text)})\Z}m
563
- [Regexp.last_match(1), Regexp.last_match(2)]
564
- rescue
565
- [nil, nil]
566
462
  end
463
+ tokens << new_token(:HEREDOC_POST, post_segment[1], :raw => "#{post_segment[1]}#{end_tag[1]}")
567
464
  end
568
465
  end
569
466
  end