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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +238 -87
  3. data/README.md +18 -0
  4. data/lib/puppet-lint.rb +1 -1
  5. data/lib/puppet-lint/data.rb +26 -11
  6. data/lib/puppet-lint/lexer.rb +97 -200
  7. data/lib/puppet-lint/lexer/string_slurper.rb +173 -0
  8. data/lib/puppet-lint/lexer/token.rb +8 -0
  9. data/lib/puppet-lint/optparser.rb +4 -5
  10. data/lib/puppet-lint/plugins/check_classes/parameter_order.rb +12 -1
  11. data/lib/puppet-lint/plugins/check_conditionals/case_without_default.rb +15 -1
  12. data/lib/puppet-lint/plugins/check_documentation/documentation.rb +4 -0
  13. data/lib/puppet-lint/plugins/check_resources/ensure_first_param.rb +5 -2
  14. data/lib/puppet-lint/plugins/check_strings/quoted_booleans.rb +1 -0
  15. data/lib/puppet-lint/plugins/check_strings/variables_not_enclosed.rb +71 -0
  16. data/lib/puppet-lint/plugins/check_whitespace/arrow_alignment.rb +1 -1
  17. data/lib/puppet-lint/tasks/puppet-lint.rb +14 -0
  18. data/lib/puppet-lint/tasks/release_test.rb +3 -1
  19. data/lib/puppet-lint/version.rb +1 -1
  20. data/spec/fixtures/test/manifests/two_warnings.pp +5 -0
  21. data/spec/puppet-lint/bin_spec.rb +47 -6
  22. data/spec/puppet-lint/data_spec.rb +12 -0
  23. data/spec/puppet-lint/lexer/string_slurper_spec.rb +473 -0
  24. data/spec/puppet-lint/lexer_spec.rb +1153 -590
  25. data/spec/puppet-lint/plugins/check_classes/parameter_order_spec.rb +18 -0
  26. data/spec/puppet-lint/plugins/check_classes/variable_scope_spec.rb +15 -1
  27. data/spec/puppet-lint/plugins/check_conditionals/case_without_default_spec.rb +39 -0
  28. data/spec/puppet-lint/plugins/check_documentation/documentation_spec.rb +18 -0
  29. data/spec/puppet-lint/plugins/check_resources/ensure_first_param_spec.rb +16 -0
  30. data/spec/puppet-lint/plugins/check_strings/double_quoted_strings_spec.rb +5 -5
  31. data/spec/puppet-lint/plugins/check_strings/only_variable_string_spec.rb +6 -6
  32. data/spec/puppet-lint/plugins/check_strings/variables_not_enclosed_spec.rb +32 -0
  33. data/spec/puppet-lint/plugins/check_variables/variable_is_lowercase_spec.rb +28 -0
  34. data/spec/spec_helper.rb +7 -5
  35. metadata +14 -17
  36. data/.gitignore +0 -12
  37. data/.rspec +0 -2
  38. data/.rubocop.yml +0 -74
  39. data/.rubocop_todo.yml +0 -89
  40. data/.travis.yml +0 -24
  41. data/Gemfile +0 -40
  42. data/Rakefile +0 -42
  43. data/appveyor.yml +0 -33
  44. 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
- noconfig = true
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
- opt_parser.parse!(args) unless args.empty?
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 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
 
@@ -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
- next if case_tokens.index { |r| r.type == :DEFAULT }
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 = 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
+
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? || ensure_param_name_token.nil? || ensure_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
 
@@ -25,3 +25,4 @@ PuppetLint.new_check(:quoted_booleans) do
25
25
  problem[:token].type = problem[:token].value.upcase.to_sym
26
26
  end
27
27
  end
28
+ PuppetLint.configuration.send('disable_quoted_booleans')
@@ -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 unless param_token.type == :DQPOST
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