foodcritic 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/foodcritic/api.rb +38 -21
- data/lib/foodcritic/chef.rb +8 -2
- data/lib/foodcritic/command_line.rb +33 -11
- data/lib/foodcritic/domain.rb +18 -6
- data/lib/foodcritic/dsl.rb +2 -1
- data/lib/foodcritic/error_checker.rb +2 -1
- data/lib/foodcritic/linter.rb +26 -8
- data/lib/foodcritic/output.rb +18 -10
- data/lib/foodcritic/rules.rb +137 -59
- data/lib/foodcritic/version.rb +1 -1
- metadata +76 -23
data/lib/foodcritic/api.rb
CHANGED
@@ -22,24 +22,11 @@ module FoodCritic
|
|
22
22
|
raise ArgumentError, "Node type not recognised"
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
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
|
25
|
+
if options[:type] == :vivified
|
26
|
+
vivified_attribute_access(ast)
|
34
27
|
else
|
35
|
-
|
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)
|
28
|
+
standard_attribute_access(ast, options)
|
41
29
|
end
|
42
|
-
).sort
|
43
30
|
end
|
44
31
|
|
45
32
|
# Does the specified recipe check for Chef Solo?
|
@@ -61,7 +48,8 @@ module FoodCritic
|
|
61
48
|
def chef_solo_search_supported?(recipe_path)
|
62
49
|
return false if recipe_path.nil? || ! File.exists?(recipe_path)
|
63
50
|
cbk_tree_path = Pathname.new(File.join(recipe_path, '../../..'))
|
64
|
-
search_libs = Dir[File.join(cbk_tree_path.realpath,
|
51
|
+
search_libs = Dir[File.join(cbk_tree_path.realpath,
|
52
|
+
'*/libraries/search.rb')]
|
65
53
|
search_libs.any? do |lib|
|
66
54
|
! read_ast(lib).xpath(%q{//class[count(descendant::const[@value='Chef']
|
67
55
|
) = 1]/descendant::def/ident[@value='search']}).empty?
|
@@ -78,6 +66,12 @@ module FoodCritic
|
|
78
66
|
file = File.absolute_path(File.dirname(file.to_s))
|
79
67
|
end
|
80
68
|
file = File.dirname(file) unless File.extname(file).empty?
|
69
|
+
md_path = File.join(file, 'metadata.rb')
|
70
|
+
if File.exists?(md_path)
|
71
|
+
name = read_ast(md_path).xpath("//stmts_add/
|
72
|
+
command[ident/@value='name']/descendant::tstring_content/@value").to_s
|
73
|
+
return name unless name.empty?
|
74
|
+
end
|
81
75
|
File.basename(file)
|
82
76
|
end
|
83
77
|
|
@@ -88,7 +82,7 @@ module FoodCritic
|
|
88
82
|
def declared_dependencies(ast)
|
89
83
|
raise_unless_xpath!(ast)
|
90
84
|
deps = ast.xpath(%q{//command[ident/@value='depends']/
|
91
|
-
descendant::args_add/descendant::tstring_content})
|
85
|
+
descendant::args_add/descendant::tstring_content[1]})
|
92
86
|
# handle quoted word arrays
|
93
87
|
var_ref = ast.xpath(%q{//command[ident/@value='depends']/
|
94
88
|
descendant::var_ref/ident})
|
@@ -138,6 +132,7 @@ module FoodCritic
|
|
138
132
|
included.inject(Hash.new([])){|h, i| h[i['value']] += [i]; h}
|
139
133
|
end
|
140
134
|
|
135
|
+
# XPath custom function
|
141
136
|
class AttFilter
|
142
137
|
def is_att_type(value)
|
143
138
|
return [] unless value.respond_to?(:select)
|
@@ -159,7 +154,8 @@ module FoodCritic
|
|
159
154
|
# Create a match from the specified node.
|
160
155
|
#
|
161
156
|
# @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
|
157
|
+
# @return [Hash] Hash with the matched node name and position with the
|
158
|
+
# recipe
|
163
159
|
def match(node)
|
164
160
|
raise_unless_xpath!(node)
|
165
161
|
pos = node.xpath('descendant::pos').first
|
@@ -175,7 +171,7 @@ module FoodCritic
|
|
175
171
|
# @return [Boolean] True if this string might be an OS command
|
176
172
|
def os_command?(str)
|
177
173
|
str.start_with?('grep ', 'which ') or # common commands
|
178
|
-
str.include?('|') or #
|
174
|
+
str.include?('|') or # a pipe, could be alternation
|
179
175
|
str.match(/^[\w]+$/) or # command name only
|
180
176
|
str.match(/ --?[a-z]/i) # command-line flag
|
181
177
|
end
|
@@ -212,7 +208,8 @@ module FoodCritic
|
|
212
208
|
if att.xpath('descendant::symbol').empty?
|
213
209
|
att_value = att.xpath('string(descendant::tstring_content/@value)')
|
214
210
|
else
|
215
|
-
att_value =
|
211
|
+
att_value =
|
212
|
+
att.xpath('string(descendant::symbol/ident/@value)').to_sym
|
216
213
|
end
|
217
214
|
atts[att.xpath('string(ident/@value)')] = att_value
|
218
215
|
end
|
@@ -346,6 +343,26 @@ module FoodCritic
|
|
346
343
|
end
|
347
344
|
end
|
348
345
|
|
346
|
+
def standard_attribute_access(ast, options)
|
347
|
+
type = options[:type] == :string ? 'tstring_content' : options[:type]
|
348
|
+
expr = '//*[self::aref_field or self::aref]'
|
349
|
+
expr += '[is_att_type(descendant::ident'
|
350
|
+
expr += '[not(ancestor::aref/call)]' if options[:ignore_calls]
|
351
|
+
expr += "/@value)]/descendant::#{type}"
|
352
|
+
ast.xpath(expr, AttFilter.new).sort
|
353
|
+
end
|
354
|
+
|
355
|
+
def vivified_attribute_access(ast)
|
356
|
+
calls = ast.xpath(%q{//*[self::call or self::field]
|
357
|
+
[is_att_type(vcall/ident/@value) or
|
358
|
+
is_att_type(var_ref/ident/@value)][@value='.']}, AttFilter.new)
|
359
|
+
calls.select do |call|
|
360
|
+
call.xpath("aref/args_add_block").size == 0 and
|
361
|
+
(call.xpath("descendant::ident").size > 1 and
|
362
|
+
! chef_dsl_methods.include?(call.xpath("ident/@value").to_s.to_sym))
|
363
|
+
end.sort
|
364
|
+
end
|
365
|
+
|
349
366
|
end
|
350
367
|
|
351
368
|
end
|
data/lib/foodcritic/chef.rb
CHANGED
@@ -8,7 +8,7 @@ module FoodCritic
|
|
8
8
|
# @return [Array] Array of method symbols
|
9
9
|
def chef_dsl_methods
|
10
10
|
load_metadata
|
11
|
-
@dsl_metadata[:dsl_methods].map
|
11
|
+
@dsl_metadata[:dsl_methods].map(&:to_sym)
|
12
12
|
end
|
13
13
|
|
14
14
|
# Is the specified attribute valid for the type of resource? Note that this
|
@@ -16,6 +16,7 @@ module FoodCritic
|
|
16
16
|
#
|
17
17
|
# @param [Symbol] resource_type The type of Chef resource
|
18
18
|
# @param [Symbol] attribute_name The attribute name
|
19
|
+
# @return [Boolean] False if the attribute is known not to be valid
|
19
20
|
def resource_attribute?(resource_type, attribute_name)
|
20
21
|
if resource_type.to_s.empty? || attribute_name.to_s.empty?
|
21
22
|
raise ArgumentError, "Arguments cannot be nil or empty."
|
@@ -46,6 +47,7 @@ module FoodCritic
|
|
46
47
|
:symbolize_keys => true)
|
47
48
|
end
|
48
49
|
|
50
|
+
# Chef Search
|
49
51
|
class Search
|
50
52
|
|
51
53
|
# The search grammars that ship with any Chef gems installed locally.
|
@@ -67,7 +69,8 @@ module FoodCritic
|
|
67
69
|
begin
|
68
70
|
break parser unless parser.nil?
|
69
71
|
# don't instantiate custom nodes
|
70
|
-
Treetop.load_from_string(
|
72
|
+
Treetop.load_from_string(
|
73
|
+
IO.read(lucene_grammar).gsub(/<[^>]+>/, ''))
|
71
74
|
LuceneParser.new
|
72
75
|
rescue
|
73
76
|
# silently swallow and try the next grammar
|
@@ -82,6 +85,9 @@ module FoodCritic
|
|
82
85
|
! @search_parser.nil?
|
83
86
|
end
|
84
87
|
|
88
|
+
# The search parser
|
89
|
+
#
|
90
|
+
# @return [LuceneParser] The search parser
|
85
91
|
def parser
|
86
92
|
@search_parser
|
87
93
|
end
|
@@ -9,17 +9,37 @@ module FoodCritic
|
|
9
9
|
def initialize(args)
|
10
10
|
@args = args
|
11
11
|
@original_args = args.dup
|
12
|
-
@options = {}
|
13
|
-
@options[:fail_tags] = []; @options[:tags] = []; @options[:include_rules] = []
|
12
|
+
@options = {:fail_tags => [], :tags => [], :include_rules => []}
|
14
13
|
@parser = OptionParser.new do |opts|
|
15
14
|
opts.banner = 'foodcritic [cookbook_path]'
|
16
|
-
opts.on("-r", "--[no-]repl",
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
opts.on("-
|
21
|
-
|
22
|
-
|
15
|
+
opts.on("-r", "--[no-]repl",
|
16
|
+
"Drop into a REPL for interactive rule editing.") do |r|
|
17
|
+
options[:repl] = r
|
18
|
+
end
|
19
|
+
opts.on("-t", "--tags TAGS",
|
20
|
+
"Only check against rules with the specified tags.") do |t|
|
21
|
+
options[:tags] << t
|
22
|
+
end
|
23
|
+
opts.on("-f", "--epic-fail TAGS",
|
24
|
+
"Fail the build if any of the specified tags are matched.") do |t|
|
25
|
+
options[:fail_tags] << t
|
26
|
+
end
|
27
|
+
opts.on("-C", "--[no-]context",
|
28
|
+
"Show lines matched against rather than the default summary.") do |c|
|
29
|
+
options[:context] = c
|
30
|
+
end
|
31
|
+
opts.on("-I", "--include PATH",
|
32
|
+
"Additional rule file path(s) to load.") do |i|
|
33
|
+
options[:include_rules] << i
|
34
|
+
end
|
35
|
+
opts.on("-S", "--search-grammar PATH",
|
36
|
+
"Specify grammar to use when validating search syntax.") do |s|
|
37
|
+
options[:search_grammar] = s
|
38
|
+
end
|
39
|
+
opts.on("-V", "--version",
|
40
|
+
"Display version.") do |v|
|
41
|
+
options[:version] = true
|
42
|
+
end
|
23
43
|
end
|
24
44
|
@parser.parse!(args) unless show_help?
|
25
45
|
end
|
@@ -68,7 +88,8 @@ module FoodCritic
|
|
68
88
|
|
69
89
|
# Is the search grammar specified valid?
|
70
90
|
#
|
71
|
-
# @return [Boolean] True if the grammar has not been provided or can be
|
91
|
+
# @return [Boolean] True if the grammar has not been provided or can be
|
92
|
+
# loaded.
|
72
93
|
def valid_grammar?
|
73
94
|
return true unless options.key?(:search_grammar)
|
74
95
|
return false unless File.exists?(options[:search_grammar])
|
@@ -77,7 +98,8 @@ module FoodCritic
|
|
77
98
|
search.parser?
|
78
99
|
end
|
79
100
|
|
80
|
-
# If matches should be shown with context rather than the default summary
|
101
|
+
# If matches should be shown with context rather than the default summary
|
102
|
+
# display.
|
81
103
|
#
|
82
104
|
# @return [Boolean] True if matches should be shown with context.
|
83
105
|
def show_context?
|
data/lib/foodcritic/domain.rb
CHANGED
@@ -8,7 +8,8 @@ module FoodCritic
|
|
8
8
|
#
|
9
9
|
# @param [FoodCritic::Rule] rule The rule which raised this warning
|
10
10
|
# @param [Hash] match The match data
|
11
|
-
# @option match [String] :filename The filename the warning was raised
|
11
|
+
# @option match [String] :filename The filename the warning was raised
|
12
|
+
# against
|
12
13
|
# @option match [Integer] :line The identified line
|
13
14
|
# @option match [Integer] :column The identified column
|
14
15
|
def initialize(rule, match={})
|
@@ -25,7 +26,8 @@ module FoodCritic
|
|
25
26
|
#
|
26
27
|
# @param [String] cookbook_path The path this review was performed against
|
27
28
|
# @param [Array] warnings The warnings raised in this review
|
28
|
-
# @param [Boolean] is_failed Have warnings been raised that mean this
|
29
|
+
# @param [Boolean] is_failed Have warnings been raised that mean this
|
30
|
+
# should be considered failed?
|
29
31
|
def initialize(cookbook_path, warnings, is_failed)
|
30
32
|
@cookbook_path = cookbook_path
|
31
33
|
@warnings = warnings
|
@@ -41,9 +43,13 @@ module FoodCritic
|
|
41
43
|
|
42
44
|
# Returns a string representation of this review.
|
43
45
|
#
|
44
|
-
# @return [String] Review as a string, this representation is liable to
|
46
|
+
# @return [String] Review as a string, this representation is liable to
|
47
|
+
# change.
|
45
48
|
def to_s
|
46
|
-
@warnings.map
|
49
|
+
@warnings.map do |w|
|
50
|
+
["#{w.rule.code}: #{w.rule.name}: #{w.match[:filename]}",
|
51
|
+
w.match[:line].to_i]
|
52
|
+
end.sort do |x,y|
|
47
53
|
x.first == y.first ? x[1] <=> y[1] : x.first <=> y.first
|
48
54
|
end.map{|w|"#{w.first}:#{w[1]}"}.uniq.join("\n")
|
49
55
|
end
|
@@ -56,13 +62,19 @@ module FoodCritic
|
|
56
62
|
|
57
63
|
# Create a new rule
|
58
64
|
#
|
59
|
-
# @param [String] code The short unique identifier for this rule,
|
60
|
-
#
|
65
|
+
# @param [String] code The short unique identifier for this rule,
|
66
|
+
# e.g. 'FC001'
|
67
|
+
# @param [String] name The short descriptive name of this rule presented to
|
68
|
+
# the end user.
|
61
69
|
def initialize(code, name)
|
62
70
|
@code, @name = code, name
|
63
71
|
@tags = [code]
|
64
72
|
end
|
65
73
|
|
74
|
+
# The tags associated with this rule.
|
75
|
+
# A rule is always tagged with the tags 'any' and the rule code.
|
76
|
+
#
|
77
|
+
# @return [Array] The tags associated.
|
66
78
|
def tags
|
67
79
|
['any'] + @tags
|
68
80
|
end
|
data/lib/foodcritic/dsl.rb
CHANGED
@@ -11,7 +11,8 @@ module FoodCritic
|
|
11
11
|
|
12
12
|
# Define a new rule
|
13
13
|
#
|
14
|
-
# @param [String] code The short unique identifier for this rule,
|
14
|
+
# @param [String] code The short unique identifier for this rule,
|
15
|
+
# e.g. 'FC001'
|
15
16
|
# @param [String] name The short descriptive name of this rule presented to
|
16
17
|
# the end user.
|
17
18
|
# @param [Block] block The rule definition
|
@@ -18,7 +18,8 @@ module FoodCritic
|
|
18
18
|
|
19
19
|
# Register with all available error handlers.
|
20
20
|
def self.register_error_handlers
|
21
|
-
SexpBuilder.public_instance_methods.grep(/^on_.*_error$/)
|
21
|
+
error_methods = SexpBuilder.public_instance_methods.grep(/^on_.*_error$/)
|
22
|
+
error_methods.sort.each do |err_meth|
|
22
23
|
define_method(err_meth) { |*| @found_error = true }
|
23
24
|
end
|
24
25
|
end
|
data/lib/foodcritic/linter.rb
CHANGED
@@ -54,13 +54,20 @@ module FoodCritic
|
|
54
54
|
active_rules = @rules.select{|rule| matching_tags?(options[:tags],
|
55
55
|
rule.tags)}
|
56
56
|
files_to_process(cookbook_path).each do |file|
|
57
|
-
cookbook_dir = Pathname.new(
|
57
|
+
cookbook_dir = Pathname.new(
|
58
|
+
File.join(File.dirname(file), '..')).cleanpath
|
58
59
|
ast = read_ast(file)
|
59
60
|
active_rules.each do |rule|
|
60
61
|
rule_matches = matches(rule.recipe, ast, file)
|
61
|
-
|
62
|
-
|
63
|
-
|
62
|
+
if File.basename(File.dirname(file)) == 'providers'
|
63
|
+
rule_matches += matches(rule.provider, ast, file)
|
64
|
+
end
|
65
|
+
if File.basename(File.dirname(file)) == 'resources'
|
66
|
+
rule_matches += matches(rule.resource, ast, file)
|
67
|
+
end
|
68
|
+
if last_dir != cookbook_dir
|
69
|
+
rule_matches += matches(rule.cookbook, cookbook_dir)
|
70
|
+
end
|
64
71
|
rule_matches.each do |match|
|
65
72
|
warnings << Warning.new(rule, {:filename => file}.merge(match))
|
66
73
|
matched_rule_tags << rule.tags
|
@@ -76,7 +83,8 @@ module FoodCritic
|
|
76
83
|
@review
|
77
84
|
end
|
78
85
|
|
79
|
-
# Convenience method to repeat the last check. Intended to be used from the
|
86
|
+
# Convenience method to repeat the last check. Intended to be used from the
|
87
|
+
# REPL.
|
80
88
|
def recheck
|
81
89
|
check(@last_cookbook_path, @last_options)
|
82
90
|
end
|
@@ -107,7 +115,16 @@ module FoodCritic
|
|
107
115
|
def matches(match_method, *params)
|
108
116
|
return [] unless match_method.respond_to?(:yield)
|
109
117
|
matches = match_method.yield(*params)
|
110
|
-
matches.respond_to?(:each)
|
118
|
+
return [] unless matches.respond_to?(:each)
|
119
|
+
matches.map do |m|
|
120
|
+
if m.respond_to?(:node_name)
|
121
|
+
match(m)
|
122
|
+
elsif m.respond_to?(:xpath)
|
123
|
+
m.to_a.map{|m| match(m)}
|
124
|
+
else
|
125
|
+
m
|
126
|
+
end
|
127
|
+
end.flatten
|
111
128
|
end
|
112
129
|
|
113
130
|
# Return the files within a cookbook tree that we are interested in trying
|
@@ -118,8 +135,9 @@ module FoodCritic
|
|
118
135
|
# processed.
|
119
136
|
def files_to_process(dir)
|
120
137
|
return [dir] unless File.directory? dir
|
121
|
-
|
122
|
-
|
138
|
+
cookbook_glob = '{attributes,providers,recipes,resources}/*.rb'
|
139
|
+
Dir.glob(File.join(dir, cookbook_glob)) +
|
140
|
+
Dir.glob(File.join(dir, "*/#{cookbook_glob}"))
|
123
141
|
end
|
124
142
|
|
125
143
|
# Whether to fail the build.
|
data/lib/foodcritic/output.rb
CHANGED
@@ -2,7 +2,8 @@ module FoodCritic
|
|
2
2
|
|
3
3
|
# Default output showing a summary view.
|
4
4
|
class SummaryOutput
|
5
|
-
# Output a summary view only listing the matching rules, file and line
|
5
|
+
# Output a summary view only listing the matching rules, file and line
|
6
|
+
# number.
|
6
7
|
#
|
7
8
|
# @param [Review] review The review to output.
|
8
9
|
def output(review)
|
@@ -21,10 +22,12 @@ module FoodCritic
|
|
21
22
|
puts review; return
|
22
23
|
end
|
23
24
|
|
24
|
-
# Cheating here and mis-using Rak (Ruby port of Ack) to generate pretty
|
25
|
+
# Cheating here and mis-using Rak (Ruby port of Ack) to generate pretty
|
26
|
+
# colourised context.
|
25
27
|
#
|
26
|
-
# Rak supports evaluating a custom expression as an alternative to a
|
27
|
-
#
|
28
|
+
# Rak supports evaluating a custom expression as an alternative to a
|
29
|
+
# regex. Our expression consults a hash of the matches found and then we
|
30
|
+
# let Rak take care of the presentation.
|
28
31
|
line_lookup = key_by_file_and_line(review)
|
29
32
|
Rak.class_eval do
|
30
33
|
const_set(:RULE_COLOUR, "\033[1;36m")
|
@@ -33,8 +36,9 @@ module FoodCritic
|
|
33
36
|
ARGV.replace(['--context', '--eval', %q{
|
34
37
|
# This code will be evaluated inline by Rak.
|
35
38
|
fn = fn.split("\n").first
|
36
|
-
if @warnings.key?(fn) and @warnings[fn].key?($.) # filename and line
|
37
|
-
rule_name =
|
39
|
+
if @warnings.key?(fn) and @warnings[fn].key?($.) # filename and line no
|
40
|
+
rule_name = opt[:colour] ? RULE_COLOUR : ''
|
41
|
+
rule_name += "#{@warnings[fn][$.].to_a.join("\n")}#{CLEAR_COLOURS}"
|
38
42
|
if ! displayed_filename
|
39
43
|
fn = "#{fn}\n#{rule_name}"
|
40
44
|
else
|
@@ -50,16 +54,20 @@ module FoodCritic
|
|
50
54
|
|
51
55
|
private
|
52
56
|
|
53
|
-
# Build a hash lookup by filename and line number for warnings found in the
|
57
|
+
# Build a hash lookup by filename and line number for warnings found in the
|
58
|
+
# specified review.
|
54
59
|
#
|
55
60
|
# @param [Review] review The review to convert.
|
56
61
|
# @return [Hash] Nested hashes keyed by filename and line number.
|
57
62
|
def key_by_file_and_line(review)
|
58
63
|
warn_hash = {}
|
59
64
|
review.warnings.each do |warning|
|
60
|
-
filename = Pathname.new(warning.match[:filename]).cleanpath.to_s
|
65
|
+
filename = Pathname.new(warning.match[:filename]).cleanpath.to_s
|
66
|
+
line_num = warning.match[:line].to_i
|
61
67
|
warn_hash[filename] = {} unless warn_hash.key?(filename)
|
62
|
-
|
68
|
+
unless warn_hash[filename].key?(line_num)
|
69
|
+
warn_hash[filename][line_num] = Set.new
|
70
|
+
end
|
63
71
|
warn_hash[filename][line_num] << warning.rule
|
64
72
|
end
|
65
73
|
warn_hash
|
@@ -67,4 +75,4 @@ module FoodCritic
|
|
67
75
|
|
68
76
|
end
|
69
77
|
|
70
|
-
end
|
78
|
+
end
|
data/lib/foodcritic/rules.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
rule "FC001",
|
1
|
+
rule "FC001",
|
2
|
+
"Use strings in preference to symbols to access node attributes" do
|
2
3
|
tags %w{style attributes}
|
3
4
|
recipe do |ast|
|
4
|
-
attribute_access(ast, :type => :symbol)
|
5
|
+
attribute_access(ast, :type => :symbol)
|
5
6
|
end
|
6
7
|
end
|
7
8
|
|
@@ -9,14 +10,19 @@ rule "FC002", "Avoid string interpolation where not required" do
|
|
9
10
|
tags %w{style strings}
|
10
11
|
recipe do |ast|
|
11
12
|
ast.xpath(%q{//string_literal[count(descendant::string_embexpr) = 1 and
|
12
|
-
count(string_add/tstring_content|string_add/string_add/tstring_content)
|
13
|
+
count(string_add/tstring_content|string_add/string_add/tstring_content)
|
14
|
+
= 0]})
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
rule "FC003",
|
18
|
+
rule "FC003",
|
19
|
+
"Check whether you are running with chef server before using" +
|
20
|
+
" server-specific features" do
|
17
21
|
tags %w{portability solo}
|
18
22
|
recipe do |ast,filename|
|
19
|
-
|
23
|
+
unless checks_for_chef_solo?(ast) or chef_solo_search_supported?(filename)
|
24
|
+
searches(ast)
|
25
|
+
end
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
@@ -25,38 +31,52 @@ rule "FC004", "Use a service resource to start and stop services" do
|
|
25
31
|
recipe do |ast|
|
26
32
|
find_resources(ast, :type => 'execute').find_all do |cmd|
|
27
33
|
cmd_str = (resource_attribute(cmd, 'command') || resource_name(cmd)).to_s
|
28
|
-
cmd_str.include?('/etc/init.d') || cmd_str.start_with?('service ') ||
|
29
|
-
|
30
|
-
|
34
|
+
cmd_str.include?('/etc/init.d') || cmd_str.start_with?('service ') ||
|
35
|
+
cmd_str.start_with?('/sbin/service ') ||
|
36
|
+
cmd_str.start_with?('start ') || cmd_str.start_with?('stop ') ||
|
37
|
+
cmd_str.start_with?('invoke-rc.d ')
|
38
|
+
end
|
31
39
|
end
|
32
40
|
end
|
33
41
|
|
34
42
|
rule "FC005", "Avoid repetition of resource declarations" do
|
35
43
|
tags %w{style}
|
36
44
|
recipe do |ast|
|
37
|
-
resources = find_resources(ast).map
|
38
|
-
|
45
|
+
resources = find_resources(ast).map do |res|
|
46
|
+
resource_attributes(res).merge({:type => resource_type(res),
|
47
|
+
:ast => res})
|
48
|
+
end.chunk{|res| res[:type]}.reject{|res| res[1].size < 3}
|
39
49
|
resources.map do |cont_res|
|
40
50
|
first_resource = cont_res[1][0][:ast]
|
41
|
-
# we have contiguous resources of the same type, but do they share the
|
42
|
-
|
43
|
-
|
51
|
+
# we have contiguous resources of the same type, but do they share the
|
52
|
+
# same attributes?
|
53
|
+
sorted_atts = cont_res[1].map do |atts|
|
54
|
+
atts.delete_if{|k| k == :ast}.to_a.sort do |x,y|
|
55
|
+
x.first.to_s <=> y.first.to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
first_resource if sorted_atts.all? do |att|
|
59
|
+
(att - sorted_atts.inject{|atts,a| atts & a}).length == 1
|
60
|
+
end
|
44
61
|
end.compact
|
45
62
|
end
|
46
63
|
end
|
47
64
|
|
48
|
-
rule "FC006",
|
65
|
+
rule "FC006",
|
66
|
+
"Mode should be quoted or fully specified when setting file permissions" do
|
49
67
|
tags %w{correctness files}
|
50
68
|
recipe do |ast|
|
51
|
-
ast.xpath(%q{//ident[@value='mode']/parent::command/
|
52
|
-
|
69
|
+
ast.xpath(%q{//ident[@value='mode']/parent::command/
|
70
|
+
descendant::int[string-length(@value) < 5 and not(starts-with(@value, "0")
|
71
|
+
and string-length(@value) = 4)]/ancestor::method_add_block})
|
53
72
|
end
|
54
73
|
end
|
55
74
|
|
56
75
|
rule "FC007", "Ensure recipe dependencies are reflected in cookbook metadata" do
|
57
76
|
tags %w{correctness metadata}
|
58
77
|
recipe do |ast,filename|
|
59
|
-
metadata_path =
|
78
|
+
metadata_path =Pathname.new(
|
79
|
+
File.join(File.dirname(filename), '..', 'metadata.rb')).cleanpath
|
60
80
|
next unless File.exists? metadata_path
|
61
81
|
undeclared = included_recipes(ast).keys.map do |recipe|
|
62
82
|
recipe.split('::').first
|
@@ -65,7 +85,7 @@ rule "FC007", "Ensure recipe dependencies are reflected in cookbook metadata" do
|
|
65
85
|
included_recipes(ast).map do |recipe, include_stmts|
|
66
86
|
if undeclared.include?(recipe) ||
|
67
87
|
undeclared.any?{|u| recipe.start_with?("#{u}::")}
|
68
|
-
include_stmts
|
88
|
+
include_stmts
|
69
89
|
end
|
70
90
|
end.flatten.compact
|
71
91
|
end
|
@@ -77,8 +97,10 @@ rule "FC008", "Generated cookbook metadata needs updating" do
|
|
77
97
|
metadata_path = Pathname.new(File.join(filename, 'metadata.rb')).cleanpath
|
78
98
|
next unless File.exists? metadata_path
|
79
99
|
md = read_ast(metadata_path)
|
80
|
-
{'maintainer' => 'YOUR_COMPANY_NAME',
|
81
|
-
|
100
|
+
{'maintainer' => 'YOUR_COMPANY_NAME',
|
101
|
+
'maintainer_email' => 'YOUR_EMAIL'}.map do |field,value|
|
102
|
+
md.xpath(%Q{//command[ident/@value='#{field}']/
|
103
|
+
descendant::tstring_content[@value='#{value}']}).map do |m|
|
82
104
|
match(m).merge(:filename => metadata_path)
|
83
105
|
end
|
84
106
|
end.flatten
|
@@ -91,12 +113,12 @@ rule "FC009", "Resource attribute not recognised" do
|
|
91
113
|
matches = []
|
92
114
|
resource_attributes_by_type(ast).each do |type,resources|
|
93
115
|
resources.each do |resource|
|
94
|
-
resource.keys.map
|
116
|
+
resource.keys.map(&:to_sym).reject do |att|
|
95
117
|
resource_attribute?(type.to_sym, att)
|
96
118
|
end.each do |invalid_att|
|
97
|
-
matches <<
|
119
|
+
matches << find_resources(ast, :type => type).find do |res|
|
98
120
|
resource_attributes(res).include?(invalid_att.to_s)
|
99
|
-
end
|
121
|
+
end
|
100
122
|
end
|
101
123
|
end
|
102
124
|
end
|
@@ -108,21 +130,25 @@ rule "FC010", "Invalid search syntax" do
|
|
108
130
|
tags %w{correctness search}
|
109
131
|
recipe do |ast|
|
110
132
|
# This only works for literal search strings
|
111
|
-
literal_searches(ast).reject{|search| valid_query?(search['value'])}
|
133
|
+
literal_searches(ast).reject{|search| valid_query?(search['value'])}
|
112
134
|
end
|
113
135
|
end
|
114
136
|
|
115
137
|
rule "FC011", "Missing README in markdown format" do
|
116
138
|
tags %w{style readme}
|
117
139
|
cookbook do |filename|
|
118
|
-
|
140
|
+
unless File.exists?(File.join(filename, 'README.md'))
|
141
|
+
[file_match(File.join(filename, 'README.md'))]
|
142
|
+
end
|
119
143
|
end
|
120
144
|
end
|
121
145
|
|
122
146
|
rule "FC012", "Use Markdown for README rather than RDoc" do
|
123
147
|
tags %w{style readme}
|
124
148
|
cookbook do |filename|
|
125
|
-
|
149
|
+
if File.exists?(File.join(filename, 'README.rdoc'))
|
150
|
+
[file_match(File.join(filename, 'README.rdoc'))]
|
151
|
+
end
|
126
152
|
end
|
127
153
|
end
|
128
154
|
|
@@ -130,9 +156,10 @@ rule "FC013", "Use file_cache_path rather than hard-coding tmp paths" do
|
|
130
156
|
tags %w{style files}
|
131
157
|
recipe do |ast|
|
132
158
|
find_resources(ast, :type => 'remote_file').find_all do |download|
|
133
|
-
path = (resource_attribute(download, 'path') ||
|
159
|
+
path = (resource_attribute(download, 'path') ||
|
160
|
+
resource_name(download)).to_s
|
134
161
|
path.start_with?('/tmp/')
|
135
|
-
end
|
162
|
+
end
|
136
163
|
end
|
137
164
|
end
|
138
165
|
|
@@ -140,30 +167,38 @@ rule "FC014", "Consider extracting long ruby_block to library" do
|
|
140
167
|
tags %w{style libraries}
|
141
168
|
recipe do |ast|
|
142
169
|
find_resources(ast, :type => 'ruby_block').find_all do |rb|
|
143
|
-
! rb.xpath("//fcall[ident/@value='block' and count(ancestor::*) = 8]
|
144
|
-
|
170
|
+
! rb.xpath("//fcall[ident/@value='block' and count(ancestor::*) = 8]/../
|
171
|
+
../do_block[count(descendant::*) > 100]").empty?
|
172
|
+
end
|
145
173
|
end
|
146
174
|
end
|
147
175
|
|
148
176
|
rule "FC015", "Consider converting definition to a LWRP" do
|
149
177
|
tags %w{style definitions lwrp}
|
150
178
|
cookbook do |dir|
|
151
|
-
Dir[File.join(dir, 'definitions', '*.rb')].reject
|
179
|
+
Dir[File.join(dir, 'definitions', '*.rb')].reject do |entry|
|
180
|
+
['.', '..'].include? entry
|
181
|
+
end.map{|entry| file_match(entry)}
|
152
182
|
end
|
153
183
|
end
|
154
184
|
|
155
185
|
rule "FC016", "LWRP does not declare a default action" do
|
156
186
|
tags %w{correctness lwrp}
|
157
187
|
resource do |ast, filename|
|
158
|
-
|
188
|
+
unless ["//ident/@value='default_action'",
|
189
|
+
"//def/bodystmt/descendant::assign/
|
190
|
+
var_field/ivar/@value='@action'"].any? {|expr| ast.xpath(expr)}
|
191
|
+
[file_match(filename)]
|
192
|
+
end
|
159
193
|
end
|
160
194
|
end
|
161
195
|
|
162
196
|
rule "FC017", "LWRP does not notify when updated" do
|
163
197
|
tags %w{correctness lwrp}
|
164
198
|
provider do |ast, filename|
|
165
|
-
if ast.xpath(%q{//call/*[self::vcall or self::var_ref/ident
|
166
|
-
|
199
|
+
if ast.xpath(%q{//call/*[self::vcall or self::var_ref/ident/
|
200
|
+
@value='new_resource']/../
|
201
|
+
ident[@value='updated_by_last_action']}).empty?
|
167
202
|
[file_match(filename)]
|
168
203
|
end
|
169
204
|
end
|
@@ -172,24 +207,34 @@ end
|
|
172
207
|
rule "FC018", "LWRP uses deprecated notification syntax" do
|
173
208
|
tags %w{style lwrp deprecated}
|
174
209
|
provider do |ast|
|
175
|
-
ast.xpath("//assign/var_field/ivar[@value='@updated']").map
|
176
|
-
|
177
|
-
|
210
|
+
ast.xpath("//assign/var_field/ivar[@value='@updated']").map do |class_var|
|
211
|
+
match(class_var)
|
212
|
+
end + ast.xpath(%q{//assign/field/*[self::vcall or self::var_ref/ident/
|
213
|
+
@value='new_resource']/../ident[@value='updated']})
|
178
214
|
end
|
179
215
|
end
|
180
216
|
|
181
217
|
rule "FC019", "Access node attributes in a consistent manner" do
|
182
218
|
tags %w{style attributes}
|
183
219
|
cookbook do |cookbook_dir|
|
184
|
-
asts = {}; files = Dir["#{cookbook_dir}/*/*.rb"].map
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
220
|
+
asts = {}; files = Dir["#{cookbook_dir}/*/*.rb"].map do |file|
|
221
|
+
{:path => file, :ast => read_ast(file)}
|
222
|
+
end
|
223
|
+
types = [:string, :symbol, :vivified].map do |type|
|
224
|
+
{:access_type => type, :count => files.map do |file|
|
225
|
+
attribute_access(file[:ast], :type => type,
|
226
|
+
:ignore_calls => true).tap do |ast|
|
227
|
+
if (! ast.empty?) and (! asts.has_key?(type))
|
228
|
+
asts[type] = {:ast => ast, :path => file[:path]}
|
229
|
+
end
|
230
|
+
end.size
|
231
|
+
end.inject(:+)}
|
232
|
+
end.reject{|type| type[:count] == 0}
|
190
233
|
if asts.size > 1
|
191
234
|
least_used = asts[types.min{|a,b| a[:count] <=> b[:count]}[:access_type]]
|
192
|
-
least_used[:ast].map
|
235
|
+
least_used[:ast].map do |ast|
|
236
|
+
match(ast).merge(:filename => least_used[:path])
|
237
|
+
end
|
193
238
|
end
|
194
239
|
end
|
195
240
|
end
|
@@ -197,13 +242,18 @@ end
|
|
197
242
|
rule "FC020", "Conditional execution string attribute looks like Ruby" do
|
198
243
|
tags %w{correctness}
|
199
244
|
recipe do |ast, filename|
|
200
|
-
conditions = ast.xpath(%q{//command[(ident/@value='only_if' or ident
|
201
|
-
descendant::tstring_content]}).map{|m| match(m)}
|
245
|
+
conditions = ast.xpath(%q{//command[(ident/@value='only_if' or ident/
|
246
|
+
@value='not_if') and descendant::tstring_content]}).map{|m| match(m)}
|
202
247
|
unless conditions.empty?
|
203
|
-
lines = File.readlines(filename) # go back
|
248
|
+
lines = File.readlines(filename) # go back for the raw untokenized string
|
204
249
|
conditions.map do |condition|
|
205
|
-
|
206
|
-
|
250
|
+
line = lines[(condition[:line].to_i) -1]
|
251
|
+
{:match => condition,
|
252
|
+
:raw_string => line.strip.sub(/^(not|only)_if[\s+]["']/, '').chop}
|
253
|
+
end.find_all do |cond|
|
254
|
+
ruby_code?(cond[:raw_string]) and
|
255
|
+
! os_command?(cond[:raw_string])
|
256
|
+
end.map{|cond| cond[:match]}
|
207
257
|
end
|
208
258
|
end
|
209
259
|
end
|
@@ -212,10 +262,12 @@ rule "FC021", "Resource condition in provider may not behave as expected" do
|
|
212
262
|
tags %w{correctness lwrp}
|
213
263
|
provider do |ast|
|
214
264
|
find_resources(ast).map do |resource|
|
215
|
-
condition = resource.xpath(%q{//method_add_block/
|
216
|
-
|
217
|
-
ancestor::
|
218
|
-
|
265
|
+
condition = resource.xpath(%q{//method_add_block/
|
266
|
+
descendant::ident[@value='not_if' or @value='only_if']/
|
267
|
+
ancestor::*[self::method_add_block or self::command][1][descendant::
|
268
|
+
ident/@value='new_resource']/ancestor::stmts_add[2]/method_add_block/
|
269
|
+
command[count(descendant::string_embexpr) = 0]})
|
270
|
+
condition
|
219
271
|
end.compact
|
220
272
|
end
|
221
273
|
end
|
@@ -224,12 +276,18 @@ rule "FC022", "Resource condition within loop may not behave as expected" do
|
|
224
276
|
tags %w{correctness}
|
225
277
|
recipe do |ast|
|
226
278
|
ast.xpath("//call[ident/@value='each']/../do_block").map do |loop|
|
227
|
-
block_vars = loop.xpath("block_var/params/child::*").map
|
279
|
+
block_vars = loop.xpath("block_var/params/child::*").map do |n|
|
280
|
+
n.name.sub(/^ident/, '')
|
281
|
+
end
|
228
282
|
find_resources(loop).map do |resource|
|
229
|
-
# if any of the parameters to the block are used in a condition then we
|
230
|
-
|
231
|
-
|
232
|
-
|
283
|
+
# if any of the parameters to the block are used in a condition then we
|
284
|
+
# have a match
|
285
|
+
unless (block_vars &
|
286
|
+
(resource.xpath(%q{descendant::ident[@value='not_if' or
|
287
|
+
@value='only_if']/ancestor::*[self::method_add_block or
|
288
|
+
self::command][1]/descendant::ident/@value}).map{|a| a.value})).empty?
|
289
|
+
c = resource.xpath('command[count(descendant::string_embexpr) = 0]')
|
290
|
+
resource unless c.empty?
|
233
291
|
end
|
234
292
|
end
|
235
293
|
end.flatten.compact
|
@@ -243,6 +301,26 @@ rule "FC023", "Prefer conditional attributes" do
|
|
243
301
|
[@value='only_if' or @value='not_if']) = 0]/ancestor::*[self::if or
|
244
302
|
self::unless][count(descendant::method_add_block[command/ident]) = 1]
|
245
303
|
[count(stmts_add/method_add_block/call) = 0]
|
246
|
-
[count(stmts_add/stmts_add) = 0]})
|
304
|
+
[count(stmts_add/stmts_add) = 0]})
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
rule "FC024", "Consider adding platform equivalents" do
|
309
|
+
tags %w{portability}
|
310
|
+
RHEL = %w{amazon centos redhat scientific}
|
311
|
+
recipe do |ast|
|
312
|
+
['//method_add_arg[fcall/ident/@value="platform?"]/arg_paren/args_add_block',
|
313
|
+
"//when"].map do |expr|
|
314
|
+
ast.xpath(expr).map do |whn|
|
315
|
+
platforms = whn.xpath("args_add/descendant::tstring_content").map do |p|
|
316
|
+
p['value']
|
317
|
+
end
|
318
|
+
unless platforms.size == 1 || (RHEL & platforms).empty?
|
319
|
+
unless (RHEL - platforms).empty?
|
320
|
+
whn
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end.compact
|
324
|
+
end.flatten
|
247
325
|
end
|
248
326
|
end
|
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: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
12
|
+
date: 2012-03-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: gherkin
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: 2.8.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.8.0
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: gist
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ~>
|
@@ -32,21 +37,43 @@ dependencies:
|
|
32
37
|
version: 2.0.4
|
33
38
|
type: :runtime
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.0.4
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: nokogiri
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
|
-
- -
|
51
|
+
- - ~>
|
42
52
|
- !ruby/object:Gem::Version
|
43
53
|
version: 1.5.0
|
54
|
+
- - ! '!='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.5.1
|
57
|
+
- - ! '!='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 1.5.2
|
44
60
|
type: :runtime
|
45
61
|
prerelease: false
|
46
|
-
version_requirements:
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.5.0
|
68
|
+
- - ! '!='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 1.5.1
|
71
|
+
- - ! '!='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 1.5.2
|
47
74
|
- !ruby/object:Gem::Dependency
|
48
75
|
name: pry
|
49
|
-
requirement:
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
50
77
|
none: false
|
51
78
|
requirements:
|
52
79
|
- - ~>
|
@@ -54,10 +81,15 @@ dependencies:
|
|
54
81
|
version: 0.9.7.4
|
55
82
|
type: :runtime
|
56
83
|
prerelease: false
|
57
|
-
version_requirements:
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.9.7.4
|
58
90
|
- !ruby/object:Gem::Dependency
|
59
91
|
name: pry-doc
|
60
|
-
requirement:
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
61
93
|
none: false
|
62
94
|
requirements:
|
63
95
|
- - ~>
|
@@ -65,10 +97,15 @@ dependencies:
|
|
65
97
|
version: 0.3.0
|
66
98
|
type: :runtime
|
67
99
|
prerelease: false
|
68
|
-
version_requirements:
|
100
|
+
version_requirements: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ~>
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: 0.3.0
|
69
106
|
- !ruby/object:Gem::Dependency
|
70
107
|
name: rak
|
71
|
-
requirement:
|
108
|
+
requirement: !ruby/object:Gem::Requirement
|
72
109
|
none: false
|
73
110
|
requirements:
|
74
111
|
- - ~>
|
@@ -76,10 +113,15 @@ dependencies:
|
|
76
113
|
version: '1.4'
|
77
114
|
type: :runtime
|
78
115
|
prerelease: false
|
79
|
-
version_requirements:
|
116
|
+
version_requirements: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ~>
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '1.4'
|
80
122
|
- !ruby/object:Gem::Dependency
|
81
123
|
name: treetop
|
82
|
-
requirement:
|
124
|
+
requirement: !ruby/object:Gem::Requirement
|
83
125
|
none: false
|
84
126
|
requirements:
|
85
127
|
- - ~>
|
@@ -87,10 +129,15 @@ dependencies:
|
|
87
129
|
version: 1.4.10
|
88
130
|
type: :runtime
|
89
131
|
prerelease: false
|
90
|
-
version_requirements:
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ~>
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: 1.4.10
|
91
138
|
- !ruby/object:Gem::Dependency
|
92
139
|
name: yajl-ruby
|
93
|
-
requirement:
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
94
141
|
none: false
|
95
142
|
requirements:
|
96
143
|
- - ~>
|
@@ -98,7 +145,12 @@ dependencies:
|
|
98
145
|
version: 1.1.0
|
99
146
|
type: :runtime
|
100
147
|
prerelease: false
|
101
|
-
version_requirements:
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ~>
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: 1.1.0
|
102
154
|
description: Lint tool for Opscode Chef cookbooks.
|
103
155
|
email:
|
104
156
|
executables:
|
@@ -118,7 +170,8 @@ files:
|
|
118
170
|
- lib/foodcritic/version.rb
|
119
171
|
- lib/foodcritic.rb
|
120
172
|
- chef_dsl_metadata.json
|
121
|
-
-
|
173
|
+
- !binary |-
|
174
|
+
YmluL2Zvb2Rjcml0aWM=
|
122
175
|
homepage: http://acrmp.github.com/foodcritic
|
123
176
|
licenses:
|
124
177
|
- MIT
|
@@ -140,12 +193,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
193
|
version: '0'
|
141
194
|
segments:
|
142
195
|
- 0
|
143
|
-
hash: -
|
196
|
+
hash: -4140544099475579299
|
144
197
|
requirements: []
|
145
198
|
rubyforge_project:
|
146
|
-
rubygems_version: 1.8.
|
199
|
+
rubygems_version: 1.8.19
|
147
200
|
signing_key:
|
148
201
|
specification_version: 3
|
149
|
-
summary: foodcritic-1.0
|
202
|
+
summary: foodcritic-1.1.0
|
150
203
|
test_files: []
|
151
204
|
has_rdoc:
|