foodcritic 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,4 @@
1
+ require 'chef'
1
2
  require 'foodcritic/domain'
2
3
  require 'foodcritic/helpers'
3
4
  require 'foodcritic/dsl'
@@ -1,9 +1,12 @@
1
+ require 'pathname'
2
+
1
3
  module FoodCritic
2
4
 
3
5
  # The DSL methods exposed for defining rules.
4
6
  class RuleDsl
5
7
  attr_reader :rules
6
8
  include Helpers
9
+ include Chef::Mixin::ConvertToClassName
7
10
 
8
11
  # Define a new rule
9
12
  #
@@ -1,5 +1,4 @@
1
1
  require 'nokogiri'
2
- require 'xmlsimple'
3
2
 
4
3
  module FoodCritic
5
4
 
@@ -12,7 +11,7 @@ module FoodCritic
12
11
  # @return [Hash] Hash with the matched node name and position with the recipe
13
12
  def match(node)
14
13
  pos = node.xpath('descendant::pos').first
15
- {:matched => node.name, :line => pos['line'], :column => pos['column']}
14
+ {:matched => node.respond_to?(:name) ? node.name : '', :line => pos['line'], :column => pos['column']}
16
15
  end
17
16
 
18
17
  # Does the specified recipe check for Chef Solo?
@@ -71,7 +70,7 @@ module FoodCritic
71
70
  # @return [Hash] The resource attributes
72
71
  def resource_attributes(resource)
73
72
  atts = {:name => resource_name(resource)}
74
- resource.xpath('do_block/descendant::command').each do |att|
73
+ resource.xpath('do_block/descendant::command[count(ancestor::do_block) = 1]').each do |att|
75
74
  if att.xpath('descendant::symbol').empty?
76
75
  att_value = att.xpath('string(descendant::tstring_content/@value)')
77
76
  else
@@ -144,26 +143,35 @@ module FoodCritic
144
143
  node.respond_to?(:length) and node.length == 2 and node.respond_to?(:all?) and node.all?{|child| child.respond_to?(:to_i)}
145
144
  end
146
145
 
147
- # Recurse the nested arrays provided by Ripper to create an intermediate Hash for ease of searching.
146
+ # Recurse the nested arrays provided by Ripper to create a tree we can more easily apply expressions to.
148
147
  #
149
- # @param [Nokogiri::XML::Node] node The AST
150
- # @return [Hash] The friendlier Hash.
151
- def ast_to_hash(node)
152
- result = {}
148
+ # @param [Hash] node The AST
149
+ # @param [Nokogiri::XML::Document] doc The document being constructed
150
+ # @param [Nokogiri::XML::Node] xml_node The current node
151
+ # @return [Nokogiri::XML::Node] The XML representation
152
+ def build_xml(node, doc = nil, xml_node=nil)
153
+ if doc.nil?
154
+ doc = Nokogiri::XML('<opt></opt>')
155
+ xml_node = doc.root
156
+ end
153
157
  if node.respond_to?(:each)
154
158
  node.drop(1).each do |child|
155
159
  if position_node?(child)
156
- result[:pos] = {:line => child.first, :column => child[1]}
160
+ pos = Nokogiri::XML::Node.new("pos", doc)
161
+ pos['line'] = child.first.to_s
162
+ pos['column'] = child[1].to_s
163
+ xml_node.add_child(pos)
157
164
  else
158
165
  if child.respond_to?(:first)
159
- result[child.first.to_s.gsub(/[^a-z_]/, '')] = ast_to_hash(child)
166
+ n = Nokogiri::XML::Node.new(child.first.to_s.gsub(/[^a-z_]/, ''), doc)
167
+ xml_node.add_child(build_xml(child, doc, n))
160
168
  else
161
- result[:value] = child unless child.nil?
169
+ xml_node['value'] = child.to_s unless child.nil?
162
170
  end
163
171
  end
164
172
  end
165
173
  end
166
- result
174
+ xml_node
167
175
  end
168
176
 
169
177
  # Read the AST for the given Ruby file
@@ -171,7 +179,7 @@ module FoodCritic
171
179
  # @param [String] file The file to read
172
180
  # @return [Nokogiri::XML::Node] The recipe AST
173
181
  def read_file(file)
174
- Nokogiri::XML(XmlSimple.xml_out(ast_to_hash(Ripper::SexpBuilder.new(IO.read(file)).parse)))
182
+ build_xml(Ripper::SexpBuilder.new(IO.read(file)).parse)
175
183
  end
176
184
 
177
185
  end
@@ -21,8 +21,8 @@ module FoodCritic
21
21
  files_to_process(cookbook_path).each do |file|
22
22
  ast = read_file(file)
23
23
  @rules.each do |rule|
24
- matches = rule.recipe.yield(ast, File.expand_path(file))
25
- matches.each{|match| warnings << Warning.new(rule, match.merge({:filename => file}))} unless matches.nil?
24
+ matches = rule.recipe.yield(ast, file)
25
+ matches.each{|match| warnings << Warning.new(rule, {:filename => file}.merge(match))} unless matches.nil?
26
26
  end
27
27
  end
28
28
  Review.new(warnings)
@@ -49,12 +49,45 @@ end
49
49
  rule "FC007", "Ensure recipe dependencies are reflected in cookbook metadata" do
50
50
  description "You are including a recipe that is not in the current cookbook and not defined as a dependency in your cookbook metadata."
51
51
  recipe do |ast,filename|
52
- metadata_path = File.join(File.dirname(filename), '..', 'metadata.rb')
52
+ metadata_path = Pathname.new(File.join(File.dirname(filename), '..', 'metadata.rb')).cleanpath
53
53
  next unless File.exists? metadata_path
54
54
  undeclared = included_recipes(ast).keys.map{|recipe|recipe.split('::').first} - [cookbook_name(filename)] -
55
55
  declared_dependencies(read_file(metadata_path))
56
56
  included_recipes(ast).map do |recipe, resource|
57
- match(resource) if undeclared.include?(recipe) || undeclared.any?{|u| recipe.start_with?("#{u}::")}
57
+ match(resource).merge(:filename => metadata_path) if undeclared.include?(recipe) || undeclared.any?{|u| recipe.start_with?("#{u}::")}
58
58
  end.compact
59
59
  end
60
60
  end
61
+
62
+ rule "FC008", "Generated cookbook metadata needs updating" do
63
+ description "The cookbook metadata for this cookbook is boilerplate output from knife generate cookbook and needs updating with the real details of your cookbook."
64
+ recipe do |ast,filename|
65
+ metadata_path = Pathname.new(File.join(File.dirname(filename), '..', 'metadata.rb')).cleanpath
66
+ next unless File.exists? metadata_path
67
+ md = read_file(metadata_path)
68
+ {'maintainer' => 'YOUR_COMPANY_NAME', 'maintainer_email' => 'YOUR_EMAIL'}.map do |field,value|
69
+ md.xpath(%Q{//command[ident/@value='#{field}']/descendant::tstring_content[@value='#{value}']}).map do |m|
70
+ match(m).merge(:filename => metadata_path)
71
+ end
72
+ end.flatten
73
+ end
74
+ end
75
+
76
+ rule "FC009", "Resource attribute not recognised" do
77
+ description "You appear to be using an unrecognised attribute on a standard Chef resource. Please check for typos."
78
+ recipe do |ast|
79
+ matches = []
80
+ resource_attributes_by_type(ast).each do |type,resources|
81
+ if Chef::Resource.const_defined?(convert_to_class_name(type))
82
+ allowed_atts = Chef::Resource.const_get(convert_to_class_name(type)).public_instance_methods(true)
83
+ resources.each do |resource|
84
+ invalid_atts = resource.keys.map{|att|att.to_sym} - allowed_atts
85
+ unless invalid_atts.empty?
86
+ matches << match(find_resources(ast, type).find{|res|resource_attributes(res).include?(invalid_atts.first.to_s)})
87
+ end
88
+ end
89
+ end
90
+ end
91
+ matches
92
+ end
93
+ end
@@ -1,3 +1,3 @@
1
1
  module FoodCritic
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.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.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,30 +9,30 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-04 00:00:00.000000000 Z
12
+ date: 2011-12-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: nokogiri
16
- requirement: &2157802560 !ruby/object:Gem::Requirement
15
+ name: chef
16
+ requirement: &2153103960 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 1.5.0
21
+ version: 0.10.4
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2157802560
24
+ version_requirements: *2153103960
25
25
  - !ruby/object:Gem::Dependency
26
- name: xml-simple
27
- requirement: &2157800260 !ruby/object:Gem::Requirement
26
+ name: nokogiri
27
+ requirement: &2153103180 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
31
31
  - !ruby/object:Gem::Version
32
- version: 1.1.1
32
+ version: 1.5.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2157800260
35
+ version_requirements: *2153103180
36
36
  description: Lint tool for Opscode Chef cookbooks.
37
37
  email:
38
38
  executables:
@@ -69,12 +69,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
69
  version: '0'
70
70
  segments:
71
71
  - 0
72
- hash: 927165501432344600
72
+ hash: 1643437083715464422
73
73
  requirements: []
74
74
  rubyforge_project:
75
75
  rubygems_version: 1.8.10
76
76
  signing_key:
77
77
  specification_version: 3
78
- summary: foodcritic-0.3.0
78
+ summary: foodcritic-0.4.0
79
79
  test_files: []
80
80
  has_rdoc: