foodcritic 1.0.1 → 1.1.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/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:
|