foodcritic 0.11.1 → 1.0.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.
- data/lib/foodcritic.rb +1 -1
- data/lib/foodcritic/{helpers.rb → api.rb} +217 -140
- data/lib/foodcritic/chef.rb +35 -20
- data/lib/foodcritic/command_line.rb +21 -5
- data/lib/foodcritic/domain.rb +7 -2
- data/lib/foodcritic/dsl.rb +13 -7
- data/lib/foodcritic/linter.rb +37 -25
- data/lib/foodcritic/rules.rb +25 -16
- data/lib/foodcritic/version.rb +1 -1
- metadata +21 -35
data/lib/foodcritic.rb
CHANGED
@@ -9,7 +9,7 @@ require_relative 'foodcritic/chef'
|
|
9
9
|
require_relative 'foodcritic/command_line'
|
10
10
|
require_relative 'foodcritic/domain'
|
11
11
|
require_relative 'foodcritic/error_checker'
|
12
|
-
require_relative 'foodcritic/
|
12
|
+
require_relative 'foodcritic/api'
|
13
13
|
require_relative 'foodcritic/dsl'
|
14
14
|
require_relative 'foodcritic/linter'
|
15
15
|
require_relative 'foodcritic/output'
|
@@ -3,36 +3,55 @@ require 'nokogiri'
|
|
3
3
|
module FoodCritic
|
4
4
|
|
5
5
|
# Helper methods that form part of the Rules DSL.
|
6
|
-
module
|
6
|
+
module Api
|
7
7
|
|
8
8
|
include FoodCritic::Chef
|
9
|
-
include FoodCritic::Chef::Search
|
10
9
|
|
11
|
-
#
|
10
|
+
# Find attribute accesses by type.
|
12
11
|
#
|
13
|
-
# @param [Nokogiri::XML::Node]
|
14
|
-
# @
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
# @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check
|
13
|
+
# @param [Symbol] type The approach used to access the attributes
|
14
|
+
# (:string, :symbol or :vivified).
|
15
|
+
# @param [Boolean] ignore_calls Exclude attribute accesses that mix
|
16
|
+
# strings/symbols with dot notation. Defaults to false.
|
17
|
+
# @return [Array] The matching nodes if any
|
18
|
+
def attribute_access(ast, options = {})
|
19
|
+
options = {:type => :any, :ignore_calls => false}.merge!(options)
|
20
|
+
return [] unless ast.respond_to?(:xpath)
|
21
|
+
unless [:string, :symbol, :vivified].include?(options[:type])
|
22
|
+
raise ArgumentError, "Node type not recognised"
|
23
|
+
end
|
19
24
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
(if options[:type] == :vivified
|
26
|
+
calls = ast.xpath(%q{//*[self::call or self::field]
|
27
|
+
[is_att_type(vcall/ident/@value) or
|
28
|
+
is_att_type(var_ref/ident/@value)][@value='.']}, AttFilter.new)
|
29
|
+
calls.select do |call|
|
30
|
+
call.xpath("aref/args_add_block").size == 0 and
|
31
|
+
(call.xpath("descendant::ident").size > 1 and
|
32
|
+
! chef_dsl_methods.include?(call.xpath("ident/@value").to_s.to_sym))
|
33
|
+
end
|
34
|
+
else
|
35
|
+
type = (options[:type] == :string) ? 'tstring_content' : options[:type]
|
36
|
+
expr = '//*[self::aref_field or self::aref]'
|
37
|
+
expr += '[is_att_type(descendant::ident'
|
38
|
+
expr += '[not(ancestor::aref/call)]' if options[:ignore_calls]
|
39
|
+
expr += "/@value)]/descendant::#{type}"
|
40
|
+
ast.xpath(expr, AttFilter.new)
|
41
|
+
end
|
42
|
+
).sort
|
27
43
|
end
|
28
44
|
|
29
45
|
# Does the specified recipe check for Chef Solo?
|
30
46
|
#
|
31
47
|
# @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check.
|
32
|
-
# @return [Boolean] True if there is a test for Chef::Config[:solo] in the
|
48
|
+
# @return [Boolean] True if there is a test for Chef::Config[:solo] in the
|
49
|
+
# recipe
|
33
50
|
def checks_for_chef_solo?(ast)
|
34
|
-
!
|
35
|
-
|
51
|
+
raise_unless_xpath!(ast)
|
52
|
+
! ast.xpath(%q{//if/aref[count(descendant::const[@value = 'Chef' or
|
53
|
+
@value = 'Config']) = 2
|
54
|
+
and count(descendant::ident[@value='solo']) > 0]}).empty?
|
36
55
|
end
|
37
56
|
|
38
57
|
# Is the chef-solo-search library available?
|
@@ -40,93 +59,144 @@ module FoodCritic
|
|
40
59
|
# @param [String] recipe_path The path to the current recipe
|
41
60
|
# @return [Boolean] True if the chef-solo-search library is available.
|
42
61
|
def chef_solo_search_supported?(recipe_path)
|
43
|
-
|
62
|
+
return false if recipe_path.nil? || ! File.exists?(recipe_path)
|
63
|
+
cbk_tree_path = Pathname.new(File.join(recipe_path, '../../..'))
|
64
|
+
search_libs = Dir[File.join(cbk_tree_path.realpath, "*/libraries/search.rb")]
|
44
65
|
search_libs.any? do |lib|
|
45
|
-
!
|
46
|
-
|
66
|
+
! read_ast(lib).xpath(%q{//class[count(descendant::const[@value='Chef']
|
67
|
+
) = 1]/descendant::def/ident[@value='search']}).empty?
|
47
68
|
end
|
48
69
|
end
|
49
70
|
|
50
|
-
#
|
71
|
+
# The name of the cookbook containing the specified file.
|
51
72
|
#
|
52
|
-
# @param [
|
53
|
-
# @return [
|
54
|
-
def
|
55
|
-
|
73
|
+
# @param [String] file The file in the cookbook
|
74
|
+
# @return [String] The name of the containing cookbook
|
75
|
+
def cookbook_name(file)
|
76
|
+
raise ArgumentError, 'File cannot be nil or empty' if file.to_s.empty?
|
77
|
+
until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty? do
|
78
|
+
file = File.absolute_path(File.dirname(file.to_s))
|
79
|
+
end
|
80
|
+
file = File.dirname(file) unless File.extname(file).empty?
|
81
|
+
File.basename(file)
|
56
82
|
end
|
57
83
|
|
58
|
-
#
|
59
|
-
# subexpression will be ignored.
|
84
|
+
# The dependencies declared in cookbook metadata.
|
60
85
|
#
|
61
|
-
# @param [Nokogiri::XML::Node] ast The
|
62
|
-
# @return [
|
63
|
-
def
|
64
|
-
ast
|
86
|
+
# @param [Nokogiri::XML::Node] ast The metadata rb AST
|
87
|
+
# @return [Array] List of cookbooks depended on
|
88
|
+
def declared_dependencies(ast)
|
89
|
+
raise_unless_xpath!(ast)
|
90
|
+
deps = ast.xpath(%q{//command[ident/@value='depends']/
|
91
|
+
descendant::args_add/descendant::tstring_content})
|
92
|
+
# handle quoted word arrays
|
93
|
+
var_ref = ast.xpath(%q{//command[ident/@value='depends']/
|
94
|
+
descendant::var_ref/ident})
|
95
|
+
unless var_ref.empty?
|
96
|
+
deps += ast.xpath(%Q{//block_var/params/ident#{var_ref.first['value']}/
|
97
|
+
ancestor::method_add_block/call/descendant::tstring_content})
|
98
|
+
end
|
99
|
+
deps.map{|dep| dep['value']}
|
100
|
+
end
|
101
|
+
|
102
|
+
# Create a match for a specified file. Use this if the presence of the file
|
103
|
+
# triggers the warning rather than content.
|
104
|
+
#
|
105
|
+
# @param [String] file The filename to create a match for
|
106
|
+
# @return [Hash] Hash with the match details
|
107
|
+
# @see FoodCritic::Api#match
|
108
|
+
def file_match(file)
|
109
|
+
raise ArgumentError, "Filename cannot be nil" if file.nil?
|
110
|
+
{:filename => file, :matched => file, :line => 1, :column => 1}
|
111
|
+
end
|
112
|
+
|
113
|
+
# Find Chef resources of the specified type.
|
114
|
+
# TODO: Include blockless resources
|
115
|
+
#
|
116
|
+
# @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check
|
117
|
+
# @param [Hash] options The find options
|
118
|
+
# @option [Symbol] :type The type of resource to look for (or :any for all
|
119
|
+
# resources)
|
120
|
+
# @return [Array] AST nodes of Chef resources.
|
121
|
+
def find_resources(ast, options = {})
|
122
|
+
options = {:type => :any}.merge!(options)
|
123
|
+
return [] unless ast.respond_to?(:xpath)
|
124
|
+
scope_type = ''
|
125
|
+
scope_type = "[@value='#{options[:type]}']" unless options[:type] == :any
|
126
|
+
ast.xpath("//method_add_block[command/ident#{scope_type}]")
|
127
|
+
end
|
128
|
+
|
129
|
+
# Retrieve the recipes that are included within the given recipe AST.
|
130
|
+
#
|
131
|
+
# @param [Nokogiri::XML::Node] ast The recipe AST
|
132
|
+
# @return [Hash] include_recipe nodes keyed by included recipe name
|
133
|
+
def included_recipes(ast)
|
134
|
+
raise_unless_xpath!(ast)
|
135
|
+
# we only support literal strings, ignoring sub-expressions
|
136
|
+
included = ast.xpath(%q{//command[ident/@value = 'include_recipe' and
|
137
|
+
count(descendant::string_embexpr) = 0]/descendant::tstring_content})
|
138
|
+
included.inject(Hash.new([])){|h, i| h[i['value']] += [i]; h}
|
65
139
|
end
|
66
140
|
|
67
141
|
class AttFilter
|
68
142
|
def is_att_type(value)
|
143
|
+
return [] unless value.respond_to?(:select)
|
69
144
|
value.select{|n| %w{node default override set normal}.include?(n.to_s)}
|
70
145
|
end
|
71
146
|
end
|
72
147
|
|
73
|
-
#
|
148
|
+
# Searches performed by the specified recipe that are literal strings.
|
149
|
+
# Searches with a query formed from a subexpression will be ignored.
|
74
150
|
#
|
75
151
|
# @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check
|
76
|
-
# @
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
(
|
81
|
-
calls = ast.xpath(%Q{//*[self::call or self::field][is_att_type(vcall/ident/@value) or
|
82
|
-
is_att_type(var_ref/ident/@value)][@value='.']}, AttFilter.new)
|
83
|
-
calls.select do |call|
|
84
|
-
call.xpath("aref/args_add_block").size == 0 and (call.xpath("descendant::ident").size > 1 and
|
85
|
-
! dsl_methods.include?(call.xpath("ident/@value").to_s.to_sym))
|
86
|
-
end
|
87
|
-
else
|
88
|
-
accessed_via = 'tstring_content' if accessed_via == :string
|
89
|
-
expr = '//*[self::aref_field or self::aref][is_att_type(descendant::ident'
|
90
|
-
expr += '[not(ancestor::aref/call)]' if exclude_with_dots
|
91
|
-
expr += "/@value)]/descendant::#{accessed_via}"
|
92
|
-
ast.xpath(expr, AttFilter.new)
|
93
|
-
end
|
94
|
-
).sort
|
152
|
+
# @return [Array] The matching nodes
|
153
|
+
def literal_searches(ast)
|
154
|
+
return [] unless ast.respond_to?(:xpath)
|
155
|
+
ast.xpath("//method_add_arg[fcall/ident/@value = 'search' and
|
156
|
+
count(descendant::string_embexpr) = 0]/descendant::tstring_content")
|
95
157
|
end
|
96
158
|
|
97
|
-
#
|
98
|
-
# TODO: Include blockless resources
|
159
|
+
# Create a match from the specified node.
|
99
160
|
#
|
100
|
-
# @param [Nokogiri::XML::Node]
|
101
|
-
# @
|
102
|
-
|
103
|
-
|
104
|
-
|
161
|
+
# @param [Nokogiri::XML::Node] node The node to create a match for
|
162
|
+
# @return [Hash] Hash with the matched node name and position with the recipe
|
163
|
+
def match(node)
|
164
|
+
raise_unless_xpath!(node)
|
165
|
+
pos = node.xpath('descendant::pos').first
|
166
|
+
return nil if pos.nil?
|
167
|
+
{:matched => node.respond_to?(:name) ? node.name : '',
|
168
|
+
:line => pos['line'].to_i, :column => pos['column'].to_i}
|
105
169
|
end
|
106
170
|
|
107
|
-
#
|
171
|
+
# Does the provided string look like an Operating System command? This is a
|
172
|
+
# rough heuristic to be taken with a pinch of salt.
|
108
173
|
#
|
109
|
-
# @param [
|
110
|
-
# @return [
|
111
|
-
def
|
112
|
-
|
174
|
+
# @param [String] str The string to check
|
175
|
+
# @return [Boolean] True if this string might be an OS command
|
176
|
+
def os_command?(str)
|
177
|
+
str.start_with?('grep ', 'which ') or # common commands
|
178
|
+
str.include?('|') or # probably a pipe, could be alternation
|
179
|
+
str.match(/^[\w]+$/) or # command name only
|
180
|
+
str.match(/ --?[a-z]/i) # command-line flag
|
113
181
|
end
|
114
182
|
|
115
|
-
#
|
183
|
+
# Read the AST for the given Ruby source file
|
116
184
|
#
|
117
|
-
# @param [
|
118
|
-
# @return [
|
119
|
-
def
|
120
|
-
|
185
|
+
# @param [String] file The file to read
|
186
|
+
# @return [Nokogiri::XML::Node] The recipe AST
|
187
|
+
def read_ast(file)
|
188
|
+
build_xml(Ripper::SexpBuilder.new(File.read(file)).parse)
|
121
189
|
end
|
122
190
|
|
123
191
|
# Retrieve a single-valued attribute from the specified resource.
|
124
192
|
#
|
193
|
+
# @param [Nokogiri::XML::Node] resource The resource AST to lookup the
|
194
|
+
# attribute under
|
125
195
|
# @param [String] name The attribute name
|
126
|
-
# @param [Nokogiri::XML::Node] resource The resource AST to lookup the attribute under
|
127
196
|
# @return [String] The attribute value for the specified attribute
|
128
|
-
def resource_attribute(
|
129
|
-
|
197
|
+
def resource_attribute(resource, name)
|
198
|
+
raise ArgumentError, "Attribute name cannot be empty" if name.empty?
|
199
|
+
resource_attributes(resource)[name.to_s]
|
130
200
|
end
|
131
201
|
|
132
202
|
# Retrieve all attributes from the specified resource.
|
@@ -134,8 +204,11 @@ module FoodCritic
|
|
134
204
|
# @param [Nokogiri::XML::Node] resource The resource AST
|
135
205
|
# @return [Hash] The resource attributes
|
136
206
|
def resource_attributes(resource)
|
137
|
-
atts = {
|
138
|
-
|
207
|
+
atts = {}
|
208
|
+
name = resource_name(resource)
|
209
|
+
atts[:name] = name unless name.empty?
|
210
|
+
resource.xpath('do_block/descendant::command
|
211
|
+
[count(ancestor::do_block) = 1]').each do |att|
|
139
212
|
if att.xpath('descendant::symbol').empty?
|
140
213
|
att_value = att.xpath('string(descendant::tstring_content/@value)')
|
141
214
|
else
|
@@ -146,20 +219,10 @@ module FoodCritic
|
|
146
219
|
atts
|
147
220
|
end
|
148
221
|
|
149
|
-
# Retrieve all resources of a given type
|
150
|
-
#
|
151
|
-
# @param [Nokogiri::XML::Node] ast The recipe AST
|
152
|
-
# @return [Hash] The matching resources
|
153
|
-
def resources_by_type(ast)
|
154
|
-
result = Hash.new{|hash, key| hash[key] = Array.new}
|
155
|
-
find_resources(ast).each{|resource| result[resource_type(resource)] << resource}
|
156
|
-
result
|
157
|
-
end
|
158
|
-
|
159
222
|
# Retrieve the attributes as a hash for all resources of a given type.
|
160
223
|
#
|
161
224
|
# @param [Nokogiri::XML::Node] ast The recipe AST
|
162
|
-
# @return [Hash]
|
225
|
+
# @return [Hash] Resources keyed by type, with an array for each
|
163
226
|
def resource_attributes_by_type(ast)
|
164
227
|
result = {}
|
165
228
|
resources_by_type(ast).each do |type,resources|
|
@@ -168,47 +231,75 @@ module FoodCritic
|
|
168
231
|
result
|
169
232
|
end
|
170
233
|
|
171
|
-
# Retrieve the
|
234
|
+
# Retrieve the name attribute associated with the specified resource.
|
235
|
+
#
|
236
|
+
# @param [Nokogiri::XML::Node] resource The resource AST to lookup the name
|
237
|
+
# attribute under
|
238
|
+
# @return [String] The name attribute value
|
239
|
+
def resource_name(resource)
|
240
|
+
raise_unless_xpath!(resource)
|
241
|
+
resource.xpath('string(command//tstring_content/@value)')
|
242
|
+
end
|
243
|
+
|
244
|
+
# Retrieve all resources of a given type
|
172
245
|
#
|
173
246
|
# @param [Nokogiri::XML::Node] ast The recipe AST
|
174
|
-
# @return [Hash]
|
175
|
-
def
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
247
|
+
# @return [Hash] The matching resources
|
248
|
+
def resources_by_type(ast)
|
249
|
+
raise_unless_xpath!(ast)
|
250
|
+
result = Hash.new{|hash, key| hash[key] = Array.new}
|
251
|
+
find_resources(ast).each do |resource|
|
252
|
+
result[resource_type(resource)] << resource
|
253
|
+
end
|
254
|
+
result
|
180
255
|
end
|
181
256
|
|
182
|
-
#
|
257
|
+
# Return the type, e.g. 'package' for a given resource
|
183
258
|
#
|
184
|
-
# @param [
|
185
|
-
# @return [String] The
|
186
|
-
def
|
187
|
-
|
259
|
+
# @param [Nokogiri::XML::Node] resource The resource AST
|
260
|
+
# @return [String] The type of resource
|
261
|
+
def resource_type(resource)
|
262
|
+
raise_unless_xpath!(resource)
|
263
|
+
type = resource.xpath('string(command/ident/@value)')
|
264
|
+
if type.empty?
|
265
|
+
raise ArgumentError, "Provided AST node is not a resource"
|
266
|
+
end
|
267
|
+
type
|
188
268
|
end
|
189
269
|
|
190
|
-
#
|
270
|
+
# Does the provided string look like ruby code?
|
191
271
|
#
|
192
|
-
# @param [
|
193
|
-
# @return [
|
194
|
-
def
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
deps.map{|dep| dep['value']}
|
272
|
+
# @param [String] str The string to check for rubiness
|
273
|
+
# @return [Boolean] True if this string could be syntactically valid Ruby
|
274
|
+
def ruby_code?(str)
|
275
|
+
str = str.to_s
|
276
|
+
return false if str.empty?
|
277
|
+
checker = FoodCritic::ErrorChecker.new(str)
|
278
|
+
checker.parse
|
279
|
+
! checker.error?
|
201
280
|
end
|
202
281
|
|
203
|
-
#
|
282
|
+
# Searches performed by the specified recipe.
|
204
283
|
#
|
205
|
-
# @param [Nokogiri::XML::Node]
|
206
|
-
# @return [
|
207
|
-
def
|
208
|
-
|
284
|
+
# @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check.
|
285
|
+
# @return [Array] The AST nodes in the recipe where searches are performed
|
286
|
+
def searches(ast)
|
287
|
+
return [] unless ast.respond_to?(:xpath)
|
288
|
+
ast.xpath("//fcall/ident[@value = 'search']")
|
209
289
|
end
|
210
290
|
|
211
|
-
#
|
291
|
+
# The list of standard cookbook sub-directories.
|
292
|
+
#
|
293
|
+
# @return [Array] The standard list of directories.
|
294
|
+
def standard_cookbook_subdirs
|
295
|
+
%w{attributes definitions files libraries providers recipes resources
|
296
|
+
templates}
|
297
|
+
end
|
298
|
+
|
299
|
+
private
|
300
|
+
|
301
|
+
# Recurse the nested arrays provided by Ripper to create a tree we can more
|
302
|
+
# easily apply expressions to.
|
212
303
|
#
|
213
304
|
# @param [Array] node The AST
|
214
305
|
# @param [Nokogiri::XML::Document] doc The document being constructed
|
@@ -228,7 +319,8 @@ module FoodCritic
|
|
228
319
|
xml_node.add_child(pos)
|
229
320
|
else
|
230
321
|
if child.respond_to?(:first)
|
231
|
-
n = Nokogiri::XML::Node.new(
|
322
|
+
n = Nokogiri::XML::Node.new(
|
323
|
+
child.first.to_s.gsub(/[^a-z_]/, ''), doc)
|
232
324
|
xml_node.add_child(build_xml(child, doc, n))
|
233
325
|
else
|
234
326
|
xml_node['value'] = child.to_s unless child.nil?
|
@@ -239,34 +331,19 @@ module FoodCritic
|
|
239
331
|
xml_node
|
240
332
|
end
|
241
333
|
|
242
|
-
#
|
243
|
-
#
|
244
|
-
# @param [String] file The file to read
|
245
|
-
# @return [Nokogiri::XML::Node] The recipe AST
|
246
|
-
def read_file(file)
|
247
|
-
build_xml(Ripper::SexpBuilder.new(IO.read(file)).parse)
|
248
|
-
end
|
249
|
-
|
250
|
-
# Does the provided string look like ruby code?
|
334
|
+
# If the provided node is the line / column information.
|
251
335
|
#
|
252
|
-
# @param [
|
253
|
-
# @return [Boolean] True if this
|
254
|
-
def
|
255
|
-
|
256
|
-
|
257
|
-
! checker.error?
|
336
|
+
# @param [Nokogiri::XML::Node] node A node within the AST
|
337
|
+
# @return [Boolean] True if this node holds the position data
|
338
|
+
def position_node?(node)
|
339
|
+
node.respond_to?(:length) and node.length == 2 and
|
340
|
+
node.respond_to?(:all?) and node.all?{|child| child.respond_to?(:to_i)}
|
258
341
|
end
|
259
342
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
# @return [Boolean] True if this string might be an OS command
|
265
|
-
def os_command?(str)
|
266
|
-
str.start_with?('grep ', 'which ') or # common commands
|
267
|
-
str.include?('|') or # probably a pipe, could be alternation
|
268
|
-
str.match(/^[\w]+$/) or # command name only
|
269
|
-
str.match(/ --?[a-z]/i) # command-line flag
|
343
|
+
def raise_unless_xpath!(ast)
|
344
|
+
unless ast.respond_to?(:xpath)
|
345
|
+
raise ArgumentError, "AST must support #xpath"
|
346
|
+
end
|
270
347
|
end
|
271
348
|
|
272
349
|
end
|
data/lib/foodcritic/chef.rb
CHANGED
@@ -3,31 +3,50 @@ module FoodCritic
|
|
3
3
|
# Encapsulates functions that previously were calls to the Chef gem.
|
4
4
|
module Chef
|
5
5
|
|
6
|
-
def load_metadata
|
7
|
-
metadata_path = File.join(File.dirname(__FILE__), '..', '..', 'chef_dsl_metadata.json')
|
8
|
-
@dsl_metadata ||= Yajl::Parser.parse(IO.read(metadata_path), :symbolize_keys => true)
|
9
|
-
end
|
10
|
-
|
11
6
|
# The set of methods in the Chef DSL
|
12
7
|
#
|
13
8
|
# @return [Array] Array of method symbols
|
14
|
-
def
|
9
|
+
def chef_dsl_methods
|
15
10
|
load_metadata
|
16
11
|
@dsl_metadata[:dsl_methods].map{|m| m.to_sym}
|
17
12
|
end
|
18
13
|
|
19
|
-
# Is the specified attribute valid for the type of resource?
|
14
|
+
# Is the specified attribute valid for the type of resource? Note that this
|
15
|
+
# method will return true if the resource_type is not recognised.
|
20
16
|
#
|
21
17
|
# @param [Symbol] resource_type The type of Chef resource
|
22
18
|
# @param [Symbol] attribute_name The attribute name
|
23
|
-
def
|
19
|
+
def resource_attribute?(resource_type, attribute_name)
|
20
|
+
if resource_type.to_s.empty? || attribute_name.to_s.empty?
|
21
|
+
raise ArgumentError, "Arguments cannot be nil or empty."
|
22
|
+
end
|
24
23
|
load_metadata
|
25
24
|
resource_attributes = @dsl_metadata[:attributes]
|
26
|
-
return true unless resource_attributes.include?(resource_type)
|
27
|
-
resource_attributes[resource_type].include?(attribute_name.to_s)
|
25
|
+
return true unless resource_attributes.include?(resource_type.to_sym)
|
26
|
+
resource_attributes[resource_type.to_sym].include?(attribute_name.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Is this a valid Lucene query?
|
30
|
+
#
|
31
|
+
# @param [String] query The query to check for syntax errors
|
32
|
+
# @return [Boolean] True if the query is well-formed
|
33
|
+
def valid_query?(query)
|
34
|
+
raise ArgumentError, "Query cannot be nil or empty" if query.to_s.empty?
|
35
|
+
search = FoodCritic::Chef::Search.new
|
36
|
+
search.create_parser(search.chef_search_grammars)
|
37
|
+
search.parser? ? (! search.parser.parse(query.to_s).nil?) : true
|
28
38
|
end
|
29
39
|
|
30
|
-
|
40
|
+
private
|
41
|
+
|
42
|
+
def load_metadata
|
43
|
+
metadata_path = File.join(File.dirname(__FILE__), '..', '..',
|
44
|
+
'chef_dsl_metadata.json')
|
45
|
+
@dsl_metadata ||= Yajl::Parser.parse(IO.read(metadata_path),
|
46
|
+
:symbolize_keys => true)
|
47
|
+
end
|
48
|
+
|
49
|
+
class Search
|
31
50
|
|
32
51
|
# The search grammars that ship with any Chef gems installed locally.
|
33
52
|
# These are returned in descending version order (a newer Chef version
|
@@ -43,7 +62,7 @@ module FoodCritic
|
|
43
62
|
# Create the search parser from the first loadable grammar.
|
44
63
|
#
|
45
64
|
# @param [Array] grammar_paths Full paths to candidate treetop grammars
|
46
|
-
def
|
65
|
+
def create_parser(grammar_paths)
|
47
66
|
@search_parser ||= grammar_paths.inject(nil) do |parser,lucene_grammar|
|
48
67
|
begin
|
49
68
|
break parser unless parser.nil?
|
@@ -59,18 +78,14 @@ module FoodCritic
|
|
59
78
|
# Has the search parser been loaded?
|
60
79
|
#
|
61
80
|
# @return [Boolean] True if the search parser has been loaded.
|
62
|
-
def
|
81
|
+
def parser?
|
63
82
|
! @search_parser.nil?
|
64
83
|
end
|
65
84
|
|
66
|
-
|
67
|
-
|
68
|
-
# @param [String] query The query to check for syntax errors
|
69
|
-
# @return [Boolean] True if the query is well-formed
|
70
|
-
def valid_query?(query)
|
71
|
-
load_search_parser(chef_search_grammars)
|
72
|
-
search_parser_loaded? ? (! @search_parser.parse(query).nil?) : true
|
85
|
+
def parser
|
86
|
+
@search_parser
|
73
87
|
end
|
88
|
+
|
74
89
|
end
|
75
90
|
end
|
76
91
|
|
@@ -3,22 +3,23 @@ module FoodCritic
|
|
3
3
|
# Command line parsing.
|
4
4
|
class CommandLine
|
5
5
|
|
6
|
-
include FoodCritic::Chef::Search
|
7
|
-
|
8
6
|
# Create a new instance of CommandLine
|
9
7
|
#
|
10
8
|
# @param [Array] args The command line arguments
|
11
9
|
def initialize(args)
|
12
10
|
@args = args
|
11
|
+
@original_args = args.dup
|
13
12
|
@options = {}
|
14
|
-
@options[:fail_tags] = []; @options[:tags] = []
|
13
|
+
@options[:fail_tags] = []; @options[:tags] = []; @options[:include_rules] = []
|
15
14
|
@parser = OptionParser.new do |opts|
|
16
15
|
opts.banner = 'foodcritic [cookbook_path]'
|
17
16
|
opts.on("-r", "--[no-]repl", "Drop into a REPL for interactive rule editing.") {|r|options[:repl] = r}
|
18
17
|
opts.on("-t", "--tags TAGS", "Only check against rules with the specified tags.") {|t|options[:tags] << t}
|
19
18
|
opts.on("-f", "--epic-fail TAGS", "Fail the build if any of the specified tags are matched.") {|t|options[:fail_tags] << t}
|
20
19
|
opts.on("-C", "--[no-]context", "Show lines matched against rather than the default summary.") {|c|options[:context] = c}
|
20
|
+
opts.on("-I", "--include PATH", "Additional rule file path(s) to load.") {|i|options[:include_rules] << i}
|
21
21
|
opts.on("-S", "--search-grammar PATH", "Specify grammar to use when validating search syntax.") {|s|options[:search_grammar] = s}
|
22
|
+
opts.on("-V", "--version", "Display version."){|v|options[:version] = true}
|
22
23
|
end
|
23
24
|
@parser.parse!(args) unless show_help?
|
24
25
|
end
|
@@ -37,6 +38,20 @@ module FoodCritic
|
|
37
38
|
@parser.help
|
38
39
|
end
|
39
40
|
|
41
|
+
# Show the current version to the end user?
|
42
|
+
#
|
43
|
+
# @return [Boolean] True if the version should be shown.
|
44
|
+
def show_version?
|
45
|
+
@options.key?(:version) and @original_args != ['-v']
|
46
|
+
end
|
47
|
+
|
48
|
+
# The version string.
|
49
|
+
#
|
50
|
+
# @return [String] Current installed version.
|
51
|
+
def version
|
52
|
+
"foodcritic #{FoodCritic::VERSION}"
|
53
|
+
end
|
54
|
+
|
40
55
|
# If the cookbook path provided is valid
|
41
56
|
#
|
42
57
|
# @return [Boolean] True if the path is a directory that exists.
|
@@ -57,8 +72,9 @@ module FoodCritic
|
|
57
72
|
def valid_grammar?
|
58
73
|
return true unless options.key?(:search_grammar)
|
59
74
|
return false unless File.exists?(options[:search_grammar])
|
60
|
-
|
61
|
-
|
75
|
+
search = FoodCritic::Chef::Search.new
|
76
|
+
search.create_parser([options[:search_grammar]])
|
77
|
+
search.parser?
|
62
78
|
end
|
63
79
|
|
64
80
|
# If matches should be shown with context rather than the default summary display.
|
data/lib/foodcritic/domain.rb
CHANGED
@@ -51,7 +51,8 @@ module FoodCritic
|
|
51
51
|
|
52
52
|
# A rule to be matched against.
|
53
53
|
class Rule
|
54
|
-
attr_accessor :code, :name, :cookbook, :recipe, :provider, :resource
|
54
|
+
attr_accessor :code, :name, :cookbook, :recipe, :provider, :resource
|
55
|
+
attr_writer :tags
|
55
56
|
|
56
57
|
# Create a new rule
|
57
58
|
#
|
@@ -62,6 +63,10 @@ module FoodCritic
|
|
62
63
|
@tags = [code]
|
63
64
|
end
|
64
65
|
|
66
|
+
def tags
|
67
|
+
['any'] + @tags
|
68
|
+
end
|
69
|
+
|
65
70
|
# Returns a string representation of this rule.
|
66
71
|
#
|
67
72
|
# @return [String] Rule as a string.
|
@@ -70,4 +75,4 @@ module FoodCritic
|
|
70
75
|
end
|
71
76
|
end
|
72
77
|
|
73
|
-
end
|
78
|
+
end
|
data/lib/foodcritic/dsl.rb
CHANGED
@@ -7,12 +7,13 @@ module FoodCritic
|
|
7
7
|
class RuleDsl
|
8
8
|
attr_reader :rules
|
9
9
|
|
10
|
-
include
|
10
|
+
include Api
|
11
11
|
|
12
12
|
# Define a new rule
|
13
13
|
#
|
14
14
|
# @param [String] code The short unique identifier for this rule, e.g. 'FC001'
|
15
|
-
# @param [String] name The short descriptive name of this rule presented to
|
15
|
+
# @param [String] name The short descriptive name of this rule presented to
|
16
|
+
# the end user.
|
16
17
|
# @param [Block] block The rule definition
|
17
18
|
def rule(code, name, &block)
|
18
19
|
@rules = [] if @rules.nil?
|
@@ -57,14 +58,19 @@ module FoodCritic
|
|
57
58
|
|
58
59
|
# Load the ruleset
|
59
60
|
#
|
60
|
-
# @param [
|
61
|
-
# @return [Array] The loaded rules, ready to be matched against provided
|
62
|
-
|
61
|
+
# @param [Array] paths The paths to the rulesets to load
|
62
|
+
# @return [Array] The loaded rules, ready to be matched against provided
|
63
|
+
# cookbooks.
|
64
|
+
def self.load(paths, with_repl)
|
63
65
|
dsl = RuleDsl.new
|
64
|
-
|
66
|
+
paths.map do |path|
|
67
|
+
File.directory?(path) ? Dir["#{path}/**/*.rb"].sort : path
|
68
|
+
end.flatten.each do |path|
|
69
|
+
dsl.instance_eval(File.read(path), path)
|
70
|
+
end
|
65
71
|
dsl.instance_eval { binding.pry } if with_repl
|
66
72
|
dsl.rules
|
67
73
|
end
|
68
74
|
end
|
69
75
|
|
70
|
-
end
|
76
|
+
end
|
data/lib/foodcritic/linter.rb
CHANGED
@@ -8,18 +8,22 @@ module FoodCritic
|
|
8
8
|
# The main entry point for linting your Chef cookbooks.
|
9
9
|
class Linter
|
10
10
|
|
11
|
-
include FoodCritic::
|
11
|
+
include FoodCritic::Api
|
12
12
|
|
13
|
-
# Perform option parsing from the provided arguments and do a lint check
|
13
|
+
# Perform option parsing from the provided arguments and do a lint check
|
14
|
+
# based on those arguments.
|
14
15
|
#
|
15
16
|
# @param [Array] args The command-line arguments to parse
|
16
|
-
# @return [Array] Pair - the first item is string output, the second is the
|
17
|
+
# @return [Array] Pair - the first item is string output, the second is the
|
18
|
+
# exit code.
|
17
19
|
def self.check(cmd_line)
|
18
20
|
return [cmd_line.help, 0] if cmd_line.show_help?
|
21
|
+
return [cmd_line.version, 0] if cmd_line.show_version?
|
19
22
|
if ! cmd_line.valid_grammar?
|
20
23
|
[cmd_line.help, 4]
|
21
24
|
elsif cmd_line.valid_path?
|
22
|
-
review = FoodCritic::Linter.new.check(cmd_line.cookbook_path,
|
25
|
+
review = FoodCritic::Linter.new.check(cmd_line.cookbook_path,
|
26
|
+
cmd_line.options)
|
23
27
|
[review, review.failed? ? 3 : 0]
|
24
28
|
else
|
25
29
|
[cmd_line.help, 2]
|
@@ -31,22 +35,27 @@ module FoodCritic
|
|
31
35
|
|
32
36
|
end
|
33
37
|
|
34
|
-
# Review the cookbooks at the provided path, identifying potential
|
38
|
+
# Review the cookbooks at the provided path, identifying potential
|
39
|
+
# improvements.
|
35
40
|
#
|
36
|
-
# @param [String] cookbook_path The file path to an individual cookbook
|
41
|
+
# @param [String] cookbook_path The file path to an individual cookbook
|
42
|
+
# directory
|
37
43
|
# @param [Hash] options Options to apply to the linting
|
38
44
|
# @option options [Array] tags The tags to filter rules based on
|
39
45
|
# @option options [Array] fail_tags The tags to fail the build on
|
40
|
-
# @return [FoodCritic::Review] A review of your cookbooks, with any
|
46
|
+
# @return [FoodCritic::Review] A review of your cookbooks, with any
|
47
|
+
# warnings issued.
|
41
48
|
def check(cookbook_path, options)
|
49
|
+
raise ArgumentError, "Cookbook path is required" if cookbook_path.nil?
|
42
50
|
@last_cookbook_path, @last_options = cookbook_path, options
|
43
51
|
load_rules unless defined? @rules
|
44
52
|
warnings = []; last_dir = nil; matched_rule_tags = Set.new
|
45
53
|
|
46
|
-
active_rules = @rules.select{|rule| matching_tags?(options[:tags],
|
54
|
+
active_rules = @rules.select{|rule| matching_tags?(options[:tags],
|
55
|
+
rule.tags)}
|
47
56
|
files_to_process(cookbook_path).each do |file|
|
48
57
|
cookbook_dir = Pathname.new(File.join(File.dirname(file), '..')).cleanpath
|
49
|
-
ast =
|
58
|
+
ast = read_ast(file)
|
50
59
|
active_rules.each do |rule|
|
51
60
|
rule_matches = matches(rule.recipe, ast, file)
|
52
61
|
rule_matches += matches(rule.provider, ast, file) if File.basename(File.dirname(file)) == 'providers'
|
@@ -54,13 +63,14 @@ module FoodCritic
|
|
54
63
|
rule_matches += matches(rule.cookbook, cookbook_dir) if last_dir != cookbook_dir
|
55
64
|
rule_matches.each do |match|
|
56
65
|
warnings << Warning.new(rule, {:filename => file}.merge(match))
|
57
|
-
matched_rule_tags
|
66
|
+
matched_rule_tags << rule.tags
|
58
67
|
end
|
59
68
|
end
|
60
69
|
last_dir = cookbook_dir
|
61
70
|
end
|
62
71
|
|
63
|
-
@review = Review.new(cookbook_path, warnings,
|
72
|
+
@review = Review.new(cookbook_path, warnings,
|
73
|
+
should_fail_build?(options[:fail_tags], matched_rule_tags))
|
64
74
|
|
65
75
|
binding.pry if options[:repl]
|
66
76
|
@review
|
@@ -73,12 +83,14 @@ module FoodCritic
|
|
73
83
|
|
74
84
|
# Load the rules from the (fairly unnecessary) DSL.
|
75
85
|
def load_rules
|
76
|
-
@rules = RuleDsl.load(File.join(File.dirname(__FILE__), 'rules.rb')
|
86
|
+
@rules = RuleDsl.load([File.join(File.dirname(__FILE__), 'rules.rb')] +
|
87
|
+
@last_options[:include_rules], @last_options[:repl])
|
77
88
|
end
|
78
89
|
|
79
90
|
alias_method :reset_rules, :load_rules
|
80
91
|
|
81
|
-
# Convenience method to retrieve the last review. Intended to be used from
|
92
|
+
# Convenience method to retrieve the last review. Intended to be used from
|
93
|
+
# the REPL.
|
82
94
|
#
|
83
95
|
# @return [Review] The last review performed.
|
84
96
|
def review
|
@@ -98,29 +110,27 @@ module FoodCritic
|
|
98
110
|
matches.respond_to?(:each) ? matches : []
|
99
111
|
end
|
100
112
|
|
101
|
-
# Return the files within a cookbook tree that we are interested in trying
|
113
|
+
# Return the files within a cookbook tree that we are interested in trying
|
114
|
+
# to match rules against.
|
102
115
|
#
|
103
116
|
# @param [String] dir The cookbook directory
|
104
|
-
# @return [Array] The files underneath the provided directory to be
|
117
|
+
# @return [Array] The files underneath the provided directory to be
|
118
|
+
# processed.
|
105
119
|
def files_to_process(dir)
|
106
120
|
return [dir] unless File.directory? dir
|
107
121
|
Dir.glob(File.join(dir, '{attributes,providers,recipes,resources}/*.rb')) +
|
108
|
-
|
122
|
+
Dir.glob(File.join(dir, '*/{attributes,providers,recipes,resources}/*.rb'))
|
109
123
|
end
|
110
124
|
|
111
125
|
# Whether to fail the build.
|
112
126
|
#
|
113
|
-
# @param [Array] fail_tags The tags that should cause the build to fail, or
|
127
|
+
# @param [Array] fail_tags The tags that should cause the build to fail, or
|
128
|
+
# special value 'any' for any tag.
|
114
129
|
# @param [Set] matched_tags The tags of warnings we have matches for
|
115
130
|
# @return [Boolean] True if the build should be failed
|
116
131
|
def should_fail_build?(fail_tags, matched_tags)
|
117
|
-
if fail_tags.empty?
|
118
|
-
|
119
|
-
elsif fail_tags.include? 'any'
|
120
|
-
true
|
121
|
-
else
|
122
|
-
matching_tags?(fail_tags, matched_tags)
|
123
|
-
end
|
132
|
+
return false if fail_tags.empty?
|
133
|
+
matched_tags.any?{|tags| matching_tags?(fail_tags, tags)}
|
124
134
|
end
|
125
135
|
|
126
136
|
# Evaluate the specified tags
|
@@ -129,7 +139,9 @@ module FoodCritic
|
|
129
139
|
# @param [Array] tags Tags to match against
|
130
140
|
# @return [Boolean] True if the tags match
|
131
141
|
def matching_tags?(tag_expr, tags)
|
132
|
-
Gherkin::TagExpression.new(tag_expr).eval(tags.map
|
142
|
+
Gherkin::TagExpression.new(tag_expr).eval(tags.map do |t|
|
143
|
+
Gherkin::Formatter::Model::Tag.new(t, 1)
|
144
|
+
end)
|
133
145
|
end
|
134
146
|
|
135
147
|
end
|
data/lib/foodcritic/rules.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
rule "FC001", "Use strings in preference to symbols to access node attributes" do
|
2
2
|
tags %w{style attributes}
|
3
3
|
recipe do |ast|
|
4
|
-
attribute_access(ast, :symbol
|
4
|
+
attribute_access(ast, :type => :symbol).map{|ar| match(ar)}
|
5
5
|
end
|
6
6
|
end
|
7
7
|
|
@@ -23,8 +23,8 @@ end
|
|
23
23
|
rule "FC004", "Use a service resource to start and stop services" do
|
24
24
|
tags %w{style services}
|
25
25
|
recipe do |ast|
|
26
|
-
find_resources(ast, 'execute').find_all do |cmd|
|
27
|
-
cmd_str = (resource_attribute('command'
|
26
|
+
find_resources(ast, :type => 'execute').find_all do |cmd|
|
27
|
+
cmd_str = (resource_attribute(cmd, 'command') || resource_name(cmd)).to_s
|
28
28
|
cmd_str.include?('/etc/init.d') || cmd_str.start_with?('service ') || cmd_str.start_with?('/sbin/service ') ||
|
29
29
|
cmd_str.start_with?('start ') || cmd_str.start_with?('stop ') || cmd_str.start_with?('invoke-rc.d ')
|
30
30
|
end.map{|cmd| match(cmd)}
|
@@ -58,11 +58,16 @@ rule "FC007", "Ensure recipe dependencies are reflected in cookbook metadata" do
|
|
58
58
|
recipe do |ast,filename|
|
59
59
|
metadata_path = Pathname.new(File.join(File.dirname(filename), '..', 'metadata.rb')).cleanpath
|
60
60
|
next unless File.exists? metadata_path
|
61
|
-
undeclared = included_recipes(ast).keys.map
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
undeclared = included_recipes(ast).keys.map do |recipe|
|
62
|
+
recipe.split('::').first
|
63
|
+
end - [cookbook_name(filename)] -
|
64
|
+
declared_dependencies(read_ast(metadata_path))
|
65
|
+
included_recipes(ast).map do |recipe, include_stmts|
|
66
|
+
if undeclared.include?(recipe) ||
|
67
|
+
undeclared.any?{|u| recipe.start_with?("#{u}::")}
|
68
|
+
include_stmts.map{|i| match(i)}
|
69
|
+
end
|
70
|
+
end.flatten.compact
|
66
71
|
end
|
67
72
|
end
|
68
73
|
|
@@ -71,7 +76,7 @@ rule "FC008", "Generated cookbook metadata needs updating" do
|
|
71
76
|
cookbook do |filename|
|
72
77
|
metadata_path = Pathname.new(File.join(filename, 'metadata.rb')).cleanpath
|
73
78
|
next unless File.exists? metadata_path
|
74
|
-
md =
|
79
|
+
md = read_ast(metadata_path)
|
75
80
|
{'maintainer' => 'YOUR_COMPANY_NAME', 'maintainer_email' => 'YOUR_EMAIL'}.map do |field,value|
|
76
81
|
md.xpath(%Q{//command[ident/@value='#{field}']/descendant::tstring_content[@value='#{value}']}).map do |m|
|
77
82
|
match(m).merge(:filename => metadata_path)
|
@@ -86,8 +91,12 @@ rule "FC009", "Resource attribute not recognised" do
|
|
86
91
|
matches = []
|
87
92
|
resource_attributes_by_type(ast).each do |type,resources|
|
88
93
|
resources.each do |resource|
|
89
|
-
resource.keys.map{|att|att.to_sym}.reject
|
90
|
-
|
94
|
+
resource.keys.map{|att|att.to_sym}.reject do |att|
|
95
|
+
resource_attribute?(type.to_sym, att)
|
96
|
+
end.each do |invalid_att|
|
97
|
+
matches << match(find_resources(ast, :type => type).find do |res|
|
98
|
+
resource_attributes(res).include?(invalid_att.to_s)
|
99
|
+
end)
|
91
100
|
end
|
92
101
|
end
|
93
102
|
end
|
@@ -120,8 +129,8 @@ end
|
|
120
129
|
rule "FC013", "Use file_cache_path rather than hard-coding tmp paths" do
|
121
130
|
tags %w{style files}
|
122
131
|
recipe do |ast|
|
123
|
-
find_resources(ast, 'remote_file').find_all do |download|
|
124
|
-
path = (resource_attribute('path'
|
132
|
+
find_resources(ast, :type => 'remote_file').find_all do |download|
|
133
|
+
path = (resource_attribute(download, 'path') || resource_name(download)).to_s
|
125
134
|
path.start_with?('/tmp/')
|
126
135
|
end.map{|download| match(download)}
|
127
136
|
end
|
@@ -130,7 +139,7 @@ end
|
|
130
139
|
rule "FC014", "Consider extracting long ruby_block to library" do
|
131
140
|
tags %w{style libraries}
|
132
141
|
recipe do |ast|
|
133
|
-
find_resources(ast, 'ruby_block').find_all do |rb|
|
142
|
+
find_resources(ast, :type => 'ruby_block').find_all do |rb|
|
134
143
|
! rb.xpath("//fcall[ident/@value='block' and count(ancestor::*) = 8]/../../do_block[count(descendant::*) > 100]").empty?
|
135
144
|
end.map{|block| match(block)}
|
136
145
|
end
|
@@ -172,9 +181,9 @@ end
|
|
172
181
|
rule "FC019", "Access node attributes in a consistent manner" do
|
173
182
|
tags %w{style attributes}
|
174
183
|
cookbook do |cookbook_dir|
|
175
|
-
asts = {}; files = Dir["#{cookbook_dir}/*/*.rb"].map{|file| {:path => file, :ast =>
|
184
|
+
asts = {}; files = Dir["#{cookbook_dir}/*/*.rb"].map{|file| {:path => file, :ast => read_ast(file)}}
|
176
185
|
types = [:string, :symbol, :vivified].map{|type| {:access_type => type, :count => files.map do |file|
|
177
|
-
attribute_access(file[:ast], type, true).tap{|ast|
|
186
|
+
attribute_access(file[:ast], :type => type, :ignore_calls => true).tap{|ast|
|
178
187
|
asts[type] = {:ast => ast, :path => file[:path]} if (! ast.empty?) and (! asts.has_key?(type))
|
179
188
|
}.size
|
180
189
|
end.inject(:+)}}.reject{|type| type[:count] == 0}
|
data/lib/foodcritic/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foodcritic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,25 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-03-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: json
|
16
|
-
requirement: &2152841480 !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ! '>='
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: 1.4.4
|
22
|
-
- - <=
|
23
|
-
- !ruby/object:Gem::Version
|
24
|
-
version: 1.6.1
|
25
|
-
type: :runtime
|
26
|
-
prerelease: false
|
27
|
-
version_requirements: *2152841480
|
28
14
|
- !ruby/object:Gem::Dependency
|
29
15
|
name: gherkin
|
30
|
-
requirement: &
|
16
|
+
requirement: &2157955580 !ruby/object:Gem::Requirement
|
31
17
|
none: false
|
32
18
|
requirements:
|
33
19
|
- - ~>
|
@@ -35,10 +21,10 @@ dependencies:
|
|
35
21
|
version: 2.8.0
|
36
22
|
type: :runtime
|
37
23
|
prerelease: false
|
38
|
-
version_requirements: *
|
24
|
+
version_requirements: *2157955580
|
39
25
|
- !ruby/object:Gem::Dependency
|
40
26
|
name: gist
|
41
|
-
requirement: &
|
27
|
+
requirement: &2157954980 !ruby/object:Gem::Requirement
|
42
28
|
none: false
|
43
29
|
requirements:
|
44
30
|
- - ~>
|
@@ -46,10 +32,10 @@ dependencies:
|
|
46
32
|
version: 2.0.4
|
47
33
|
type: :runtime
|
48
34
|
prerelease: false
|
49
|
-
version_requirements: *
|
35
|
+
version_requirements: *2157954980
|
50
36
|
- !ruby/object:Gem::Dependency
|
51
37
|
name: nokogiri
|
52
|
-
requirement: &
|
38
|
+
requirement: &2157954500 !ruby/object:Gem::Requirement
|
53
39
|
none: false
|
54
40
|
requirements:
|
55
41
|
- - ~>
|
@@ -57,10 +43,10 @@ dependencies:
|
|
57
43
|
version: 1.5.0
|
58
44
|
type: :runtime
|
59
45
|
prerelease: false
|
60
|
-
version_requirements: *
|
46
|
+
version_requirements: *2157954500
|
61
47
|
- !ruby/object:Gem::Dependency
|
62
48
|
name: pry
|
63
|
-
requirement: &
|
49
|
+
requirement: &2157953920 !ruby/object:Gem::Requirement
|
64
50
|
none: false
|
65
51
|
requirements:
|
66
52
|
- - ~>
|
@@ -68,10 +54,10 @@ dependencies:
|
|
68
54
|
version: 0.9.7.4
|
69
55
|
type: :runtime
|
70
56
|
prerelease: false
|
71
|
-
version_requirements: *
|
57
|
+
version_requirements: *2157953920
|
72
58
|
- !ruby/object:Gem::Dependency
|
73
59
|
name: pry-doc
|
74
|
-
requirement: &
|
60
|
+
requirement: &2157953460 !ruby/object:Gem::Requirement
|
75
61
|
none: false
|
76
62
|
requirements:
|
77
63
|
- - ~>
|
@@ -79,10 +65,10 @@ dependencies:
|
|
79
65
|
version: 0.3.0
|
80
66
|
type: :runtime
|
81
67
|
prerelease: false
|
82
|
-
version_requirements: *
|
68
|
+
version_requirements: *2157953460
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: rak
|
85
|
-
requirement: &
|
71
|
+
requirement: &2157952960 !ruby/object:Gem::Requirement
|
86
72
|
none: false
|
87
73
|
requirements:
|
88
74
|
- - ~>
|
@@ -90,10 +76,10 @@ dependencies:
|
|
90
76
|
version: '1.4'
|
91
77
|
type: :runtime
|
92
78
|
prerelease: false
|
93
|
-
version_requirements: *
|
79
|
+
version_requirements: *2157952960
|
94
80
|
- !ruby/object:Gem::Dependency
|
95
81
|
name: treetop
|
96
|
-
requirement: &
|
82
|
+
requirement: &2157952180 !ruby/object:Gem::Requirement
|
97
83
|
none: false
|
98
84
|
requirements:
|
99
85
|
- - ~>
|
@@ -101,10 +87,10 @@ dependencies:
|
|
101
87
|
version: 1.4.10
|
102
88
|
type: :runtime
|
103
89
|
prerelease: false
|
104
|
-
version_requirements: *
|
90
|
+
version_requirements: *2157952180
|
105
91
|
- !ruby/object:Gem::Dependency
|
106
92
|
name: yajl-ruby
|
107
|
-
requirement: &
|
93
|
+
requirement: &2157951540 !ruby/object:Gem::Requirement
|
108
94
|
none: false
|
109
95
|
requirements:
|
110
96
|
- - ~>
|
@@ -112,7 +98,7 @@ dependencies:
|
|
112
98
|
version: 1.1.0
|
113
99
|
type: :runtime
|
114
100
|
prerelease: false
|
115
|
-
version_requirements: *
|
101
|
+
version_requirements: *2157951540
|
116
102
|
description: Lint tool for Opscode Chef cookbooks.
|
117
103
|
email:
|
118
104
|
executables:
|
@@ -120,12 +106,12 @@ executables:
|
|
120
106
|
extensions: []
|
121
107
|
extra_rdoc_files: []
|
122
108
|
files:
|
109
|
+
- lib/foodcritic/api.rb
|
123
110
|
- lib/foodcritic/chef.rb
|
124
111
|
- lib/foodcritic/command_line.rb
|
125
112
|
- lib/foodcritic/domain.rb
|
126
113
|
- lib/foodcritic/dsl.rb
|
127
114
|
- lib/foodcritic/error_checker.rb
|
128
|
-
- lib/foodcritic/helpers.rb
|
129
115
|
- lib/foodcritic/linter.rb
|
130
116
|
- lib/foodcritic/output.rb
|
131
117
|
- lib/foodcritic/rules.rb
|
@@ -154,12 +140,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
154
140
|
version: '0'
|
155
141
|
segments:
|
156
142
|
- 0
|
157
|
-
hash:
|
143
|
+
hash: 4610293132888604111
|
158
144
|
requirements: []
|
159
145
|
rubyforge_project:
|
160
146
|
rubygems_version: 1.8.10
|
161
147
|
signing_key:
|
162
148
|
specification_version: 3
|
163
|
-
summary: foodcritic-0.
|
149
|
+
summary: foodcritic-1.0.0
|
164
150
|
test_files: []
|
165
151
|
has_rdoc:
|