foodcritic 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require 'foodcritic'
2
+ require_relative '../lib/foodcritic'
3
3
  result, status = FoodCritic::Linter.check(ARGV)
4
- puts result; exit status.to_i
4
+ puts result; exit status.to_i
@@ -1,7 +1,8 @@
1
1
  require 'chef'
2
2
  require 'pry'
3
- require 'foodcritic/domain'
4
- require 'foodcritic/helpers'
5
- require 'foodcritic/dsl'
6
- require 'foodcritic/linter'
7
- require 'foodcritic/version'
3
+ require_relative 'foodcritic/domain'
4
+ require_relative 'foodcritic/error_checker'
5
+ require_relative 'foodcritic/helpers'
6
+ require_relative 'foodcritic/dsl'
7
+ require_relative 'foodcritic/linter'
8
+ require_relative 'foodcritic/version'
@@ -0,0 +1,29 @@
1
+ module FoodCritic
2
+
3
+ # Expose if any errors are found in parsing
4
+ class ErrorChecker < Ripper::SexpBuilder
5
+
6
+ # Create a new instance of ErrorChecker
7
+ #
8
+ # @see Ripper::SexpBuilder#initialize
9
+ def initialize(*args)
10
+ super(*args)
11
+ @found_error = false
12
+ end
13
+
14
+ # Was an error encountered during parsing?
15
+ def error?
16
+ @found_error
17
+ end
18
+
19
+ # Register with all available error handlers.
20
+ def self.register_error_handlers
21
+ SexpBuilder.public_instance_methods.grep(/^on_.*_error$/).sort.each do |err_meth|
22
+ define_method(err_meth) { |*| @found_error = true }
23
+ end
24
+ end
25
+
26
+ self.register_error_handlers
27
+
28
+ end
29
+ end
@@ -76,11 +76,35 @@ module FoodCritic
76
76
  end
77
77
  end
78
78
 
79
+ # Find attribute accesses by type.
80
+ #
81
+ # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check
82
+ # @param [Symbol] accessed_via The approach used to access the attributes (:string, :symbol or :vivified)
83
+ # @param [Boolean] exclude_with_dots Exclude attribute accesses that mix strings/symbols with dot notation.
84
+ # @return [Array] The matching nodes if any
85
+ def attribute_access(ast, accessed_via, exclude_with_dots)
86
+ %w{node default override set normal}.map do |att_type|
87
+ if accessed_via == :vivified
88
+ call = ast.xpath(%Q{//*[self::call or self::field][descendant::ident/@value='#{att_type}']
89
+ [@value='.']/descendant::ident})
90
+ (call.size > 1 and call.first['value'] == 'node' and
91
+ Chef::Node.public_instance_methods.include?(call[1]['value'].to_sym)) ? [] : call
92
+ else
93
+ accessed_via = 'tstring_content' if accessed_via == :string
94
+ expr = '//*[self::aref_field or self::aref][descendant::ident'
95
+ expr += '[not(ancestor::aref/call)]' if exclude_with_dots
96
+ expr += "/@value='#{att_type}']/descendant::#{accessed_via}"
97
+ ast.xpath(expr)
98
+ end
99
+ end.flatten.sort
100
+ end
101
+
79
102
  # Find Chef resources of the specified type.
80
103
  # TODO: Include blockless resources
81
104
  #
82
105
  # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check
83
106
  # @param [String] type The type of resource to look for (or nil for all resources)
107
+ # @return [Array] AST nodes of Chef resources.
84
108
  def find_resources(ast, type = nil)
85
109
  ast.xpath(%Q{//method_add_block[command/ident#{type.nil? ? '' : "[@value='#{type}']"}]})
86
110
  end
@@ -96,6 +120,7 @@ module FoodCritic
96
120
  # Retrieve the name attribute associated with the specified resource.
97
121
  #
98
122
  # @param [Nokogiri::XML::Node] resource The resource AST to lookup the name attribute under
123
+ # @return [String] The name attribute value
99
124
  def resource_name(resource)
100
125
  resource.xpath('string(command//tstring_content/@value)')
101
126
  end
@@ -227,6 +252,28 @@ module FoodCritic
227
252
  build_xml(Ripper::SexpBuilder.new(IO.read(file)).parse)
228
253
  end
229
254
 
255
+ # Does the provided string look like ruby code?
256
+ #
257
+ # @param [String] str The string to check for rubiness
258
+ # @return [Boolean] True if this string could be syntactically valid Ruby
259
+ def ruby_code?(str)
260
+ checker = FoodCritic::ErrorChecker.new(str)
261
+ checker.parse
262
+ ! checker.error?
263
+ end
264
+
265
+ # Does the provided string look like an Operating System command? This is a rough heuristic to be taken with a
266
+ # pinch of salt.
267
+ #
268
+ # @param [String] str The string to check
269
+ # @return [Boolean] True if this string might be an OS command
270
+ def os_command?(str)
271
+ str.start_with?('grep ', 'which ') or # common commands
272
+ str.include?('|') or # probably a pipe, could be alternation
273
+ str.match(/^[\w]+$/) or # command name only
274
+ str.match(/ --?[a-z]/i) # command-line flag
275
+ end
276
+
230
277
  end
231
278
 
232
279
  end
@@ -1,9 +1,7 @@
1
1
  rule "FC001", "Use strings in preference to symbols to access node attributes" do
2
2
  tags %w{style attributes}
3
3
  recipe do |ast|
4
- %w{node default override set normal}.map do |type|
5
- ast.xpath("//*[self::aref_field or self::aref][descendant::ident/@value='#{type}']//symbol").map{|ar| match(ar)}
6
- end.flatten
4
+ attribute_access(ast, :symbol, false).map{|ar| match(ar)}
7
5
  end
8
6
  end
9
7
 
@@ -150,13 +148,6 @@ rule "FC015", "Consider converting definition to a LWRP" do
150
148
  end
151
149
  end
152
150
 
153
- rule "FC016", "LWRP does not declare a default action" do
154
- tags %w{correctness lwrp}
155
- provider do |ast, filename|
156
- ast.xpath("//def/bodystmt/descendant::assign/var_field/ivar/@value='@action'") ? [] : [file_match(filename)]
157
- end
158
- end
159
-
160
151
  rule "FC017", "LWRP does not notify when updated" do
161
152
  tags %w{correctness lwrp}
162
153
  provider do |ast, filename|
@@ -174,4 +165,34 @@ rule "FC018", "LWRP uses deprecated notification syntax" do
174
165
  ast.xpath(%q{//assign/field/*[self::vcall or self::var_ref/ident/@value='new_resource']/../
175
166
  ident[@value='updated']}).map{|assign| match(assign)}
176
167
  end
168
+ end
169
+
170
+ rule "FC019", "Access node attributes in a consistent manner" do
171
+ tags %w{style attributes}
172
+ cookbook do |cookbook_dir|
173
+ asts = {}; files = Dir["#{cookbook_dir}/**/*.rb"].map{|file| {:path => file, :ast => read_file(file)}}
174
+ types = [:string, :symbol, :vivified].map{|type| {:access_type => type, :count => files.count do |file|
175
+ ! attribute_access(file[:ast], type, true).tap{|ast|
176
+ asts[type] = {:ast => ast.first, :path => file[:path]} if (! ast.empty?) and (! asts.has_key?(type))
177
+ }.empty?
178
+ end}}.reject{|type| type[:count] == 0}
179
+ if asts.size > 1
180
+ least_used = asts[types.min{|a,b| a[:count] <=> b[:count]}[:access_type]]
181
+ [match(least_used[:ast]).merge(:filename => least_used[:path])]
182
+ end
183
+ end
184
+ end
185
+
186
+ rule "FC020", "Conditional execution string attribute looks like Ruby" do
187
+ tags %w{correctness}
188
+ recipe do |ast, filename|
189
+ conditions = ast.xpath(%q{//command[(ident/@value='only_if' or ident/@value='not_if') and
190
+ descendant::tstring_content]}).map{|m| match(m)}
191
+ unless conditions.empty?
192
+ lines = File.readlines(filename) # go back and get the raw untokenized string
193
+ conditions.map do |condition|
194
+ {:match => condition, :raw_string => lines[(condition[:line].to_i) -1].strip.sub(/^(not|only)_if[\s+]"/, '').chop}
195
+ end.find_all{|cond| ruby_code?(cond[:raw_string]) and ! os_command?(cond[:raw_string])}.map{|cond| cond[:match]}
196
+ end
197
+ end
177
198
  end
@@ -1,4 +1,4 @@
1
1
  module FoodCritic
2
2
  # The current version of foodcritic
3
- VERSION = '0.7.0'
3
+ VERSION = '0.8.0'
4
4
  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.7.0
4
+ version: 0.8.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-31 00:00:00.000000000 Z
12
+ date: 2012-01-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chef
16
- requirement: &2160561320 !ruby/object:Gem::Requirement
16
+ requirement: &2152976460 !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: *2160561320
24
+ version_requirements: *2152976460
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: json
27
- requirement: &2160560580 !ruby/object:Gem::Requirement
27
+ requirement: &2152975660 !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: *2160560580
38
+ version_requirements: *2152975660
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: gherkin
41
- requirement: &2160559620 !ruby/object:Gem::Requirement
41
+ requirement: &2152974740 !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: *2160559620
49
+ version_requirements: *2152974740
50
50
  - !ruby/object:Gem::Dependency
51
51
  name: gist
52
- requirement: &2160558760 !ruby/object:Gem::Requirement
52
+ requirement: &2152974160 !ruby/object:Gem::Requirement
53
53
  none: false
54
54
  requirements:
55
55
  - - ~>
@@ -57,10 +57,10 @@ dependencies:
57
57
  version: 2.0.4
58
58
  type: :runtime
59
59
  prerelease: false
60
- version_requirements: *2160558760
60
+ version_requirements: *2152974160
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: nokogiri
63
- requirement: &2160557900 !ruby/object:Gem::Requirement
63
+ requirement: &2152966880 !ruby/object:Gem::Requirement
64
64
  none: false
65
65
  requirements:
66
66
  - - ~>
@@ -68,10 +68,10 @@ dependencies:
68
68
  version: 1.5.0
69
69
  type: :runtime
70
70
  prerelease: false
71
- version_requirements: *2160557900
71
+ version_requirements: *2152966880
72
72
  - !ruby/object:Gem::Dependency
73
73
  name: pry
74
- requirement: &2160556980 !ruby/object:Gem::Requirement
74
+ requirement: &2152966140 !ruby/object:Gem::Requirement
75
75
  none: false
76
76
  requirements:
77
77
  - - ~>
@@ -79,10 +79,10 @@ dependencies:
79
79
  version: 0.9.7.4
80
80
  type: :runtime
81
81
  prerelease: false
82
- version_requirements: *2160556980
82
+ version_requirements: *2152966140
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: pry-doc
85
- requirement: &2160556240 !ruby/object:Gem::Requirement
85
+ requirement: &2152965480 !ruby/object:Gem::Requirement
86
86
  none: false
87
87
  requirements:
88
88
  - - ~>
@@ -90,7 +90,7 @@ dependencies:
90
90
  version: 0.3.0
91
91
  type: :runtime
92
92
  prerelease: false
93
- version_requirements: *2160556240
93
+ version_requirements: *2152965480
94
94
  description: Lint tool for Opscode Chef cookbooks.
95
95
  email:
96
96
  executables:
@@ -100,6 +100,7 @@ extra_rdoc_files: []
100
100
  files:
101
101
  - lib/foodcritic/domain.rb
102
102
  - lib/foodcritic/dsl.rb
103
+ - lib/foodcritic/error_checker.rb
103
104
  - lib/foodcritic/helpers.rb
104
105
  - lib/foodcritic/linter.rb
105
106
  - lib/foodcritic/rules.rb
@@ -127,12 +128,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
128
  version: '0'
128
129
  segments:
129
130
  - 0
130
- hash: -1380277540148398826
131
+ hash: -3790252679895367977
131
132
  requirements: []
132
133
  rubyforge_project:
133
134
  rubygems_version: 1.8.10
134
135
  signing_key:
135
136
  specification_version: 3
136
- summary: foodcritic-0.7.0
137
+ summary: foodcritic-0.8.0
137
138
  test_files: []
138
139
  has_rdoc: