puppet-lint 2.4.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -1014
  3. data/HISTORY.md +1130 -0
  4. data/lib/puppet-lint/data.rb +9 -9
  5. data/lib/puppet-lint/lexer/string_slurper.rb +21 -5
  6. data/lib/puppet-lint/lexer/token.rb +6 -0
  7. data/lib/puppet-lint/lexer.rb +16 -5
  8. data/lib/puppet-lint/plugins/check_classes/parameter_order.rb +12 -1
  9. data/lib/puppet-lint/plugins/check_documentation/documentation.rb +4 -0
  10. data/lib/puppet-lint/plugins/check_resources/ensure_first_param.rb +1 -1
  11. data/lib/puppet-lint/plugins/check_strings/double_quoted_strings.rb +1 -1
  12. data/lib/puppet-lint/tasks/puppet-lint.rb +14 -0
  13. data/lib/puppet-lint/tasks/release_test.rb +3 -1
  14. data/lib/puppet-lint/version.rb +1 -1
  15. data/lib/puppet-lint.rb +1 -1
  16. data/spec/puppet-lint/bin_spec.rb +8 -8
  17. data/spec/puppet-lint/lexer/string_slurper_spec.rb +68 -2
  18. data/spec/puppet-lint/lexer_spec.rb +56 -0
  19. data/spec/puppet-lint/plugins/check_classes/parameter_order_spec.rb +18 -0
  20. data/spec/puppet-lint/plugins/check_classes/variable_scope_spec.rb +14 -0
  21. data/spec/puppet-lint/plugins/check_documentation/documentation_spec.rb +18 -0
  22. data/spec/puppet-lint/plugins/check_strings/double_quoted_strings_spec.rb +6 -5
  23. data/spec/puppet-lint/plugins/check_variables/variable_is_lowercase_spec.rb +28 -0
  24. data/spec/spec_helper.rb +7 -5
  25. metadata +16 -18
  26. data/.gitignore +0 -12
  27. data/.rspec +0 -2
  28. data/.rubocop.yml +0 -74
  29. data/.rubocop_todo.yml +0 -89
  30. data/.travis.yml +0 -26
  31. data/Gemfile +0 -40
  32. data/Rakefile +0 -42
  33. data/appveyor.yml +0 -35
  34. data/puppet-lint.gemspec +0 -19
@@ -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
@@ -37,11 +37,11 @@ class PuppetLint
37
37
  end_interp
38
38
  elsif unenclosed_variable?
39
39
  unenclosed_variable
40
- elsif scanner.match?(ESC_DQUOTE_PATTERN)
41
- @segment << scanner.scan(ESC_DQUOTE_PATTERN)
42
40
  elsif scanner.match?(END_STRING_PATTERN)
43
41
  end_string
44
42
  break if interp_stack.empty?
43
+ elsif scanner.match?(ESC_DQUOTE_PATTERN)
44
+ @segment << scanner.scan(ESC_DQUOTE_PATTERN)
45
45
  else
46
46
  read_char
47
47
  end
@@ -60,7 +60,7 @@ class PuppetLint
60
60
 
61
61
  def parse_heredoc(heredoc_tag)
62
62
  heredoc_name = heredoc_tag[%r{\A"?(.+?)"?(:.+?)?#{PuppetLint::Lexer::WHITESPACE_RE}*(/.*)?\Z}, 1]
63
- end_heredoc_pattern = %r{\A\|?\s*-?\s*#{Regexp.escape(heredoc_name)}}
63
+ end_heredoc_pattern = %r{^\|?\s*-?\s*#{Regexp.escape(heredoc_name)}$}
64
64
  interpolation = heredoc_tag.start_with?('"')
65
65
 
66
66
  @segment_type = :HEREDOC
@@ -98,11 +98,27 @@ class PuppetLint
98
98
  end
99
99
  end
100
100
 
101
- def consumed_bytes
102
- scanner.pos
101
+ # Get the number of characters consumed by the StringSlurper.
102
+ #
103
+ # StringScanner from Ruby 2.0 onwards supports #charpos which returns
104
+ # the number of characters and is multibyte character aware.
105
+ #
106
+ # Prior to this, Ruby's multibyte character support in Strings was a
107
+ # bit unusual and neither String#length nor String#split behave as
108
+ # expected, so we use String#scan to split all the consumed text using
109
+ # a UTF-8 aware regex and use the length of the result
110
+ def consumed_chars
111
+ return scanner.charpos if scanner.respond_to?(:charpos)
112
+
113
+ (scanner.pre_match + scanner.matched).scan(%r{.}mu).length
103
114
  end
104
115
 
105
116
  def start_interp
117
+ if @segment.last && @segment.last == '\\'
118
+ read_char
119
+ return
120
+ end
121
+
106
122
  if interp_stack.empty?
107
123
  scanner.skip(START_INTERP_PATTERN)
108
124
  results << [@segment_type, @segment.join]
@@ -199,6 +199,12 @@ class PuppetLint
199
199
  end
200
200
  nil
201
201
  end
202
+
203
+ def interpolated_variable?
204
+ return false if type == :TYPE && value != 'type'
205
+ return true if type == :NAME
206
+ PuppetLint::Lexer::KEYWORDS.include?(type.to_s.downcase)
207
+ end
202
208
  end
203
209
  end
204
210
  end
@@ -104,6 +104,7 @@ class PuppetLint
104
104
  :IF => true,
105
105
  :ELSIF => true,
106
106
  :LPAREN => true,
107
+ :EQUALS => true,
107
108
  }.freeze
108
109
 
109
110
  # Internal: some commonly used regular expressions
@@ -123,6 +124,8 @@ class PuppetLint
123
124
  # value of the token.
124
125
  KNOWN_TOKENS = [
125
126
  [:WHITESPACE, %r{\A(#{WHITESPACE_RE}+)}],
127
+ # FIXME: Future breaking change, the following :TYPE tokens conflict with
128
+ # the :TYPE keyword token.
126
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
127
130
  [:CLASSREF, %r{\A(((::){0,1}[A-Z][-\w]*)+)}],
128
131
  [:NUMBER, %r{\A\b((?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?))\b}],
@@ -239,9 +242,9 @@ class PuppetLint
239
242
  begin
240
243
  string_segments = slurper.parse
241
244
  process_string_segments(string_segments)
242
- length = slurper.consumed_bytes + 1
245
+ length = slurper.consumed_chars + 1
243
246
  rescue PuppetLint::Lexer::StringSlurper::UnterminatedStringError
244
- raise PuppetLint::LexerError, @line_no, @column, 'unterminated string'
247
+ raise PuppetLint::LexerError.new(@line_no, @column, 'unterminated string')
245
248
  end
246
249
 
247
250
  elsif heredoc_name = chunk[%r{\A@\(("?.+?"?(:.+?)?#{WHITESPACE_RE}*(/.*?)?)\)}, 1]
@@ -286,13 +289,21 @@ class PuppetLint
286
289
  slurper = PuppetLint::Lexer::StringSlurper.new(code[i + length..-1])
287
290
  heredoc_segments = slurper.parse_heredoc(heredoc_tag)
288
291
  process_heredoc_segments(heredoc_segments)
289
- length += slurper.consumed_bytes
292
+ length += slurper.consumed_chars
290
293
  end
291
294
 
292
295
  elsif eol = chunk[%r{\A(#{LINE_END_RE})}, 1]
293
296
  length = eol.size
294
297
  tokens << new_token(:NEWLINE, eol)
295
298
 
299
+ unless heredoc_queue.empty?
300
+ heredoc_tag = heredoc_queue.shift
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
305
+ end
306
+
296
307
  elsif chunk.start_with?('/')
297
308
  length = 1
298
309
  tokens << new_token(:DIV, '/')
@@ -408,7 +419,7 @@ class PuppetLint
408
419
  lexer = PuppetLint::Lexer.new
409
420
  lexer.tokenise(segment[1])
410
421
  lexer.tokens.each_with_index do |t, i|
411
- type = i.zero? && t.type == :NAME ? :VARIABLE : t.type
422
+ type = i.zero? && t.interpolated_variable? ? :VARIABLE : t.type
412
423
  tokens << new_token(type, t.value, :raw => t.raw)
413
424
  end
414
425
  when :UNENC_VAR
@@ -440,7 +451,7 @@ class PuppetLint
440
451
  lexer = PuppetLint::Lexer.new
441
452
  lexer.tokenise(segment[1])
442
453
  lexer.tokens.each_with_index do |t, i|
443
- type = i.zero? && t.type == :NAME ? :VARIABLE : t.type
454
+ type = i.zero? && t.interpolated_variable? ? :VARIABLE : t.type
444
455
  tokens << new_token(type, t.value, :raw => t.raw)
445
456
  end
446
457
  when :UNENC_VAR
@@ -22,6 +22,7 @@ PuppetLint.new_check(:parameter_order) do
22
22
  end
23
23
 
24
24
  next unless hash_or_array_stack.empty? && paren_stack.empty?
25
+ next unless parameter?(token)
25
26
  next unless required_parameter?(token)
26
27
 
27
28
  prev_tokens = class_idx[:param_tokens][0..i]
@@ -38,9 +39,19 @@ PuppetLint.new_check(:parameter_order) do
38
39
  end
39
40
  end
40
41
 
41
- def required_parameter?(token)
42
+ def parameter?(token)
42
43
  return false unless token.type == :VARIABLE
44
+ return false unless token.prev_code_token
45
+
46
+ [
47
+ :LPAREN, # First parameter, no type specification
48
+ :COMMA, # Subsequent parameter, no type specification
49
+ :TYPE, # Parameter with simple type specification
50
+ :RBRACK, # Parameter with complex type specification
51
+ ].include?(token.prev_code_token.type)
52
+ end
43
53
 
54
+ def required_parameter?(token)
44
55
  data_type = token.prev_token_of(:TYPE, :skip_blocks => true)
45
56
  return false if data_type && data_type.value == 'Optional'
46
57
 
@@ -30,8 +30,12 @@ PuppetLint.new_check(:documentation) do
30
30
  end
31
31
 
32
32
  def find_comment_token(start_token)
33
+ newlines = 0
34
+
33
35
  prev_token = start_token.prev_token
34
36
  while !prev_token.nil? && WHITESPACE_TOKENS.include?(prev_token.type)
37
+ newlines += 1 if prev_token.type == :NEWLINE
38
+ break if newlines > 1
35
39
  prev_token = prev_token.prev_token
36
40
  end
37
41
 
@@ -26,7 +26,7 @@ PuppetLint.new_check(:ensure_first_param) do
26
26
  end
27
27
 
28
28
  def fix(problem)
29
- first_param_name_token = tokens[problem[:resource][:start]].next_token_of(:NAME)
29
+ first_param_name_token = problem[:resource][:param_tokens].first
30
30
  first_param_comma_token = first_param_name_token.next_token_of(:COMMA)
31
31
  ensure_param_name_token = first_param_comma_token.next_token_of(:NAME, :value => 'ensure')
32
32
 
@@ -4,7 +4,7 @@
4
4
  #
5
5
  # https://puppet.com/docs/puppet/latest/style_guide.html#quoting
6
6
  PuppetLint.new_check(:double_quoted_strings) do
7
- ESCAPE_CHAR_RE = %r{(\\\$|\\"|\\'|'|\r|\t|\\t|\n|\\n|\\\\)}
7
+ ESCAPE_CHAR_RE = %r{(\\\$|\\"|\\'|'|\r|\t|\\t|\\s|\n|\\n|\\\\)}
8
8
 
9
9
  def check
10
10
  tokens.select { |token|
@@ -20,6 +20,7 @@ class PuppetLint
20
20
  attr_accessor :ignore_paths
21
21
  attr_accessor :with_filename
22
22
  attr_accessor :disable_checks
23
+ attr_accessor :only_checks
23
24
  attr_accessor :fail_on_warnings
24
25
  attr_accessor :error_level
25
26
  attr_accessor :log_format
@@ -40,6 +41,7 @@ class PuppetLint
40
41
  @pattern = DEFAULT_PATTERN
41
42
  @with_filename = true
42
43
  @disable_checks = []
44
+ @only_checks = []
43
45
  @ignore_paths = []
44
46
 
45
47
  define(args, &task_block)
@@ -55,6 +57,17 @@ class PuppetLint
55
57
  task @name do
56
58
  PuppetLint::OptParser.build
57
59
 
60
+ if Array(@only_checks).any?
61
+ enable_checks = Array(@only_checks).map(&:to_sym)
62
+ PuppetLint.configuration.checks.each do |check|
63
+ if enable_checks.include?(check)
64
+ PuppetLint.configuration.send("enable_#{check}")
65
+ else
66
+ PuppetLint.configuration.send("disable_#{check}")
67
+ end
68
+ end
69
+ end
70
+
58
71
  Array(@disable_checks).each do |check|
59
72
  PuppetLint.configuration.send("disable_#{check}")
60
73
  end
@@ -79,6 +92,7 @@ class PuppetLint
79
92
  matched_files = matched_files.exclude(*@ignore_paths)
80
93
 
81
94
  matched_files.to_a.each do |puppet_file|
95
+ next unless File.file?(puppet_file)
82
96
  linter.file = puppet_file
83
97
  linter.run
84
98
  linter.print_problems
@@ -55,7 +55,9 @@ def with_puppet_lint_head
55
55
  end
56
56
 
57
57
  task :release_test do
58
- branch = if ENV['APPVEYOR']
58
+ branch = if ENV['GITHUB_REF']
59
+ ENV['GITHUB_REF']
60
+ elsif ENV['APPVEYOR']
59
61
  ENV['APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH']
60
62
  elsif ENV['TRAVIS']
61
63
  ENV['TRAVIS_PULL_REQUEST_BRANCH']
@@ -1,3 +1,3 @@
1
1
  class PuppetLint
2
- VERSION = '2.4.0'.freeze
2
+ VERSION = '2.5.2'.freeze
3
3
  end
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
 
@@ -91,8 +91,8 @@ describe PuppetLint::Bin do
91
91
  its(:stdout) do
92
92
  is_expected.to eq(
93
93
  [
94
- "#{args[0]} - WARNING: optional parameter listed before required parameter on line 2",
95
- "#{args[1]} - ERROR: test::foo not in autoload module layout on line 2",
94
+ "#{args[0]} - WARNING: optional parameter listed before required parameter on line 2 (check: parameter_order)",
95
+ "#{args[1]} - ERROR: test::foo not in autoload module layout on line 2 (check: autoloader_layout)",
96
96
  ].join("\n")
97
97
  )
98
98
  end
@@ -102,7 +102,7 @@ describe PuppetLint::Bin do
102
102
  let(:args) { 'spec/fixtures/test/manifests/malformed.pp' }
103
103
 
104
104
  its(:exitstatus) { is_expected.to eq(1) }
105
- its(:stdout) { is_expected.to eq('ERROR: Syntax error on line 1') }
105
+ its(:stdout) { is_expected.to eq('ERROR: Syntax error on line 1 (check: syntax)') }
106
106
  its(:stderr) { is_expected.to eq('Try running `puppet parser validate <file>`') }
107
107
  end
108
108
 
@@ -198,7 +198,7 @@ describe PuppetLint::Bin do
198
198
  its(:stdout) do
199
199
  is_expected.to eq(
200
200
  [
201
- 'WARNING: optional parameter listed before required parameter on line 2',
201
+ 'WARNING: optional parameter listed before required parameter on line 2 (check: parameter_order)',
202
202
  '',
203
203
  " define test::warning($foo='bar', $baz) { }",
204
204
  ' ^',
@@ -432,7 +432,7 @@ describe PuppetLint::Bin do
432
432
  its(:stdout) do
433
433
  is_expected.to eq(
434
434
  [
435
- 'IGNORED: double quoted string containing no variables on line 3',
435
+ 'IGNORED: double quoted string containing no variables on line 3 (check: double_quoted_strings)',
436
436
  ' for a good reason',
437
437
  ].join("\n")
438
438
  )
@@ -457,7 +457,7 @@ describe PuppetLint::Bin do
457
457
  end
458
458
 
459
459
  its(:exitstatus) { is_expected.to eq(0) }
460
- its(:stdout) { is_expected.to match(%r{^.*line 6$}) }
460
+ its(:stdout) { is_expected.to match(%r{^.*line 6(?!\d)}) }
461
461
  end
462
462
 
463
463
  context 'when an lint:endignore control comment exists with no opening lint:ignore comment' do
@@ -548,7 +548,7 @@ describe PuppetLint::Bin do
548
548
 
549
549
  its(:exitstatus) { is_expected.to eq(0) }
550
550
  its(:stdout) do
551
- is_expected.to eq('WARNING: variable contains a dash on line 3')
551
+ is_expected.to eq('WARNING: variable contains a dash on line 3 (check: variable_contains_dash)')
552
552
  end
553
553
  end
554
554
 
@@ -562,7 +562,7 @@ describe PuppetLint::Bin do
562
562
 
563
563
  its(:exitstatus) { is_expected.to eq(0) }
564
564
  its(:stdout) do
565
- is_expected.to eq('WARNING: variable contains an uppercase letter on line 4')
565
+ is_expected.to eq('WARNING: variable contains an uppercase letter on line 4 (check: variable_is_lowercase)')
566
566
  end
567
567
  end
568
568
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe PuppetLint::Lexer::StringSlurper do
@@ -36,14 +38,22 @@ describe PuppetLint::Lexer::StringSlurper do
36
38
  end
37
39
  end
38
40
 
39
- context 'an escaped $' do
41
+ context 'an escaped $var' do
40
42
  let(:string) { '\$foo"' }
41
43
 
42
- it 'does not create an interpolation segment' do
44
+ it 'does not create an unenclosed variable segment' do
43
45
  expect(segments).to eq([[:STRING, '\$foo']])
44
46
  end
45
47
  end
46
48
 
49
+ context 'an escaped ${} enclosure' do
50
+ let(:string) { '\"\${\"string\"}\""' }
51
+
52
+ it 'does not create an interpolation segment' do
53
+ expect(segments).to eq([[:STRING, '\"\${\"string\"}\"']])
54
+ end
55
+ end
56
+
47
57
  context 'a variable and a suffix' do
48
58
  let(:string) { '${foo}bar"' }
49
59
 
@@ -238,6 +248,30 @@ describe PuppetLint::Lexer::StringSlurper do
238
248
  end
239
249
  end
240
250
 
251
+ context 'a variable followed by an odd number of backslashes before a double quote' do
252
+ let(:string) { '${foo}\"bar"' }
253
+
254
+ it 'does not let this double quote terminate the string' do
255
+ expect(segments).to eq([
256
+ [:STRING, ''],
257
+ [:INTERP, 'foo'],
258
+ [:STRING, '\\"bar'],
259
+ ])
260
+ end
261
+ end
262
+
263
+ context 'a variable followed by an even number of backslashes before a double quote' do
264
+ let(:string) { '${foo}\\\\"bar"' }
265
+
266
+ it 'recognizes this double quote as the terminator' do
267
+ expect(segments).to eq([
268
+ [:STRING, ''],
269
+ [:INTERP, 'foo'],
270
+ [:STRING, '\\\\'],
271
+ ])
272
+ end
273
+ end
274
+
241
275
  context 'an interpolation with a complex function chain' do
242
276
  let(:string) { '${key} ${flatten([$value]).join("\nkey ")}"' }
243
277
 
@@ -284,6 +318,18 @@ describe PuppetLint::Lexer::StringSlurper do
284
318
  describe '#parse_heredoc' do
285
319
  subject(:segments) { described_class.new(heredoc).parse_heredoc(heredoc_tag) }
286
320
 
321
+ context 'when the heredoc text contains the tag' do
322
+ let(:heredoc) { %( SOMETHING else\n |-THING) }
323
+ let(:heredoc_tag) { 'THING' }
324
+
325
+ it 'terminates the heredoc at the closing tag' do
326
+ expect(segments).to eq([
327
+ [:HEREDOC, " SOMETHING else\n "],
328
+ [:HEREDOC_TERM, '|-THING'],
329
+ ])
330
+ end
331
+ end
332
+
287
333
  context 'when parsing a heredoc with interpolation disabled' do
288
334
  context 'that is a plain heredoc' do
289
335
  let(:heredoc) { %( SOMETHING\n ELSE\n :\n |-myheredoc) }
@@ -404,4 +450,24 @@ describe PuppetLint::Lexer::StringSlurper do
404
450
  end
405
451
  end
406
452
  end
453
+
454
+ describe '#consumed_chars' do
455
+ subject { described_class.new(string).tap(&:parse).consumed_chars }
456
+
457
+ context 'when slurping a string containing multibyte characters' do
458
+ let(:string) { 'accentués"' }
459
+
460
+ it 'counts the multibyte character as a single consumed character' do
461
+ is_expected.to eq(10)
462
+ end
463
+ end
464
+
465
+ context 'when slurping an empty string' do
466
+ let(:string) { '"' }
467
+
468
+ it 'consumes only the closing quote' do
469
+ is_expected.to eq(1)
470
+ end
471
+ end
472
+ end
407
473
  end