foodcritic 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,13 +32,15 @@ module FoodCritic
32
32
  #
33
33
  # @return [String] Review as a string, this representation is liable to change.
34
34
  def to_s
35
- @warnings.map { |w| "#{w.rule.code}: #{w.rule.name}: #{w.match[:filename]}:#{w.match[:line]}" }.sort.uniq.join("\n")
35
+ @warnings.map{|w|["#{w.rule.code}: #{w.rule.name}: #{w.match[:filename]}", w.match[:line].to_i]}.sort do |x,y|
36
+ x.first == y.first ? x[1] <=> y[1] : x.first <=> y.first
37
+ end.map{|w|"#{w.first}:#{w[1]}"}.uniq.join("\n")
36
38
  end
37
39
  end
38
40
 
39
41
  # A rule to be matched against.
40
42
  class Rule
41
- attr_accessor :code, :name, :description, :recipe, :tags
43
+ attr_accessor :code, :name, :description, :cookbook, :recipe, :provider, :tags
42
44
 
43
45
  # Create a new rule
44
46
  #
@@ -40,6 +40,20 @@ module FoodCritic
40
40
  rules.last.recipe = block
41
41
  end
42
42
 
43
+ # Define a matcher that will be passed the AST with this method.
44
+ #
45
+ # @param [block] block Your implemented matcher that returns a match Hash.
46
+ def provider(&block)
47
+ rules.last.provider = block
48
+ end
49
+
50
+ # Define a matcher that will be passed the cookbook path with this method.
51
+ #
52
+ # @param [block] block Your implemented matcher that returns a match Hash.
53
+ def cookbook(&block)
54
+ rules.last.cookbook = block
55
+ end
56
+
43
57
  # Load the ruleset
44
58
  #
45
59
  # @param [String] filename The path to the ruleset to load
@@ -15,6 +15,10 @@ module FoodCritic
15
15
  {:matched => node.respond_to?(:name) ? node.name : '', :line => pos['line'], :column => pos['column']}
16
16
  end
17
17
 
18
+ def file_match(file)
19
+ {:filename => file, :matched => file, :line => 1, :column => 1}
20
+ end
21
+
18
22
  # Does the specified recipe check for Chef Solo?
19
23
  #
20
24
  # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check.
@@ -20,20 +20,35 @@ module FoodCritic
20
20
  # @option options [Array] tags The tags to filter rules based on
21
21
  # @return [FoodCritic::Review] A review of your cookbooks, with any warnings issued.
22
22
  def check(cookbook_path, options)
23
- warnings = []
23
+ warnings = []; last_dir = nil
24
24
  tag_expr = Gherkin::TagExpression.new(options[:tags])
25
25
  files_to_process(cookbook_path).each do |file|
26
+ cookbook_dir = Pathname.new(File.join(File.dirname(file), '..')).cleanpath
26
27
  ast = read_file(file)
27
28
  @rules.select{|rule| tag_expr.eval(rule.tags)}.each do |rule|
28
- matches = rule.recipe.yield(ast, file)
29
- matches.each{|match| warnings << Warning.new(rule, {:filename => file}.merge(match))} unless matches.nil?
29
+ rule_matches = matches(rule.recipe, ast, file)
30
+ rule_matches += matches(rule.provider, ast, file) if File.basename(File.dirname(file)) == 'providers'
31
+ rule_matches += matches(rule.cookbook, cookbook_dir) if last_dir != cookbook_dir
32
+ rule_matches.each{|match| warnings << Warning.new(rule, {:filename => file}.merge(match))}
30
33
  end
34
+ last_dir = cookbook_dir
31
35
  end
32
36
  Review.new(warnings)
33
37
  end
34
38
 
35
39
  private
36
40
 
41
+ # Invoke the DSL method with the provided parameters.
42
+ #
43
+ # @param [Proc] match_method Proc to invoke
44
+ # @param params Parameters for the proc
45
+ # @return [Array] The returned matches
46
+ def matches(match_method, *params)
47
+ return [] unless match_method.respond_to?(:yield)
48
+ matches = match_method.yield(*params)
49
+ matches.respond_to?(:each) ? matches : []
50
+ end
51
+
37
52
  # Load the rules from the (fairly unnecessary) DSL.
38
53
  def load_rules
39
54
  @rules = RuleDsl.load(File.join(File.dirname(__FILE__), 'rules.rb'))
@@ -45,7 +60,8 @@ module FoodCritic
45
60
  # @return [Array] The files underneath the provided directory to be processed.
46
61
  def files_to_process(dir)
47
62
  return [dir] unless File.directory? dir
48
- Dir.glob(File.join(dir, '{attributes,recipes}/*.rb')) + Dir.glob(File.join(dir, '*/{attributes,recipes}/*.rb'))
63
+ Dir.glob(File.join(dir, '{attributes,providers,recipes}/*.rb')) +
64
+ Dir.glob(File.join(dir, '*/{attributes,providers,recipes}/*.rb'))
49
65
  end
50
66
 
51
67
  end
@@ -1,3 +1,13 @@
1
+ rule "FC001", "Use strings in preference to symbols to access node attributes" do
2
+ tags %w{style attributes}
3
+ description "When accessing node attributes you should use a string for a key rather than a symbol."
4
+ recipe do |ast|
5
+ %w{node default override set normal}.map do |type|
6
+ ast.xpath("//*[self::aref_field or self::aref][descendant::ident/@value='#{type}']//symbol").map{|ar| match(ar)}
7
+ end.flatten
8
+ end
9
+ end
10
+
1
11
  rule "FC002", "Avoid string interpolation where not required" do
2
12
  tags %w{style strings}
3
13
  description "When setting a resource value avoid string interpolation where not required."
@@ -21,7 +31,8 @@ rule "FC004", "Use a service resource to start and stop services" do
21
31
  recipe do |ast|
22
32
  find_resources(ast, 'execute').find_all do |cmd|
23
33
  cmd_str = (resource_attribute('command', cmd) || resource_name(cmd)).to_s
24
- cmd_str.include?('/etc/init.d') || cmd_str.start_with?('service ') || cmd_str.start_with?('/sbin/service ')
34
+ cmd_str.include?('/etc/init.d') || cmd_str.start_with?('service ') || cmd_str.start_with?('/sbin/service ') ||
35
+ cmd_str.start_with?('start ') || cmd_str.start_with?('stop ') || cmd_str.start_with?('invoke-rc.d ')
25
36
  end.map{|cmd| match(cmd)}
26
37
  end
27
38
  end
@@ -34,7 +45,7 @@ rule "FC005", "Avoid repetition of resource declarations" do
34
45
  # do all of the attributes for all resources of a given type match apart aside from one?
35
46
  resource_attributes_by_type(ast).each do |type, resource_atts|
36
47
  sorted_atts = resource_atts.map{|atts| atts.to_a.sort{|x,y| x.first.to_s <=> y.first.to_s }}
37
- if sorted_atts.all?{|att| (att - sorted_atts.inject{|atts,a| atts & a}).length == 1}
48
+ if sorted_atts.length > 2 and sorted_atts.all?{|att| (att - sorted_atts.inject{|atts,a| atts & a}).length == 1}
38
49
  matches << match(find_resources(ast, type).first)
39
50
  end
40
51
  end
@@ -68,8 +79,8 @@ end
68
79
  rule "FC008", "Generated cookbook metadata needs updating" do
69
80
  tags %w{style metadata}
70
81
  description "The cookbook metadata for this cookbook is boilerplate output from knife generate cookbook and needs updating with the real details of your cookbook."
71
- recipe do |ast,filename|
72
- metadata_path = Pathname.new(File.join(File.dirname(filename), '..', 'metadata.rb')).cleanpath
82
+ cookbook do |filename|
83
+ metadata_path = Pathname.new(File.join(filename, 'metadata.rb')).cleanpath
73
84
  next unless File.exists? metadata_path
74
85
  md = read_file(metadata_path)
75
86
  {'maintainer' => 'YOUR_COMPANY_NAME', 'maintainer_email' => 'YOUR_EMAIL'}.map do |field,value|
@@ -107,4 +118,70 @@ rule "FC010", "Invalid search syntax" do
107
118
  # This only works for literal search strings
108
119
  literal_searches(ast).reject{|search| valid_query?(search['value'])}.map{|search| match(search)}
109
120
  end
121
+ end
122
+
123
+ rule "FC011", "Missing README in markdown format" do
124
+ tags %w{style readme}
125
+ cookbook do |filename|
126
+ [file_match(File.join(filename, 'README.md'))] unless File.exists?(File.join(filename, 'README.md'))
127
+ end
128
+ end
129
+
130
+ rule "FC012", "Use Markdown for README rather than RDoc" do
131
+ tags %w{style readme}
132
+ cookbook do |filename|
133
+ [file_match(File.join(filename, 'README.rdoc'))] if File.exists?(File.join(filename, 'README.rdoc'))
134
+ end
135
+ end
136
+
137
+ rule "FC013", "Use file_cache_path rather than hard-coding tmp paths" do
138
+ tags %w{style files}
139
+ recipe do |ast|
140
+ find_resources(ast, 'remote_file').find_all do |download|
141
+ path = (resource_attribute('path', download) || resource_name(download)).to_s
142
+ path.start_with?('/tmp/')
143
+ end.map{|download| match(download)}
144
+ end
145
+ end
146
+
147
+ rule "FC014", "Consider extracting long ruby_block to library" do
148
+ tags %w{style libraries}
149
+ recipe do |ast|
150
+ find_resources(ast, 'ruby_block').find_all do |rb|
151
+ ! rb.xpath("//fcall[ident/@value='block' and count(ancestor::*) = 8]/../../do_block[count(descendant::*) > 100]").empty?
152
+ end.map{|block| match(block)}
153
+ end
154
+ end
155
+
156
+ rule "FC015", "Consider converting definition to a LWRP" do
157
+ tags %w{style definitions lwrp}
158
+ cookbook do |dir|
159
+ Dir[File.join(dir, 'definitions', '*.rb')].reject{|entry| ['.', '..'].include? entry}.map{|entry| file_match(entry)}
160
+ end
161
+ end
162
+
163
+ rule "FC016", "LWRP does not declare a default action" do
164
+ tags %w{correctness lwrp}
165
+ provider do |ast, filename|
166
+ ast.xpath("//def/bodystmt/descendant::assign/var_field/ivar/@value='@action'") ? [] : [file_match(filename)]
167
+ end
168
+ end
169
+
170
+ rule "FC017", "LWRP does not notify when updated" do
171
+ tags %w{correctness lwrp}
172
+ provider do |ast, filename|
173
+ if ast.xpath(%q{//call/*[self::vcall or self::var_ref/ident/@value='new_resource']/../
174
+ ident[@value='updated_by_last_action']}).empty?
175
+ [file_match(filename)]
176
+ end
177
+ end
178
+ end
179
+
180
+ rule "FC018", "LWRP uses deprecated notification syntax" do
181
+ tags %w{style lwrp deprecated}
182
+ provider do |ast|
183
+ ast.xpath("//assign/var_field/ivar[@value='@updated']").map{|class_var| match(class_var)} +
184
+ ast.xpath(%q{//assign/field/*[self::vcall or self::var_ref/ident/@value='new_resource']/../
185
+ ident[@value='updated']}).map{|assign| match(assign)}
186
+ end
110
187
  end
@@ -1,3 +1,3 @@
1
1
  module FoodCritic
2
- VERSION = '0.5.2'
2
+ VERSION = '0.6.0'
3
3
  end
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.5.2
4
+ version: 0.6.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: 2011-12-15 00:00:00.000000000Z
12
+ date: 2011-12-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chef
16
- requirement: &2155159160 !ruby/object:Gem::Requirement
16
+ requirement: &2152976100 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.10.4
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2155159160
24
+ version_requirements: *2152976100
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: json
27
- requirement: &2155158600 !ruby/object:Gem::Requirement
27
+ requirement: &2152975580 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -35,10 +35,10 @@ dependencies:
35
35
  version: 1.6.1
36
36
  type: :runtime
37
37
  prerelease: false
38
- version_requirements: *2155158600
38
+ version_requirements: *2152975580
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: gherkin
41
- requirement: &2155157740 !ruby/object:Gem::Requirement
41
+ requirement: &2152974820 !ruby/object:Gem::Requirement
42
42
  none: false
43
43
  requirements:
44
44
  - - ~>
@@ -46,10 +46,10 @@ dependencies:
46
46
  version: 2.7.1
47
47
  type: :runtime
48
48
  prerelease: false
49
- version_requirements: *2155157740
49
+ version_requirements: *2152974820
50
50
  - !ruby/object:Gem::Dependency
51
51
  name: nokogiri
52
- requirement: &2155157240 !ruby/object:Gem::Requirement
52
+ requirement: &2152974340 !ruby/object:Gem::Requirement
53
53
  none: false
54
54
  requirements:
55
55
  - - ~>
@@ -57,7 +57,7 @@ dependencies:
57
57
  version: 1.5.0
58
58
  type: :runtime
59
59
  prerelease: false
60
- version_requirements: *2155157240
60
+ version_requirements: *2152974340
61
61
  description: Lint tool for Opscode Chef cookbooks.
62
62
  email:
63
63
  executables:
@@ -94,12 +94,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  version: '0'
95
95
  segments:
96
96
  - 0
97
- hash: 2219223099964233335
97
+ hash: 2590602930680125427
98
98
  requirements: []
99
99
  rubyforge_project:
100
100
  rubygems_version: 1.8.10
101
101
  signing_key:
102
102
  specification_version: 3
103
- summary: foodcritic-0.5.2
103
+ summary: foodcritic-0.6.0
104
104
  test_files: []
105
105
  has_rdoc: