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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +112 -96
- data/lib/puppet-lint.rb +1 -1
- data/lib/puppet-lint/lexer.rb +7 -5
- data/lib/puppet-lint/lexer/string_slurper.rb +14 -3
- data/lib/puppet-lint/lexer/token.rb +6 -0
- data/lib/puppet-lint/plugins/check_classes/parameter_order.rb +12 -1
- data/lib/puppet-lint/plugins/check_documentation/documentation.rb +4 -0
- data/lib/puppet-lint/plugins/check_resources/ensure_first_param.rb +1 -1
- data/lib/puppet-lint/tasks/puppet-lint.rb +14 -0
- data/lib/puppet-lint/tasks/release_test.rb +3 -1
- data/lib/puppet-lint/version.rb +1 -1
- data/spec/puppet-lint/bin_spec.rb +8 -8
- data/spec/puppet-lint/lexer/string_slurper_spec.rb +34 -0
- data/spec/puppet-lint/plugins/check_classes/parameter_order_spec.rb +18 -0
- data/spec/puppet-lint/plugins/check_documentation/documentation_spec.rb +18 -0
- data/spec/puppet-lint/plugins/check_strings/double_quoted_strings_spec.rb +5 -5
- data/spec/puppet-lint/plugins/check_variables/variable_is_lowercase_spec.rb +28 -0
- data/spec/spec_helper.rb +7 -5
- metadata +9 -16
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/.rubocop.yml +0 -74
- data/.rubocop_todo.yml +0 -89
- data/.travis.yml +0 -26
- data/Gemfile +0 -40
- data/Rakefile +0 -44
- data/appveyor.yml +0 -35
- data/puppet-lint.gemspec +0 -19
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
|
|
data/lib/puppet-lint/lexer.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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? &&
|
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.
|
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{
|
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
|
-
|
102
|
-
|
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
|
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 =
|
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['
|
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']
|
data/lib/puppet-lint/version.rb
CHANGED
@@ -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
|
96
|
+
context 'double quoted strings containing supported escape patterns' do
|
97
97
|
let(:code) do
|
98
98
|
<<-END
|
99
|
-
$string1 = "this string
|
100
|
-
$string2 = "this string contains \
|
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
|
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
|
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
|