foodcritic 7.0.1 → 7.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +10 -10
- data/Rakefile +21 -21
- data/bin/foodcritic +1 -1
- data/chef_dsl_metadata/chef_12.12.13.json +17809 -0
- data/chef_dsl_metadata/chef_12.13.37.json +18021 -0
- data/features/step_definitions/cookbook_steps.rb +456 -457
- data/features/support/command_helpers.rb +120 -120
- data/features/support/cookbook_helpers.rb +87 -87
- data/features/support/env.rb +6 -6
- data/foodcritic.gemspec +22 -22
- data/lib/foodcritic.rb +20 -20
- data/lib/foodcritic/api.rb +94 -91
- data/lib/foodcritic/chef.rb +14 -14
- data/lib/foodcritic/command_line.rb +35 -35
- data/lib/foodcritic/domain.rb +4 -4
- data/lib/foodcritic/dsl.rb +1 -2
- data/lib/foodcritic/linter.rb +31 -32
- data/lib/foodcritic/notifications.rb +5 -5
- data/lib/foodcritic/output.rb +5 -5
- data/lib/foodcritic/rake_task.rb +7 -7
- data/lib/foodcritic/rules.rb +234 -234
- data/lib/foodcritic/template.rb +1 -1
- data/lib/foodcritic/version.rb +1 -1
- data/lib/foodcritic/xml.rb +5 -5
- data/spec/foodcritic/api_spec.rb +275 -275
- data/spec/foodcritic/chef_spec.rb +11 -11
- data/spec/foodcritic/command_line_spec.rb +7 -7
- data/spec/foodcritic/domain_spec.rb +20 -20
- data/spec/foodcritic/linter_spec.rb +10 -11
- data/spec/foodcritic/template_spec.rb +8 -8
- data/spec/regression/regression_spec.rb +3 -3
- data/spec/regression_helpers.rb +10 -10
- data/spec/spec_helper.rb +6 -6
- metadata +5 -3
data/lib/foodcritic/chef.rb
CHANGED
@@ -23,7 +23,7 @@ module FoodCritic
|
|
23
23
|
|
24
24
|
# Is this a valid Lucene query?
|
25
25
|
def valid_query?(query)
|
26
|
-
|
26
|
+
raise ArgumentError, "Query cannot be nil or empty" if query.to_s.empty?
|
27
27
|
|
28
28
|
# Attempt to create a search query parser
|
29
29
|
search = FoodCritic::Chef::Search.new
|
@@ -51,7 +51,7 @@ module FoodCritic
|
|
51
51
|
else
|
52
52
|
Linter::DEFAULT_CHEF_VERSION
|
53
53
|
end
|
54
|
-
metadata_path = [version, version.sub(/\.[a-z].*/,
|
54
|
+
metadata_path = [version, version.sub(/\.[a-z].*/, ""),
|
55
55
|
Linter::DEFAULT_CHEF_VERSION].map do |version|
|
56
56
|
metadata_path(version)
|
57
57
|
end.find { |m| File.exist?(m) }
|
@@ -60,13 +60,13 @@ module FoodCritic
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def metadata_path(chef_version)
|
63
|
-
File.join(File.dirname(__FILE__),
|
63
|
+
File.join(File.dirname(__FILE__), "..", "..",
|
64
64
|
"chef_dsl_metadata/chef_#{chef_version}.json")
|
65
65
|
end
|
66
66
|
|
67
67
|
def resource_check?(key, resource_type, field)
|
68
68
|
if resource_type.to_s.empty? || field.to_s.empty?
|
69
|
-
|
69
|
+
raise ArgumentError, "Arguments cannot be nil or empty."
|
70
70
|
end
|
71
71
|
|
72
72
|
load_metadata
|
@@ -85,23 +85,23 @@ module FoodCritic
|
|
85
85
|
# lucene.treetop used to be provided by chef gem
|
86
86
|
# We're keeping a local copy from chef 10.x
|
87
87
|
def chef_search_grammars
|
88
|
-
[File.expand_path(
|
88
|
+
[File.expand_path("../../..", __FILE__) + "/misc/lucene.treetop"]
|
89
89
|
end
|
90
90
|
|
91
91
|
# Create the search parser from the first loadable grammar.
|
92
92
|
def create_parser(grammar_paths)
|
93
93
|
@search_parser ||=
|
94
94
|
grammar_paths.inject(nil) do |parser, lucene_grammar|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
95
|
+
begin
|
96
|
+
break parser unless parser.nil?
|
97
|
+
# Don't instantiate custom nodes
|
98
|
+
Treetop.load_from_string(
|
99
|
+
IO.read(lucene_grammar).gsub(/<[^>]+>/, ""))
|
100
|
+
LuceneParser.new
|
101
|
+
rescue
|
102
|
+
# Silently swallow and try the next grammar
|
103
|
+
end
|
103
104
|
end
|
104
|
-
end
|
105
105
|
end
|
106
106
|
|
107
107
|
# Has the search parser been loaded?
|
@@ -14,75 +14,75 @@ module FoodCritic
|
|
14
14
|
cookbook_paths: [],
|
15
15
|
role_paths: [],
|
16
16
|
environment_paths: [],
|
17
|
-
exclude_paths: []
|
17
|
+
exclude_paths: [],
|
18
18
|
}
|
19
19
|
@parser = OptionParser.new do |opts|
|
20
|
-
opts.banner =
|
21
|
-
opts.on(
|
22
|
-
|
23
|
-
|
20
|
+
opts.banner = "foodcritic [cookbook_paths]"
|
21
|
+
opts.on("-t", "--tags TAGS",
|
22
|
+
"Check against (or exclude ~) rules with the "\
|
23
|
+
"specified tags.") do |t|
|
24
24
|
@options[:tags] << t
|
25
25
|
end
|
26
|
-
opts.on(
|
27
|
-
|
26
|
+
opts.on("-l", "--list",
|
27
|
+
"List all enabled rules and their descriptions.") do |t|
|
28
28
|
@options[:list] = t
|
29
|
-
end
|
30
|
-
opts.on(
|
29
|
+
end
|
30
|
+
opts.on("-f", "--epic-fail TAGS",
|
31
31
|
"Fail the build based on tags. Use 'any' to fail "\
|
32
|
-
|
32
|
+
"on all warnings.") do |t|
|
33
33
|
@options[:fail_tags] << t
|
34
34
|
end
|
35
|
-
opts.on(
|
36
|
-
|
37
|
-
|
35
|
+
opts.on("-c", "--chef-version VERSION",
|
36
|
+
"Only check against rules valid for this version "\
|
37
|
+
"of Chef.") do |c|
|
38
38
|
@options[:chef_version] = c
|
39
39
|
end
|
40
|
-
opts.on(
|
41
|
-
|
40
|
+
opts.on("-B", "--cookbook-path PATH",
|
41
|
+
"Cookbook path(s) to check.") do |b|
|
42
42
|
@options[:cookbook_paths] << b
|
43
43
|
end
|
44
|
-
opts.on(
|
45
|
-
|
46
|
-
|
44
|
+
opts.on("-C", "--[no-]context",
|
45
|
+
"Show lines matched against rather than the "\
|
46
|
+
"default summary.") do |c|
|
47
47
|
@options[:context] = c
|
48
48
|
end
|
49
|
-
opts.on(
|
50
|
-
|
49
|
+
opts.on("-E", "--environment-path PATH",
|
50
|
+
"Environment path(s) to check.") do |e|
|
51
51
|
@options[:environment_paths] << e
|
52
52
|
end
|
53
|
-
opts.on(
|
54
|
-
|
53
|
+
opts.on("-I", "--include PATH",
|
54
|
+
"Additional rule file path(s) to load.") do |i|
|
55
55
|
@options[:include_rules] << i
|
56
56
|
end
|
57
|
-
opts.on(
|
58
|
-
|
59
|
-
|
57
|
+
opts.on("-G", "--search-gems",
|
58
|
+
"Search rubygems for rule files with the path "\
|
59
|
+
"foodcritic/rules/**/*.rb") do |g|
|
60
60
|
@options[:search_gems] = true
|
61
61
|
end
|
62
62
|
opts.on("-P", "--progress",
|
63
63
|
"Show progress of files being checked") do
|
64
64
|
@options[:progress] = true
|
65
65
|
end
|
66
|
-
opts.on(
|
67
|
-
|
66
|
+
opts.on("-R", "--role-path PATH",
|
67
|
+
"Role path(s) to check.") do |r|
|
68
68
|
@options[:role_paths] << r
|
69
69
|
end
|
70
|
-
opts.on(
|
71
|
-
|
70
|
+
opts.on("-S", "--search-grammar PATH",
|
71
|
+
"Specify grammar to use when validating search syntax.") do |s|
|
72
72
|
@options[:search_grammar] = s
|
73
73
|
end
|
74
|
-
opts.on(
|
75
|
-
|
74
|
+
opts.on("-V", "--version",
|
75
|
+
"Display the foodcritic version.") do |v|
|
76
76
|
@options[:version] = true
|
77
77
|
end
|
78
|
-
opts.on(
|
79
|
-
|
78
|
+
opts.on("-X", "--exclude PATH",
|
79
|
+
"Exclude path(s) from being linted.") do |e|
|
80
80
|
options[:exclude_paths] << e
|
81
81
|
end
|
82
82
|
end
|
83
83
|
# -v is not implemented but OptionParser gives the Foodcritic's version
|
84
84
|
# if that flag is passed
|
85
|
-
if args.include?
|
85
|
+
if args.include? "-v"
|
86
86
|
help
|
87
87
|
else
|
88
88
|
begin
|
@@ -97,7 +97,7 @@ module FoodCritic
|
|
97
97
|
#
|
98
98
|
# @return [Boolean] True if help should be shown.
|
99
99
|
def show_help?
|
100
|
-
@args.length == 1 && @args.first ==
|
100
|
+
@args.length == 1 && @args.first == "--help"
|
101
101
|
end
|
102
102
|
|
103
103
|
# The help text.
|
data/lib/foodcritic/domain.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "cucumber/core/gherkin/tag_expression"
|
2
2
|
|
3
3
|
module FoodCritic
|
4
4
|
# A warning of a possible issue
|
@@ -33,7 +33,7 @@ module FoodCritic
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def to_s
|
36
|
-
@rules.sort { |a,b| a.code <=> b.code }.
|
36
|
+
@rules.sort { |a, b| a.code <=> b.code }.
|
37
37
|
map { |r| r.to_s }.join("\n")
|
38
38
|
end
|
39
39
|
|
@@ -69,7 +69,7 @@ module FoodCritic
|
|
69
69
|
w.match[:line].to_i]
|
70
70
|
end.sort do |x, y|
|
71
71
|
x.first == y.first ? x[1] <=> y[1] : x.first <=> y.first
|
72
|
-
end.map { |w|"#{w.first}:#{w[1]}" }.uniq.join("\n")
|
72
|
+
end.map { |w| "#{w.first}:#{w[1]}" }.uniq.join("\n")
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
@@ -90,7 +90,7 @@ module FoodCritic
|
|
90
90
|
# The tags associated with this rule. Rule is always tagged with the tag
|
91
91
|
# `any` and the rule code.
|
92
92
|
def tags
|
93
|
-
[
|
93
|
+
["any"] + @tags
|
94
94
|
end
|
95
95
|
|
96
96
|
# Checks the rule tags to see if they match a Gherkin (Cucumber) expression
|
data/lib/foodcritic/dsl.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "pathname"
|
2
2
|
|
3
3
|
module FoodCritic
|
4
4
|
# The DSL methods exposed for defining rules. A minimal example rule:
|
@@ -59,7 +59,6 @@ module FoodCritic
|
|
59
59
|
# `recipe` rule blocks are also evaluated against providers.
|
60
60
|
rule_block :recipe
|
61
61
|
|
62
|
-
|
63
62
|
rule_block :cookbook
|
64
63
|
rule_block :metadata
|
65
64
|
rule_block :resource
|
data/lib/foodcritic/linter.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "optparse"
|
2
|
+
require "ripper"
|
3
|
+
require "set"
|
4
4
|
|
5
5
|
module FoodCritic
|
6
6
|
# The main entry point for linting your Chef cookbooks.
|
@@ -9,7 +9,7 @@ module FoodCritic
|
|
9
9
|
|
10
10
|
# The default version that will be used to determine relevant rules. This
|
11
11
|
# can be over-ridden at the command line with the `--chef-version` option.
|
12
|
-
DEFAULT_CHEF_VERSION =
|
12
|
+
DEFAULT_CHEF_VERSION = "12.13.37"
|
13
13
|
attr_reader :chef_version
|
14
14
|
|
15
15
|
# Perform a lint check. This method is intended for use by the command-line
|
@@ -51,7 +51,6 @@ module FoodCritic
|
|
51
51
|
RuleList.new(@rules)
|
52
52
|
end
|
53
53
|
|
54
|
-
|
55
54
|
# Review the cookbooks at the provided path, identifying potential
|
56
55
|
# improvements.
|
57
56
|
#
|
@@ -88,7 +87,7 @@ module FoodCritic
|
|
88
87
|
cookbook_tags(p[:filename])
|
89
88
|
end
|
90
89
|
|
91
|
-
progress =
|
90
|
+
progress = "."
|
92
91
|
|
93
92
|
active_rules(relevant_tags).each do |rule|
|
94
93
|
state = {
|
@@ -96,7 +95,7 @@ module FoodCritic
|
|
96
95
|
file: p[:filename],
|
97
96
|
ast: read_ast(p[:filename]),
|
98
97
|
rule: rule,
|
99
|
-
last_dir: last_dir
|
98
|
+
last_dir: last_dir,
|
100
99
|
}
|
101
100
|
|
102
101
|
matches = if p[:path_type] == :cookbook
|
@@ -107,7 +106,7 @@ module FoodCritic
|
|
107
106
|
|
108
107
|
matches = remove_ignored(matches, state[:rule], state[:file])
|
109
108
|
|
110
|
-
progress =
|
109
|
+
progress = "x" if matches.any?
|
111
110
|
|
112
111
|
# Convert the matches into warnings
|
113
112
|
matches.each do |match|
|
@@ -123,7 +122,7 @@ module FoodCritic
|
|
123
122
|
last_dir = cookbook_dir(p[:filename])
|
124
123
|
end
|
125
124
|
|
126
|
-
puts
|
125
|
+
puts "" if options[:progress]
|
127
126
|
|
128
127
|
Review.new(paths, warnings)
|
129
128
|
end
|
@@ -137,7 +136,7 @@ module FoodCritic
|
|
137
136
|
end
|
138
137
|
|
139
138
|
per_cookbook_rules(state[:last_dir], state[:file]) do
|
140
|
-
if File.basename(state[:file]) ==
|
139
|
+
if File.basename(state[:file]) == "metadata.rb"
|
141
140
|
cbk_matches += matches(
|
142
141
|
state[:rule].metadata, state[:ast], state[:file])
|
143
142
|
end
|
@@ -158,7 +157,7 @@ module FoodCritic
|
|
158
157
|
end
|
159
158
|
|
160
159
|
def load_rules!(options)
|
161
|
-
rule_files = [File.join(File.dirname(__FILE__),
|
160
|
+
rule_files = [File.join(File.dirname(__FILE__), "rules.rb")]
|
162
161
|
rule_files << options[:include_rules]
|
163
162
|
rule_files << rule_files_in_gems if options[:search_gems]
|
164
163
|
@rules = RuleDsl.load(rule_files.flatten.compact, chef_version)
|
@@ -168,7 +167,7 @@ module FoodCritic
|
|
168
167
|
|
169
168
|
def rule_files_in_gems
|
170
169
|
Gem::Specification.latest_specs(true).map do |spec|
|
171
|
-
spec.matches_for_glob(
|
170
|
+
spec.matches_for_glob("foodcritic/rules/**/*.rb")
|
172
171
|
end.flatten
|
173
172
|
end
|
174
173
|
|
@@ -176,14 +175,14 @@ module FoodCritic
|
|
176
175
|
matches.reject do |m|
|
177
176
|
matched_file = m[:filename] || file
|
178
177
|
(line = m[:line]) && File.exist?(matched_file) &&
|
179
|
-
|
180
|
-
|
178
|
+
!File.directory?(matched_file) &&
|
179
|
+
ignore_line_match?(File.readlines(matched_file)[line - 1], rule)
|
181
180
|
end
|
182
181
|
end
|
183
182
|
|
184
183
|
def ignore_line_match?(line, rule)
|
185
184
|
ignores = line.to_s[/\s+#\s*(.*)/, 1]
|
186
|
-
if ignores && ignores.include?(
|
185
|
+
if ignores && ignores.include?("~")
|
187
186
|
!rule.matches_tags?(ignores.split(/[ ,]/))
|
188
187
|
else
|
189
188
|
false
|
@@ -220,21 +219,21 @@ module FoodCritic
|
|
220
219
|
def cookbook_dir(file)
|
221
220
|
Pathname.new(File.join(File.dirname(file),
|
222
221
|
case File.basename(file)
|
223
|
-
when
|
224
|
-
when /\.erb$/ then
|
225
|
-
else
|
222
|
+
when "metadata.rb" then ""
|
223
|
+
when /\.erb$/ then "../.."
|
224
|
+
else ".."
|
226
225
|
end)).cleanpath
|
227
226
|
end
|
228
227
|
|
229
228
|
def dsl_method_for_file(file)
|
230
229
|
dir_mapping = {
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
230
|
+
"attributes" => :attributes,
|
231
|
+
"libraries" => :library,
|
232
|
+
"providers" => :provider,
|
233
|
+
"resources" => :resource,
|
234
|
+
"templates" => :template,
|
236
235
|
}
|
237
|
-
if file.end_with?
|
236
|
+
if file.end_with? ".erb"
|
238
237
|
dir_mapping[File.basename(File.dirname(File.dirname(file)))]
|
239
238
|
else
|
240
239
|
dir_mapping[File.basename(File.dirname(file))]
|
@@ -250,16 +249,16 @@ module FoodCritic
|
|
250
249
|
|
251
250
|
unless paths[:exclude].empty?
|
252
251
|
exclusions = Dir.glob(paths[:exclude].map do |p|
|
253
|
-
File.join(dir, p,
|
252
|
+
File.join(dir, p, "**/**")
|
254
253
|
end)
|
255
254
|
end
|
256
255
|
|
257
256
|
if File.directory?(dir)
|
258
257
|
glob = if path_type == :cookbook
|
259
|
-
|
260
|
-
|
258
|
+
"{metadata.rb,{attributes,definitions,libraries,"\
|
259
|
+
"providers,recipes,resources}/*.rb,templates/*/*.erb}"
|
261
260
|
else
|
262
|
-
|
261
|
+
"*.rb"
|
263
262
|
end
|
264
263
|
|
265
264
|
(Dir.glob(File.join(dir, glob)) +
|
@@ -297,21 +296,21 @@ module FoodCritic
|
|
297
296
|
|
298
297
|
def specified_paths!(options)
|
299
298
|
paths = Hash[options.map do |key, value|
|
300
|
-
[key, Array(value)] if key.to_s.end_with?(
|
299
|
+
[key, Array(value)] if key.to_s.end_with?("paths")
|
301
300
|
end.compact]
|
302
301
|
|
303
302
|
unless paths.find { |k, v| k != :exclude_paths && !v.empty? }
|
304
|
-
|
303
|
+
raise ArgumentError, "A cookbook path or role path must be specified"
|
305
304
|
end
|
306
305
|
|
307
306
|
Hash[paths.map do |key, value|
|
308
|
-
[key.to_s.sub(/_paths$/,
|
307
|
+
[key.to_s.sub(/_paths$/, "").to_sym, value]
|
309
308
|
end]
|
310
309
|
end
|
311
310
|
|
312
311
|
def setup_defaults(options)
|
313
312
|
{ tags: [], fail_tags: [], include_rules: [], exclude_paths: [],
|
314
|
-
|
313
|
+
cookbook_paths: [], role_paths: [] }.merge(options)
|
315
314
|
end
|
316
315
|
end
|
317
316
|
end
|
@@ -51,7 +51,7 @@ module FoodCritic
|
|
51
51
|
action: notification_action(notify),
|
52
52
|
|
53
53
|
# The notification timing: `:before`, `:immediate` or `:delayed`.
|
54
|
-
timing: notification_timing(notify)
|
54
|
+
timing: notification_timing(notify),
|
55
55
|
}
|
56
56
|
)
|
57
57
|
end.compact
|
@@ -80,11 +80,11 @@ module FoodCritic
|
|
80
80
|
# Normally the `resource_name` will be a simple string. However in the
|
81
81
|
# case where it has an embedded sub-expression then we will return the
|
82
82
|
# AST to the caller to handle.
|
83
|
-
if notify.xpath(
|
83
|
+
if notify.xpath("descendant::string_embexpr").empty?
|
84
84
|
return nil if resource_name.empty?
|
85
85
|
else
|
86
86
|
resource_name =
|
87
|
-
notify.xpath(
|
87
|
+
notify.xpath("args_add_block/args_add/string_literal")
|
88
88
|
end
|
89
89
|
{ resource_name: resource_name, resource_type: resource_type }
|
90
90
|
end
|
@@ -93,7 +93,7 @@ module FoodCritic
|
|
93
93
|
# notification.
|
94
94
|
def old_style_notification(notify)
|
95
95
|
resources = resource_hash_references(notify)
|
96
|
-
resource_type = resources.xpath(
|
96
|
+
resource_type = resources.xpath("symbol[1]/ident/@value").to_s.to_sym
|
97
97
|
resource_name = resources.xpath('string_add[1][count(../
|
98
98
|
descendant::string_add) = 1]/tstring_content/@value').to_s
|
99
99
|
resource_name = resources if resource_name.empty?
|
@@ -136,7 +136,7 @@ module FoodCritic
|
|
136
136
|
end
|
137
137
|
|
138
138
|
def notification_type(notify)
|
139
|
-
notify.xpath(
|
139
|
+
notify.xpath("ident/@value[1] | fcall/ident/@value[1]").to_s.to_sym
|
140
140
|
end
|
141
141
|
|
142
142
|
def resource_hash_references(ast)
|