foodcritic 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/foodcritic/domain.rb +4 -2
- data/lib/foodcritic/dsl.rb +14 -0
- data/lib/foodcritic/helpers.rb +4 -0
- data/lib/foodcritic/linter.rb +20 -4
- data/lib/foodcritic/rules.rb +81 -4
- data/lib/foodcritic/version.rb +1 -1
- metadata +12 -12
data/lib/foodcritic/domain.rb
CHANGED
@@ -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
|
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
|
#
|
data/lib/foodcritic/dsl.rb
CHANGED
@@ -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
|
data/lib/foodcritic/helpers.rb
CHANGED
@@ -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.
|
data/lib/foodcritic/linter.rb
CHANGED
@@ -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
|
-
|
29
|
-
|
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')) +
|
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
|
data/lib/foodcritic/rules.rb
CHANGED
@@ -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
|
-
|
72
|
-
metadata_path = Pathname.new(File.join(
|
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
|
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: 0.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *2152976100
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: json
|
27
|
-
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: *
|
38
|
+
version_requirements: *2152975580
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
40
|
name: gherkin
|
41
|
-
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: *
|
49
|
+
version_requirements: *2152974820
|
50
50
|
- !ruby/object:Gem::Dependency
|
51
51
|
name: nokogiri
|
52
|
-
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: *
|
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:
|
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.
|
103
|
+
summary: foodcritic-0.6.0
|
104
104
|
test_files: []
|
105
105
|
has_rdoc:
|