puppet-lint 2.3.6 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +238 -87
- data/README.md +18 -0
- data/lib/puppet-lint.rb +1 -1
- data/lib/puppet-lint/data.rb +26 -11
- data/lib/puppet-lint/lexer.rb +97 -200
- data/lib/puppet-lint/lexer/string_slurper.rb +173 -0
- data/lib/puppet-lint/lexer/token.rb +8 -0
- data/lib/puppet-lint/optparser.rb +4 -5
- data/lib/puppet-lint/plugins/check_classes/parameter_order.rb +12 -1
- data/lib/puppet-lint/plugins/check_conditionals/case_without_default.rb +15 -1
- data/lib/puppet-lint/plugins/check_documentation/documentation.rb +4 -0
- data/lib/puppet-lint/plugins/check_resources/ensure_first_param.rb +5 -2
- data/lib/puppet-lint/plugins/check_strings/quoted_booleans.rb +1 -0
- data/lib/puppet-lint/plugins/check_strings/variables_not_enclosed.rb +71 -0
- data/lib/puppet-lint/plugins/check_whitespace/arrow_alignment.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/fixtures/test/manifests/two_warnings.pp +5 -0
- data/spec/puppet-lint/bin_spec.rb +47 -6
- data/spec/puppet-lint/data_spec.rb +12 -0
- data/spec/puppet-lint/lexer/string_slurper_spec.rb +473 -0
- data/spec/puppet-lint/lexer_spec.rb +1153 -590
- data/spec/puppet-lint/plugins/check_classes/parameter_order_spec.rb +18 -0
- data/spec/puppet-lint/plugins/check_classes/variable_scope_spec.rb +15 -1
- data/spec/puppet-lint/plugins/check_conditionals/case_without_default_spec.rb +39 -0
- data/spec/puppet-lint/plugins/check_documentation/documentation_spec.rb +18 -0
- data/spec/puppet-lint/plugins/check_resources/ensure_first_param_spec.rb +16 -0
- data/spec/puppet-lint/plugins/check_strings/double_quoted_strings_spec.rb +5 -5
- data/spec/puppet-lint/plugins/check_strings/only_variable_string_spec.rb +6 -6
- data/spec/puppet-lint/plugins/check_strings/variables_not_enclosed_spec.rb +32 -0
- data/spec/puppet-lint/plugins/check_variables/variable_is_lowercase_spec.rb +28 -0
- data/spec/spec_helper.rb +7 -5
- metadata +14 -17
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/.rubocop.yml +0 -74
- data/.rubocop_todo.yml +0 -89
- data/.travis.yml +0 -24
- data/Gemfile +0 -40
- data/Rakefile +0 -42
- data/appveyor.yml +0 -33
- data/puppet-lint.gemspec +0 -19
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
class PuppetLint
|
4
|
+
class Lexer
|
5
|
+
# Document this
|
6
|
+
# TODO
|
7
|
+
class StringSlurper
|
8
|
+
class UnterminatedStringError < StandardError; end
|
9
|
+
|
10
|
+
attr_accessor :scanner
|
11
|
+
attr_accessor :results
|
12
|
+
attr_accessor :interp_stack
|
13
|
+
|
14
|
+
START_INTERP_PATTERN = %r{\$\{}
|
15
|
+
END_INTERP_PATTERN = %r{\}}
|
16
|
+
END_STRING_PATTERN = %r{(\A|[^\\])(\\\\)*"}
|
17
|
+
UNENC_VAR_PATTERN = %r{(\A|[^\\])\$(::)?(\w+(-\w+)*::)*\w+(-\w+)*}
|
18
|
+
ESC_DQUOTE_PATTERN = %r{\\+"}
|
19
|
+
LBRACE_PATTERN = %r{\{}
|
20
|
+
|
21
|
+
def initialize(string)
|
22
|
+
@scanner = StringScanner.new(string)
|
23
|
+
@results = []
|
24
|
+
@interp_stack = []
|
25
|
+
@segment = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse
|
29
|
+
@segment_type = :STRING
|
30
|
+
|
31
|
+
until scanner.eos?
|
32
|
+
if scanner.match?(START_INTERP_PATTERN)
|
33
|
+
start_interp
|
34
|
+
elsif !interp_stack.empty? && scanner.match?(LBRACE_PATTERN)
|
35
|
+
read_char
|
36
|
+
elsif scanner.match?(END_INTERP_PATTERN)
|
37
|
+
end_interp
|
38
|
+
elsif unenclosed_variable?
|
39
|
+
unenclosed_variable
|
40
|
+
elsif scanner.match?(END_STRING_PATTERN)
|
41
|
+
end_string
|
42
|
+
break if interp_stack.empty?
|
43
|
+
elsif scanner.match?(ESC_DQUOTE_PATTERN)
|
44
|
+
@segment << scanner.scan(ESC_DQUOTE_PATTERN)
|
45
|
+
else
|
46
|
+
read_char
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
raise UnterminatedStringError if results.empty? && scanner.matched?
|
51
|
+
|
52
|
+
results
|
53
|
+
end
|
54
|
+
|
55
|
+
def unenclosed_variable?
|
56
|
+
interp_stack.empty? &&
|
57
|
+
scanner.match?(UNENC_VAR_PATTERN) &&
|
58
|
+
(@segment.last.nil? ? true : !@segment.last.end_with?('\\'))
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_heredoc(heredoc_tag)
|
62
|
+
heredoc_name = heredoc_tag[%r{\A"?(.+?)"?(:.+?)?#{PuppetLint::Lexer::WHITESPACE_RE}*(/.*)?\Z}, 1]
|
63
|
+
end_heredoc_pattern = %r{^\|?\s*-?\s*#{Regexp.escape(heredoc_name)}$}
|
64
|
+
interpolation = heredoc_tag.start_with?('"')
|
65
|
+
|
66
|
+
@segment_type = :HEREDOC
|
67
|
+
|
68
|
+
until scanner.eos?
|
69
|
+
if scanner.match?(end_heredoc_pattern)
|
70
|
+
end_heredoc(end_heredoc_pattern)
|
71
|
+
break if interp_stack.empty?
|
72
|
+
elsif interpolation && scanner.match?(START_INTERP_PATTERN)
|
73
|
+
start_interp
|
74
|
+
elsif interpolation && !interp_stack.empty? && scanner.match?(LBRACE_PATTERN)
|
75
|
+
read_char
|
76
|
+
elsif interpolation && unenclosed_variable?
|
77
|
+
unenclosed_variable
|
78
|
+
elsif interpolation && scanner.match?(END_INTERP_PATTERN)
|
79
|
+
end_interp
|
80
|
+
else
|
81
|
+
read_char
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
results
|
86
|
+
end
|
87
|
+
|
88
|
+
def read_char
|
89
|
+
@segment << scanner.getch
|
90
|
+
|
91
|
+
return if interp_stack.empty?
|
92
|
+
|
93
|
+
case @segment.last
|
94
|
+
when '{'
|
95
|
+
interp_stack.push(true)
|
96
|
+
when '}'
|
97
|
+
interp_stack.pop
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
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
|
114
|
+
end
|
115
|
+
|
116
|
+
def start_interp
|
117
|
+
if @segment.last && @segment.last == '\\'
|
118
|
+
read_char
|
119
|
+
return
|
120
|
+
end
|
121
|
+
|
122
|
+
if interp_stack.empty?
|
123
|
+
scanner.skip(START_INTERP_PATTERN)
|
124
|
+
results << [@segment_type, @segment.join]
|
125
|
+
@segment = []
|
126
|
+
else
|
127
|
+
@segment << scanner.scan(START_INTERP_PATTERN)
|
128
|
+
end
|
129
|
+
|
130
|
+
interp_stack.push(true)
|
131
|
+
end
|
132
|
+
|
133
|
+
def end_interp
|
134
|
+
if interp_stack.empty?
|
135
|
+
@segment << scanner.scan(END_INTERP_PATTERN)
|
136
|
+
return
|
137
|
+
else
|
138
|
+
interp_stack.pop
|
139
|
+
end
|
140
|
+
|
141
|
+
if interp_stack.empty?
|
142
|
+
results << [:INTERP, @segment.join]
|
143
|
+
@segment = []
|
144
|
+
scanner.skip(END_INTERP_PATTERN)
|
145
|
+
else
|
146
|
+
@segment << scanner.scan(END_INTERP_PATTERN)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def unenclosed_variable
|
151
|
+
read_char if scanner.match?(%r{.\$})
|
152
|
+
|
153
|
+
results << [@segment_type, @segment.join]
|
154
|
+
results << [:UNENC_VAR, scanner.scan(UNENC_VAR_PATTERN)]
|
155
|
+
@segment = []
|
156
|
+
end
|
157
|
+
|
158
|
+
def end_heredoc(pattern)
|
159
|
+
results << [:HEREDOC, @segment.join]
|
160
|
+
results << [:HEREDOC_TERM, scanner.scan(pattern)]
|
161
|
+
end
|
162
|
+
|
163
|
+
def end_string
|
164
|
+
if interp_stack.empty?
|
165
|
+
@segment << scanner.scan(END_STRING_PATTERN).gsub!(%r{"\Z}, '')
|
166
|
+
results << [@segment_type, @segment.join]
|
167
|
+
else
|
168
|
+
@segment << scanner.scan(END_STRING_PATTERN)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -193,10 +193,18 @@ class PuppetLint
|
|
193
193
|
token_iter = token_iter.send("#{direction}_token_of".to_sym, ["#{closing_token}PAREN".to_sym, opts])
|
194
194
|
end
|
195
195
|
end
|
196
|
+
|
197
|
+
return nil if token_iter.nil?
|
196
198
|
token_iter = token_iter.send("#{direction}_token".to_sym)
|
197
199
|
end
|
198
200
|
nil
|
199
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
|
200
208
|
end
|
201
209
|
end
|
202
210
|
end
|
@@ -18,7 +18,6 @@ class PuppetLint::OptParser
|
|
18
18
|
#
|
19
19
|
# Returns an OptionParser object.
|
20
20
|
def self.build(args = [])
|
21
|
-
noconfig = false
|
22
21
|
opt_parser = OptionParser.new do |opts|
|
23
22
|
opts.banner = HELP_TEXT
|
24
23
|
|
@@ -27,7 +26,7 @@ class PuppetLint::OptParser
|
|
27
26
|
end
|
28
27
|
|
29
28
|
opts.on('--no-config', 'Do not load default puppet-lint option files.') do
|
30
|
-
|
29
|
+
# nothing to do, option is handled differently
|
31
30
|
end
|
32
31
|
|
33
32
|
opts.on('-c', '--config FILE', 'Load puppet-lint options from file.') do |file|
|
@@ -134,9 +133,7 @@ class PuppetLint::OptParser
|
|
134
133
|
end
|
135
134
|
end
|
136
135
|
|
137
|
-
|
138
|
-
|
139
|
-
unless noconfig
|
136
|
+
unless args.include?('--no-config')
|
140
137
|
opt_parser.load('/etc/puppet-lint.rc')
|
141
138
|
if ENV.key?('HOME') && File.readable?(ENV['HOME'])
|
142
139
|
home_dotfile_path = File.expand_path('~/.puppet-lint.rc')
|
@@ -145,6 +142,8 @@ class PuppetLint::OptParser
|
|
145
142
|
opt_parser.load('.puppet-lint.rc')
|
146
143
|
end
|
147
144
|
|
145
|
+
opt_parser.parse!(args) unless args.empty?
|
146
|
+
|
148
147
|
opt_parser
|
149
148
|
end
|
150
149
|
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
|
|
@@ -31,7 +31,21 @@ PuppetLint.new_check(:case_without_default) do
|
|
31
31
|
case_tokens -= tokens[successor_kase[:start]..successor_kase[:end]]
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
found_matching_default = false
|
35
|
+
depth = 0
|
36
|
+
|
37
|
+
case_tokens.each do |token|
|
38
|
+
if token.type == :LBRACE
|
39
|
+
depth += 1
|
40
|
+
elsif token.type == :RBRACE
|
41
|
+
depth -= 1
|
42
|
+
elsif token.type == :DEFAULT && depth == 1
|
43
|
+
found_matching_default = true
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
next if found_matching_default
|
35
49
|
|
36
50
|
notify(
|
37
51
|
:warning,
|
@@ -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,12 +26,15 @@ 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
|
+
|
33
|
+
raise PuppetLint::NoFix if ensure_param_name_token.nil?
|
34
|
+
|
32
35
|
ensure_param_comma_token = ensure_param_name_token.next_token_of([:COMMA, :SEMIC])
|
33
36
|
|
34
|
-
if first_param_name_token.nil? || first_param_comma_token.nil? ||
|
37
|
+
if first_param_name_token.nil? || first_param_comma_token.nil? || ensure_param_comma_token.nil?
|
35
38
|
raise PuppetLint::NoFix
|
36
39
|
end
|
37
40
|
|
@@ -1,9 +1,19 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'strscan'
|
3
|
+
|
1
4
|
# Public: Check the manifest tokens for any variables in a string that have
|
2
5
|
# not been enclosed by braces ({}) and record a warning for each instance
|
3
6
|
# found.
|
4
7
|
#
|
5
8
|
# https://puppet.com/docs/puppet/latest/style_guide.html#quoting
|
6
9
|
PuppetLint.new_check(:variables_not_enclosed) do
|
10
|
+
STRING_TOKEN_TYPES = Set[
|
11
|
+
:DQMID,
|
12
|
+
:DQPOST,
|
13
|
+
:HEREDOC_MID,
|
14
|
+
:HEREDOC_POST,
|
15
|
+
]
|
16
|
+
|
7
17
|
def check
|
8
18
|
tokens.select { |r|
|
9
19
|
r.type == :UNENC_VARIABLE
|
@@ -18,7 +28,68 @@ PuppetLint.new_check(:variables_not_enclosed) do
|
|
18
28
|
end
|
19
29
|
end
|
20
30
|
|
31
|
+
def hash_or_array_ref?(token)
|
32
|
+
token.next_token &&
|
33
|
+
STRING_TOKEN_TYPES.include?(token.next_token.type) &&
|
34
|
+
token.next_token.value.start_with?('[')
|
35
|
+
end
|
36
|
+
|
37
|
+
def extract_hash_or_array_ref(token)
|
38
|
+
scanner = StringScanner.new(token.value)
|
39
|
+
|
40
|
+
brack_depth = 0
|
41
|
+
result = { :ref => '' }
|
42
|
+
|
43
|
+
until scanner.eos?
|
44
|
+
result[:ref] += scanner.getch
|
45
|
+
|
46
|
+
# Pass a length of 1 when slicing the last character from the string
|
47
|
+
# to prevent Ruby 1.8 returning a Fixnum instead of a String.
|
48
|
+
case result[:ref][-1, 1]
|
49
|
+
when '['
|
50
|
+
brack_depth += 1
|
51
|
+
when ']'
|
52
|
+
brack_depth -= 1
|
53
|
+
end
|
54
|
+
|
55
|
+
break if brack_depth.zero? && scanner.peek(1) != '['
|
56
|
+
end
|
57
|
+
|
58
|
+
result[:remainder] = scanner.rest
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
def variable_contains_dash?(token)
|
63
|
+
token.value.include?('-')
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_variable_containing_dash(var_token)
|
67
|
+
str_token = var_token.next_token
|
68
|
+
|
69
|
+
var_name, text = var_token.value.split('-', 2)
|
70
|
+
var_token.value = var_name
|
71
|
+
|
72
|
+
return if str_token.nil?
|
73
|
+
str_token.value = "-#{text}#{str_token.value}"
|
74
|
+
end
|
75
|
+
|
21
76
|
def fix(problem)
|
22
77
|
problem[:token].type = :VARIABLE
|
78
|
+
|
79
|
+
if hash_or_array_ref?(problem[:token])
|
80
|
+
string_token = problem[:token].next_token
|
81
|
+
tokens_index = tokens.index(string_token)
|
82
|
+
|
83
|
+
hash_or_array_ref = extract_hash_or_array_ref(string_token)
|
84
|
+
|
85
|
+
ref_tokens = PuppetLint::Lexer.new.tokenise(hash_or_array_ref[:ref])
|
86
|
+
ref_tokens.each_with_index do |token, i|
|
87
|
+
add_token(tokens_index + i, token)
|
88
|
+
end
|
89
|
+
|
90
|
+
string_token.value = hash_or_array_ref[:remainder]
|
91
|
+
end
|
92
|
+
|
93
|
+
handle_variable_containing_dash(problem[:token]) if variable_contains_dash?(problem[:token])
|
23
94
|
end
|
24
95
|
end
|
@@ -51,7 +51,7 @@ PuppetLint.new_check(:arrow_alignment) do
|
|
51
51
|
this_arrow_column = param_column[level_idx] + param_length + 1
|
52
52
|
else
|
53
53
|
this_arrow_column = param_token.column + param_token.to_manifest.length
|
54
|
-
this_arrow_column += 1
|
54
|
+
this_arrow_column += 1
|
55
55
|
end
|
56
56
|
|
57
57
|
if arrow_column[level_idx] < this_arrow_column
|
@@ -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
|