puppet-lint 2.4.0 → 2.5.2

Sign up to get free protection for your applications and to get access to all the features.
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