foodcritic 0.3.0 → 0.4.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.
@@ -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: