puppet-lint 2.3.6 → 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.
- 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
|