puppet-lint 2.4.2 → 2.5.0

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