puppet-lint 2.4.2 → 2.5.0

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.
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
 
@@ -124,6 +124,8 @@ class PuppetLint
124
124
  # value of the token.
125
125
  KNOWN_TOKENS = [
126
126
  [:WHITESPACE, %r{\A(#{WHITESPACE_RE}+)}],
127
+ # FIXME: Future breaking change, the following :TYPE tokens conflict with
128
+ # the :TYPE keyword token.
127
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
128
130
  [:CLASSREF, %r{\A(((::){0,1}[A-Z][-\w]*)+)}],
129
131
  [:NUMBER, %r{\A\b((?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?))\b}],
@@ -240,7 +242,7 @@ class PuppetLint
240
242
  begin
241
243
  string_segments = slurper.parse
242
244
  process_string_segments(string_segments)
243
- length = slurper.consumed_bytes + 1
245
+ length = slurper.consumed_chars + 1
244
246
  rescue PuppetLint::Lexer::StringSlurper::UnterminatedStringError
245
247
  raise PuppetLint::LexerError.new(@line_no, @column, 'unterminated string')
246
248
  end
@@ -287,7 +289,7 @@ class PuppetLint
287
289
  slurper = PuppetLint::Lexer::StringSlurper.new(code[i + length..-1])
288
290
  heredoc_segments = slurper.parse_heredoc(heredoc_tag)
289
291
  process_heredoc_segments(heredoc_segments)
290
- length += slurper.consumed_bytes
292
+ length += slurper.consumed_chars
291
293
  end
292
294
 
293
295
  elsif eol = chunk[%r{\A(#{LINE_END_RE})}, 1]
@@ -299,7 +301,7 @@ class PuppetLint
299
301
  slurper = PuppetLint::Lexer::StringSlurper.new(code[i + length..-1])
300
302
  heredoc_segments = slurper.parse_heredoc(heredoc_tag)
301
303
  process_heredoc_segments(heredoc_segments)
302
- length += slurper.consumed_bytes
304
+ length += slurper.consumed_chars
303
305
  end
304
306
 
305
307
  elsif chunk.start_with?('/')
@@ -417,7 +419,7 @@ class PuppetLint
417
419
  lexer = PuppetLint::Lexer.new
418
420
  lexer.tokenise(segment[1])
419
421
  lexer.tokens.each_with_index do |t, i|
420
- type = i.zero? && (t.type == :NAME || KEYWORDS.include?(t.type.to_s.downcase)) ? :VARIABLE : t.type
422
+ type = i.zero? && t.interpolated_variable? ? :VARIABLE : t.type
421
423
  tokens << new_token(type, t.value, :raw => t.raw)
422
424
  end
423
425
  when :UNENC_VAR
@@ -449,7 +451,7 @@ class PuppetLint
449
451
  lexer = PuppetLint::Lexer.new
450
452
  lexer.tokenise(segment[1])
451
453
  lexer.tokens.each_with_index do |t, i|
452
- type = i.zero? && t.type == :NAME ? :VARIABLE : t.type
454
+ type = i.zero? && t.interpolated_variable? ? :VARIABLE : t.type
453
455
  tokens << new_token(type, t.value, :raw => t.raw)
454
456
  end
455
457
  when :UNENC_VAR
@@ -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,8 +98,19 @@ 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
@@ -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
@@ -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
 
@@ -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.2'.freeze
2
+ VERSION = '2.5.0'.freeze
3
3
  end
@@ -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
@@ -316,6 +318,18 @@ describe PuppetLint::Lexer::StringSlurper do
316
318
  describe '#parse_heredoc' do
317
319
  subject(:segments) { described_class.new(heredoc).parse_heredoc(heredoc_tag) }
318
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
+
319
333
  context 'when parsing a heredoc with interpolation disabled' do
320
334
  context 'that is a plain heredoc' do
321
335
  let(:heredoc) { %( SOMETHING\n ELSE\n :\n |-myheredoc) }
@@ -436,4 +450,24 @@ describe PuppetLint::Lexer::StringSlurper do
436
450
  end
437
451
  end
438
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
439
473
  end
@@ -147,5 +147,23 @@ describe 'parameter_order' do
147
147
 
148
148
  it { expect(problems).to have(0).problems }
149
149
  end
150
+
151
+ context "#{type} parameter with array operation" do
152
+ let(:code) do
153
+ <<-END
154
+ #{type} ntp (
155
+ # XXX: remove self from list
156
+ Array[String] $ntp_servers = [
157
+ 'foo',
158
+ 'bar',
159
+ 'baz',
160
+ ] - $::fqdn,
161
+ Array[String] $pools = [],
162
+ ) { }
163
+ END
164
+ end
165
+
166
+ it { expect(problems).to have(0).problems }
167
+ end
150
168
  end
151
169
  end
@@ -29,6 +29,24 @@ describe 'documentation' do
29
29
  end
30
30
  end
31
31
 
32
+ describe 'incorrectly documented class' do
33
+ let(:code) do
34
+ <<-END
35
+ # foo
36
+
37
+ class test {}
38
+ END
39
+ end
40
+
41
+ it 'should only detect a single problem' do
42
+ expect(problems).to have(1).problem
43
+ end
44
+
45
+ it 'should create a warning' do
46
+ expect(problems).to contain_warning(class_msg).on_line(3).in_column(9)
47
+ end
48
+ end
49
+
32
50
  describe 'undocumented defined type' do
33
51
  let(:code) { 'define test {}' }
34
52
 
@@ -93,15 +93,15 @@ describe 'double_quoted_strings' do
93
93
  end
94
94
  end
95
95
 
96
- context 'double quoted stings containing supported escape patterns' do
96
+ context 'double quoted strings containing supported escape patterns' do
97
97
  let(:code) do
98
98
  <<-END
99
- $string1 = "this string contins \n newline"
100
- $string2 = "this string contains \ttab"
99
+ $string1 = "this string contains \n newline"
100
+ $string2 = "this string contains \t tab"
101
101
  $string3 = "this string contains \${escaped} var"
102
102
  $string4 = "this string contains \\"escaped \\" double quotes"
103
103
  $string5 = "this string contains \\'escaped \\' single quotes"
104
- $string6 = "this string contains \r line return"
104
+ $string6 = "this string contains \r carriage return"
105
105
  $string7 = "this string contains \\\\ an escaped backslash"
106
106
  END
107
107
  end
@@ -112,7 +112,7 @@ describe 'double_quoted_strings' do
112
112
  end
113
113
 
114
114
  context 'double quoted string with random escape should be rejected' do
115
- let(:code) { %( $ztring = "this string contains \l random esape" ) }
115
+ let(:code) { %( $ztring = "this string contains \l random escape" ) }
116
116
 
117
117
  it 'should only detect a single problem' do
118
118
  expect(problems).to have(1).problem
@@ -22,4 +22,32 @@ describe 'variable_is_lowercase' do
22
22
  expect(problems).to have(0).problems
23
23
  end
24
24
  end
25
+
26
+ context 'when typecasting inside an interpolation' do
27
+ let(:code) { %("${Integer(fact('memory.system.total_bytes'))}") }
28
+
29
+ it 'should not detect any problems' do
30
+ expect(problems).to have(0).problems
31
+ end
32
+ end
33
+
34
+ context 'when an interpolated variable contains an uppercase letter' do
35
+ let(:code) { '"${fooBar}"' }
36
+
37
+ it 'should only detect a single problem' do
38
+ expect(problems).to have(1).problem
39
+ end
40
+
41
+ it 'should create a warning' do
42
+ expect(problems).to contain_warning(msg).on_line(1).in_column(4)
43
+ end
44
+ end
45
+
46
+ context 'when an interpolated variable only contains lowercase letters' do
47
+ let(:code) { '"${foobar}"' }
48
+
49
+ it 'should not detect any problems' do
50
+ expect(problems).to have(0).problems
51
+ end
52
+ end
25
53
  end