puppet-lint 0.1.13 → 0.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle/
3
+ .rbenv-version
4
+ Gemfile.lock
5
+ vendor/gems
data/README.md CHANGED
@@ -112,7 +112,7 @@ ERROR: mymodule::myclass not in autoload module layout on line X
112
112
 
113
113
  Puppet attempts to autoload only the required manifests for the resources and
114
114
  classes specified in your manifests. In order to do this, the autoloader
115
- expects your manifests to be layed out on disk in a particular format. For
115
+ expects your manifests to be laid out on disk in a particular format. For
116
116
  example, when you use `mymodule::myclass` in your manifests, Puppet will
117
117
  attempt to read `<modulepath>/mymodule/manifests/myclass.pp`. The only
118
118
  exception to this is when you reference `mymodule` itself (without any
@@ -230,11 +230,11 @@ Placeholder
230
230
  ### puppet-lint
231
231
 
232
232
  You can disable any of the checks when running the `puppet-lint` command by
233
- adding a `--disable-<check name>` flag to the command. For example, if you
233
+ adding a `--no-<check name>-check` flag to the command. For example, if you
234
234
  wanted to skip the 80 character check, you would run
235
235
 
236
236
  ```
237
- puppet-lint --disable-80chars /path/to/my/manifest.pp
237
+ puppet-lint --no-80chars-check /path/to/my/manifest.pp
238
238
  ```
239
239
 
240
240
  puppet-lint will also check for a `.puppet-lintrc` file in the current
@@ -243,9 +243,16 @@ wanted to always skip the hard tab character check, you could create
243
243
  `~./puppet-lintrc` containing
244
244
 
245
245
  ```
246
- --disable-hard_tabs
246
+ --no-hard_tabs-check
247
247
  ```
248
248
 
249
+ For a list of all the flags just type:
250
+
251
+ ```
252
+ puppet-lint --help
253
+ ```
254
+
255
+
249
256
  ### Rake task
250
257
 
251
258
  You can also disable checks when running puppet-lint through the supplied Rake
@@ -253,7 +260,7 @@ task. Simply add the following line after the `require` statement in your
253
260
  `Rakefile`.
254
261
 
255
262
  ``` ruby
256
- PuppetLint.configuration.send("disable_<check name")
263
+ PuppetLint.configuration.send("disable_<check name>")
257
264
  ```
258
265
 
259
266
  So, to disable the 80 character check, you would add:
@@ -262,6 +269,13 @@ So, to disable the 80 character check, you would add:
262
269
  PuppetLint.configuration.send("disable_80chars")
263
270
  ```
264
271
 
272
+ The Rake task also supports ignoring certain paths
273
+ from being linted:
274
+
275
+ ``` ruby
276
+ PuppetLint.configuration.ignore_paths = ["vendor/**/*.pp"]
277
+ ```
278
+
265
279
  ## Reporting bugs or incorrect results
266
280
 
267
281
  If you find a bug in puppet-lint or its results, please create an issue in the
data/bin/puppet-lint CHANGED
@@ -84,6 +84,8 @@ begin
84
84
  if File.directory?(path)
85
85
  Dir.chdir(path)
86
86
  path = Dir.glob('**/*.pp')
87
+ else
88
+ path = [path]
87
89
  end
88
90
 
89
91
  path.each do |f|
data/lib/puppet-lint.rb CHANGED
@@ -1,12 +1,5 @@
1
- # We're doing this instead of a gem dependency so folks using Puppet
2
- # from their distro packages don't have to install the gem.
3
- begin
4
- require 'puppet'
5
- rescue LoadError
6
- puts 'Unable to require puppet. Please gem install puppet and try again.'
7
- exit 1
8
- end
9
-
1
+ require 'puppet-lint/version'
2
+ require 'puppet-lint/lexer'
10
3
  require 'puppet-lint/configuration'
11
4
  require 'puppet-lint/plugin'
12
5
 
@@ -74,8 +67,6 @@ end
74
67
  class PuppetLint::NoCodeError < StandardError; end
75
68
 
76
69
  class PuppetLint
77
- VERSION = '0.1.12'
78
-
79
70
  attr_reader :code, :file
80
71
 
81
72
  def initialize
@@ -53,5 +53,9 @@ class PuppetLint
53
53
  method[0..-10]
54
54
  }
55
55
  end
56
+
57
+ def self.ignore_paths
58
+ settings[:ignore_paths] ||= []
59
+ end
56
60
  end
57
61
  end
@@ -0,0 +1,244 @@
1
+ require 'pp'
2
+ require 'strscan'
3
+
4
+ class PuppetLint
5
+ class Lexer
6
+ KEYWORDS = [
7
+ 'class',
8
+ 'case',
9
+ 'default',
10
+ 'define',
11
+ 'import',
12
+ 'if',
13
+ 'else',
14
+ 'elsif',
15
+ 'inherits',
16
+ 'node',
17
+ 'and',
18
+ 'or',
19
+ 'undef',
20
+ 'true',
21
+ 'false',
22
+ 'in',
23
+ 'unless',
24
+ ]
25
+
26
+ KNOWN_TOKENS = [
27
+ [:CLASSREF, /\A(((::){0,1}[A-Z][-\w]*)+)/],
28
+ [:NUMBER, /\A(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b/],
29
+ [:NAME, /\A(((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*)/],
30
+ [:LBRACK, /\A(\[)/],
31
+ [:RBRACK, /\A(\])/],
32
+ [:LBRACE, /\A(\{)/],
33
+ [:RBRACE, /\A(\})/],
34
+ [:LPAREN, /\A(\()/],
35
+ [:RPAREN, /\A(\))/],
36
+ [:ISEQUAL, /\A(==)/],
37
+ [:MATCH, /\A(=~)/],
38
+ [:FARROW, /\A(=>)/],
39
+ [:EQUALS, /\A(=)/],
40
+ [:APPENDS, /\A(\+=)/],
41
+ [:PARROW, /\A(\+>)/],
42
+ [:PLUS, /\A(\+)/],
43
+ [:GREATEREQUAL, /\A(>=)/],
44
+ [:RSHIFT, /\A(>>)/],
45
+ [:GREATERTHAN, /\A(>)/],
46
+ [:LESSEQUAL, /\A(<=)/],
47
+ [:LLCOLLECT, /\A(<<\|)/],
48
+ [:OUT_EDGE, /\A(<-)/],
49
+ [:OUT_EDGE_SUB, /\A(<~)/],
50
+ [:LCOLLECT, /\A(<\|)/],
51
+ [:LSHIFT, /\A(<<)/],
52
+ [:LESSTHAN, /\A(<)/],
53
+ [:NOMATCH, /\A(!~)/],
54
+ [:NOTEQUAL, /\A(!=)/],
55
+ [:NOT, /\A(!)/],
56
+ [:RRCOLLECT, /\A(\|>>)/],
57
+ [:RCOLLECT, /\A(\|>)/],
58
+ [:IN_EDGE, /\A(->)/],
59
+ [:IN_EDGE_SUB, /\A(~>)/],
60
+ [:MINUS, /\A(-)/],
61
+ [:COMMA, /\A(,)/],
62
+ [:DOT, /\A(\.)/],
63
+ [:COLON, /\A(:)/],
64
+ [:AT, /\A(@)/],
65
+ [:SEMIC, /\A(;)/],
66
+ [:QMARK, /\A(\?)/],
67
+ [:BACKSLASH, /\A(\\)/],
68
+ [:DIV, /\A(\/)/],
69
+ [:TIMES, /\A(\*)/],
70
+ ]
71
+
72
+ def tokens
73
+ @tokens ||= []
74
+ end
75
+
76
+ def tokenise(code)
77
+ code.chomp!
78
+
79
+ i = 0
80
+
81
+ while i < code.size
82
+ chunk = code[i..-1]
83
+
84
+ found = false
85
+
86
+ KNOWN_TOKENS.each do |type, regex|
87
+ if value = chunk[regex, 1]
88
+ if type == :NAME
89
+ if KEYWORDS.include? value
90
+ tokens << new_token(value.upcase.to_sym, value, code[0..i])
91
+ else
92
+ tokens << new_token(type, value, code[0..i])
93
+ end
94
+ else
95
+ tokens << new_token(type, value, code[0..i])
96
+ end
97
+ i += value.size
98
+ found = true
99
+ break
100
+ end
101
+ end
102
+
103
+ unless found
104
+ if identifier = chunk[/\A([a-z]\w*)/, 1]
105
+ tokens << new_token(:IDENTIFIER, identifier, code[0..i])
106
+ i += identifier.size
107
+
108
+ elsif var_name = chunk[/\A\$((::)?([\w-]+::)*[\w-]+)/, 1]
109
+ tokens << new_token(:VARIABLE, var_name, code[0..i])
110
+ i += var_name.size + 1
111
+
112
+ elsif sstring = chunk[/\A'(.*?)'/, 1]
113
+ tokens << new_token(:SSTRING, sstring, code[0..i])
114
+ i += sstring.size + 2
115
+
116
+ elsif chunk.match(/\A"/)
117
+ str_contents = StringScanner.new(code[i+1..-1]).scan_until(/[^\\]"/m)
118
+ _ = code[0..i].split("\n")
119
+ interpolate_string(str_contents, _.count, _.last.length)
120
+ i += str_contents.size + 1
121
+
122
+ elsif chunk.match(/\A\//)
123
+ str_content = StringScanner.new(code[i+1..-1]).scan_until(/[^\\]\//m)
124
+ tokens << new_token(:REGEX, str_content, code[0..i])
125
+ i += str_content.size + 1
126
+
127
+ elsif comment = chunk[/\A(#.*)/, 1]
128
+ comment_size = comment.size
129
+ comment.sub!(/# ?/, '')
130
+ tokens << new_token(:COMMENT, comment, code[0..i])
131
+ i += comment_size
132
+
133
+ elsif slash_comment = chunk[/\A(\/\/.*)/, 1]
134
+ slash_comment_size = slash_comment.size
135
+ slash_comment.sub!(/\/\/ ?/, '')
136
+ tokens << new_token(:SLASH_COMMENT, slash_comment, code[0..i])
137
+ i += slash_comment_size
138
+
139
+ elsif mlcomment = chunk[/\A(\/\*.*?\*\/)/m, 1]
140
+ mlcomment_size = mlcomment_size
141
+ mlcomment.sub!(/^\/\* ?/, '')
142
+ mlcomment.sub!(/ ?\*\/$/, '')
143
+ tokens << new_token(:MLCOMMENT, mlcomment, code[0..i])
144
+ i += mlcomment_size
145
+
146
+ elsif indent = chunk[/\A\n([ \t]+)/m, 1]
147
+ tokens << new_token(:NEWLINE, '\n', code[0..i])
148
+ tokens << new_token(:INDENT, indent, code[0..i+1])
149
+ i += indent.size + 1
150
+
151
+ elsif whitespace = chunk[/\A([ \t]+)/, 1]
152
+ tokens << new_token(:WHITESPACE, whitespace, code[0..i])
153
+ i += whitespace.size
154
+
155
+ elsif chunk.match(/\A\n/)
156
+ tokens << new_token(:NEWLINE, '\n', code[0..i])
157
+ i += 1
158
+
159
+ else
160
+ value = chunk[0,1]
161
+ tokens << new_token(value, value, code[0..i])
162
+ i += 1
163
+ end
164
+ end
165
+ end
166
+
167
+ tokens
168
+ end
169
+
170
+ def new_token(type, value, chunk)
171
+ lines = chunk.split("\n")
172
+ line_no = lines.empty? ? 1 : lines.count
173
+ column = lines.empty? ? 1 : lines.last.length
174
+
175
+ PuppetLint::Token.new(type, value, line_no, column)
176
+ end
177
+
178
+ def get_string_segment(string, terminators)
179
+ str = string.scan_until(/([^\\]|^|[^\\])([\\]{2})*[#{terminators}]/)
180
+ begin
181
+ [str[0..-2], str[-1,1]]
182
+ rescue
183
+ [nil, nil]
184
+ end
185
+ end
186
+
187
+ def interpolate_string(string, line, column)
188
+ ss = StringScanner.new(string)
189
+ first = true
190
+ value, terminator = get_string_segment(ss, '"$')
191
+ until value.nil?
192
+ if terminator == "\""
193
+ if first
194
+ tokens << PuppetLint::Token.new(:STRING, value, line, column)
195
+ first = false
196
+ else
197
+ line += value.count("\n")
198
+ token_column = column + (ss.pos - value.size)
199
+ tokens << PuppetLint::Token.new(:DQPOST, value, line, token_column)
200
+ end
201
+ else
202
+ if first
203
+ tokens << PuppetLint::Token.new(:DQPRE, value, line, column)
204
+ first = false
205
+ else
206
+ line += value.count("\n")
207
+ token_column = column + (ss.pos - value.size)
208
+ tokens << PuppetLint::Token.new(:DQMID, value, line, token_column)
209
+ end
210
+ if ss.scan(/\{/).nil?
211
+ var_name = ss.scan(/(::)?([\w-]+::)*[\w-]+/)
212
+ unless var_name.nil?
213
+ token_column = column + (ss.pos - var_name.size)
214
+ tokens << PuppetLint::Token.new(:UNENC_VARIABLE, var_name, line, token_column)
215
+ end
216
+ else
217
+ var_name = ss.scan(/(::)?([\w-]+::)*[\w-]+/)
218
+ unless var_name.nil?
219
+ token_column = column + (ss.pos - var_name.size)
220
+ tokens << PuppetLint::Token.new(:VARIABLE, var_name, line, token_column)
221
+ ss.scan(/\}/)
222
+ end
223
+ end
224
+ end
225
+ value, terminator = get_string_segment(ss, '"$')
226
+ end
227
+ end
228
+ end
229
+
230
+ class Token
231
+ attr_reader :type, :value, :line, :column
232
+
233
+ def initialize(type, value, line, column)
234
+ @value = value
235
+ @type = type
236
+ @line = line
237
+ @column = column
238
+ end
239
+
240
+ def inspect
241
+ "<Token #{@type.inspect} (#{@value}) @#{@line}:#{@column}>"
242
+ end
243
+ end
244
+ end
@@ -54,9 +54,8 @@ class PuppetLint::CheckPlugin
54
54
  end
55
55
 
56
56
  def run(fileinfo, data)
57
- lexer = Puppet::Parser::Lexer.new
58
- lexer.string = data
59
- @tokens = lexer.fullscan
57
+ lexer = PuppetLint::Lexer.new
58
+ @tokens = lexer.tokenise(data)
60
59
  @fileinfo = fileinfo
61
60
  @data = data
62
61
 
@@ -71,49 +70,6 @@ class PuppetLint::CheckPlugin
71
70
  @problems
72
71
  end
73
72
 
74
- def filter_tokens
75
- @title_tokens = []
76
- @resource_indexes = []
77
- @class_indexes = []
78
- @defined_type_indexes = []
79
-
80
- @tokens.each_index do |token_idx|
81
- if @tokens[token_idx].first == :COLON
82
- # gather a list of tokens that are resource titles
83
- if @tokens[token_idx-1].first == :RBRACK
84
- title_array_tokens = @tokens[@tokens.rindex { |r| r.first == :LBRACK }+1..token_idx-2]
85
- @title_tokens += title_array_tokens.select { |token| [:STRING, :NAME].include? token.first }
86
- else
87
- if @tokens[token_idx + 1].first != :LBRACE
88
- @title_tokens << @tokens[token_idx-1]
89
- end
90
- end
91
-
92
- # gather a list of start and end indexes for resource attribute blocks
93
- if @tokens[token_idx+1].first != :LBRACE
94
- @resource_indexes << {:start => token_idx+1, :end => @tokens[token_idx+1..-1].index { |r| [:SEMIC, :RBRACE].include? r.first }+token_idx}
95
- end
96
- elsif [:CLASS, :DEFINE].include? @tokens[token_idx].first
97
- lbrace_count = 0
98
- @tokens[token_idx+1..-1].each_index do |class_token_idx|
99
- idx = class_token_idx + token_idx
100
- if @tokens[idx].first == :LBRACE
101
- lbrace_count += 1
102
- elsif @tokens[idx].first == :RBRACE
103
- lbrace_count -= 1
104
- if lbrace_count == 0
105
- if @tokens[token_idx].first == :CLASS and @tokens[token_idx + 1].first != :LBRACE
106
- @class_indexes << {:start => token_idx, :end => idx}
107
- end
108
- @defined_type_indexes << {:start => token_idx, :end => idx} if @tokens[token_idx].first == :DEFINE
109
- break
110
- end
111
- end
112
- end
113
- end
114
- end
115
- end
116
-
117
73
  def tokens
118
74
  @tokens
119
75
  end
@@ -131,23 +87,138 @@ class PuppetLint::CheckPlugin
131
87
  end
132
88
 
133
89
  def title_tokens
134
- filter_tokens if @title_tokens.nil?
135
- @title_tokens
90
+ @title_tokens ||= Proc.new do
91
+ result = []
92
+ tokens.each_index do |token_idx|
93
+ if tokens[token_idx].type == :COLON
94
+ # gather a list of tokens that are resource titles
95
+ if tokens[token_idx-1].type == :RBRACK
96
+ array_start_idx = tokens.rindex { |r|
97
+ r.type == :LBRACK
98
+ }
99
+ title_array_tokens = tokens[(array_start_idx + 1)..(token_idx - 2)]
100
+ result += title_array_tokens.select { |token|
101
+ [:STRING, :NAME].include? token.type
102
+ }
103
+ else
104
+ if tokens[token_idx + 1].type != :LBRACE
105
+ result << tokens[token_idx - 1]
106
+ end
107
+ end
108
+ end
109
+ end
110
+ result
111
+ end.call
136
112
  end
137
113
 
114
+ # Internal: Calculate the positions of all resource declarations within the
115
+ # tokenised manifest. These positions only point to the content of the
116
+ # resource declaration, they do not include resource types or
117
+ # titles/namevars.
118
+ #
119
+ # Returns an Array of Hashes, each containing:
120
+ # :start - An Integer position in the `tokens` Array pointing to the first
121
+ # Token of a resource declaration parameters (type :NAME).
122
+ # :end - An Integer position in the `tokens` Array pointing to the last
123
+ # Token of a resource declaration parameters (type :RBRACE).
138
124
  def resource_indexes
139
- filter_tokens if @resource_indexes.nil?
140
- @resource_indexes
125
+ @resource_indexes ||= Proc.new do
126
+ result = []
127
+ tokens.each_index do |token_idx|
128
+ if tokens[token_idx].type == :COLON
129
+ next_tokens = tokens[(token_idx + 1)..-1].reject { |r|
130
+ formatting_tokens.include? r.type
131
+ }
132
+ if next_tokens.first.type != :LBRACE
133
+ end_idx = tokens[(token_idx + 1)..-1].index { |r|
134
+ [:SEMIC, :RBRACE].include? r.type
135
+ } + token_idx
136
+
137
+ result << {:start => token_idx + 1, :end => end_idx}
138
+ end
139
+ end
140
+ end
141
+ result
142
+ end.call
141
143
  end
142
144
 
145
+ # Internal: Calculate the positions of all class definitions within the
146
+ # tokenised manifest.
147
+ #
148
+ # Returns an Array of Hashes, each containing:
149
+ # :start - An Integer position in the `tokens` Array pointing to the first
150
+ # token of a class (type :CLASS).
151
+ # :end - An Integer position in the `tokens` Array pointing to the last
152
+ # token of a class (type :RBRACE).
143
153
  def class_indexes
144
- filter_tokens if @class_indexes.nil?
145
- @class_indexes
154
+ @class_indexes ||= Proc.new do
155
+ result = []
156
+ tokens.each_index do |token_idx|
157
+ if tokens[token_idx].type == :CLASS
158
+ depth = 0
159
+ tokens[token_idx+1..-1].each_index do |class_token_idx|
160
+ idx = class_token_idx + token_idx + 1
161
+ if tokens[idx].type == :LBRACE
162
+ depth += 1
163
+ elsif tokens[idx].type == :RBRACE
164
+ depth -= 1
165
+ if depth == 0
166
+ if tokens[token_idx..-1].reject { |r|
167
+ r.type == :WHITESPACE
168
+ }[1].type != :LBRACE
169
+ result << {:start => token_idx, :end => idx}
170
+ end
171
+ break
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ result
178
+ end.call
146
179
  end
147
180
 
181
+ # Internal: Calculate the positions of all defined type definitions within
182
+ # the tokenised manifest.
183
+ #
184
+ # Returns an Array of Hashes, each containing:
185
+ # :start - An Integer position in the `tokens` Array pointing to the first
186
+ # token of a defined type (type :DEFINE).
187
+ # :end - An Integer position in the `tokens` Array pointing to the last
188
+ # token of a defined type (type :RBRACE).
148
189
  def defined_type_indexes
149
- filter_tokens if @defined_type_indexes.nil?
150
- @defined_type_indexes
190
+ @defined_type_indexes ||= Proc.new do
191
+ result = []
192
+ tokens.each_index do |token_idx|
193
+ if tokens[token_idx].type == :DEFINE
194
+ depth = 0
195
+ tokens[token_idx+1..-1].each_index do |define_token_idx|
196
+ idx = define_token_idx + token_idx + 1
197
+ if tokens[idx].type == :LBRACE
198
+ depth += 1
199
+ elsif tokens[idx].type == :RBRACE
200
+ depth -= 1
201
+ if depth == 0
202
+ result << {:start => token_idx, :end => idx}
203
+ break
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ result
210
+ end.call
211
+ end
212
+
213
+ def formatting_tokens
214
+ [
215
+ :COMMENT,
216
+ :MLCOMMENT,
217
+ :SLASH_COMENT,
218
+ :INDENT,
219
+ :WHITESPACE,
220
+ :NEWLINE,
221
+ ]
151
222
  end
152
223
 
153
224
  def manifest_lines