foodcritic 0.5.2 → 0.6.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.
- 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:
|