foodcritic 15.1.0 → 16.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/chef_dsl_metadata/{chef_14.8.12.json → chef_14.14.29.json} +394 -169
  4. data/chef_dsl_metadata/{chef_13.12.3.json → chef_15.4.45.json} +15454 -2631
  5. data/foodcritic.gemspec +2 -2
  6. data/lib/foodcritic.rb +2 -1
  7. data/lib/foodcritic/api.rb +55 -42
  8. data/lib/foodcritic/chef.rb +5 -3
  9. data/lib/foodcritic/command_line.rb +78 -52
  10. data/lib/foodcritic/domain.rb +7 -9
  11. data/lib/foodcritic/dsl.rb +4 -1
  12. data/lib/foodcritic/gerkin/tag.rb +55 -0
  13. data/lib/foodcritic/gerkin/tag_expression.rb +66 -0
  14. data/lib/foodcritic/linter.rb +24 -8
  15. data/lib/foodcritic/notifications.rb +3 -1
  16. data/lib/foodcritic/output.rb +1 -1
  17. data/lib/foodcritic/rules/fc001.rb +6 -6
  18. data/lib/foodcritic/rules/fc004.rb +1 -0
  19. data/lib/foodcritic/rules/fc006.rb +9 -9
  20. data/lib/foodcritic/rules/fc007.rb +3 -1
  21. data/lib/foodcritic/rules/fc016.rb +1 -0
  22. data/lib/foodcritic/rules/fc019.rb +7 -6
  23. data/lib/foodcritic/rules/fc022.rb +24 -25
  24. data/lib/foodcritic/rules/fc024.rb +16 -13
  25. data/lib/foodcritic/rules/fc029.rb +1 -0
  26. data/lib/foodcritic/rules/fc031.rb +1 -1
  27. data/lib/foodcritic/rules/fc032.rb +2 -2
  28. data/lib/foodcritic/rules/fc033.rb +2 -2
  29. data/lib/foodcritic/rules/fc034.rb +5 -2
  30. data/lib/foodcritic/rules/fc039.rb +12 -12
  31. data/lib/foodcritic/rules/fc040.rb +1 -1
  32. data/lib/foodcritic/rules/fc044.rb +8 -12
  33. data/lib/foodcritic/rules/fc048.rb +1 -0
  34. data/lib/foodcritic/rules/fc121.rb +6 -10
  35. data/lib/foodcritic/rules/fc123.rb +16 -0
  36. data/lib/foodcritic/template.rb +3 -6
  37. data/lib/foodcritic/version.rb +1 -1
  38. metadata +34 -32
@@ -4,7 +4,7 @@ require "foodcritic/version"
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "foodcritic"
6
6
  s.version = FoodCritic::VERSION
7
- s.description = "Lint tool for Chef cookbooks."
7
+ s.description = "A code linting tool for Chef Infra cookbooks."
8
8
  s.summary = "foodcritic-#{s.version}"
9
9
  s.authors = ["Andrew Crump"]
10
10
  s.homepage = "http://foodcritic.io"
@@ -17,7 +17,6 @@ Gem::Specification.new do |s|
17
17
  Dir["misc/**/*"]
18
18
  s.files += Dir["Gemfile", "foodcritic.gemspec", "LICENSE"]
19
19
 
20
- s.add_dependency("cucumber-core", ">= 1.3", "< 4.0")
21
20
  s.add_dependency("nokogiri", ">= 1.5", "< 2.0")
22
21
  s.add_dependency("rake")
23
22
  s.add_dependency("treetop", "~> 1.4")
@@ -25,6 +24,7 @@ Gem::Specification.new do |s|
25
24
  s.add_dependency("erubis")
26
25
  s.add_dependency("rufus-lru", "~> 1.0")
27
26
 
27
+ s.add_development_dependency "cucumber-core", ">= 1.3", "< 4.0"
28
28
  s.add_development_dependency "rspec", "~> 3.5"
29
29
  s.add_development_dependency "fuubar", "~> 2.0"
30
30
  s.add_development_dependency "rspec-command", "~> 1.0"
@@ -2,7 +2,6 @@ Encoding.default_external = Encoding::UTF_8
2
2
  Encoding.default_internal = Encoding::UTF_8
3
3
 
4
4
  require "pathname"
5
- require "cucumber/core"
6
5
  require "treetop"
7
6
  require "ripper"
8
7
  require "ffi_yajl"
@@ -22,3 +21,5 @@ require_relative "foodcritic/output"
22
21
  require_relative "foodcritic/rake_task"
23
22
  require_relative "foodcritic/template"
24
23
  require_relative "foodcritic/version"
24
+ require_relative "foodcritic/gerkin/tag"
25
+ require_relative "foodcritic/gerkin/tag_expression"
@@ -29,7 +29,7 @@ module FoodCritic
29
29
  options = { type: :any, ignore_calls: false }.merge!(options)
30
30
  return [] unless ast.respond_to?(:xpath)
31
31
 
32
- unless [:any, :string, :symbol, :vivified].include?(options[:type])
32
+ unless %i{any string symbol vivified}.include?(options[:type])
33
33
  raise ArgumentError, "Node type not recognised"
34
34
  end
35
35
 
@@ -56,9 +56,7 @@ module FoodCritic
56
56
 
57
57
  # get list of items in the dir and intersect with metadata array.
58
58
  # until we get an interfact (we have a metadata) walk up the dir structure
59
- until (Dir.entries(file) & %w{metadata.rb metadata.json}).any?
60
- file = File.dirname(file)
61
- end
59
+ file = File.dirname(file) until (Dir.entries(file) & %w{metadata.rb metadata.json}).any?
62
60
 
63
61
  file
64
62
  end
@@ -69,9 +67,7 @@ module FoodCritic
69
67
  # @since 7.0.0
70
68
  # @return [String] the value of the metadata field
71
69
  def metadata_field(file, field)
72
- until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty?
73
- file = File.absolute_path(File.dirname(file.to_s))
74
- end
70
+ file = File.absolute_path(File.dirname(file.to_s)) until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty?
75
71
  file = File.dirname(file) unless File.extname(file).empty?
76
72
 
77
73
  md_path = File.join(file, "metadata.rb")
@@ -80,6 +76,7 @@ module FoodCritic
80
76
  command[ident/@value='#{field}']/
81
77
  descendant::tstring_content/@value").to_s
82
78
  raise "Cant read #{field} from #{md_path}" if value.to_s.empty?
79
+
83
80
  return value
84
81
  else
85
82
  raise "Cant find #{md_path}"
@@ -98,9 +95,7 @@ module FoodCritic
98
95
  begin
99
96
  metadata_field(file, "name")
100
97
  rescue RuntimeError
101
- until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty?
102
- file = File.absolute_path(File.dirname(file.to_s))
103
- end
98
+ file = File.absolute_path(File.dirname(file.to_s)) until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty?
104
99
  file = File.dirname(file) unless File.extname(file).empty?
105
100
  File.basename(file)
106
101
  end
@@ -156,6 +151,7 @@ module FoodCritic
156
151
  if field_name.nil? || field_name.to_s.empty?
157
152
  raise ArgumentError, "Field name cannot be nil or empty"
158
153
  end
154
+
159
155
  ast.xpath("(.//command[ident/@value='#{field_name}']|.//fcall[ident/@value='#{field_name}']/..)")
160
156
  end
161
157
 
@@ -163,13 +159,14 @@ module FoodCritic
163
159
  def field_value(ast, field_name)
164
160
  field(ast, field_name).xpath('.//args_add_block//tstring_content
165
161
  [count(ancestor::args_add) = 1][count(ancestor::string_add) = 1]
166
- /@value').map { |a| a.to_s }.last
162
+ /@value').map(&:to_s).last
167
163
  end
168
164
 
169
165
  # Create a match for a specified file. Use this if the presence of the file
170
166
  # triggers the warning rather than content.
171
167
  def file_match(file)
172
168
  raise ArgumentError, "Filename cannot be nil" if file.nil?
169
+
173
170
  { filename: file, matched: file, line: 1, column: 1 }
174
171
  end
175
172
 
@@ -189,10 +186,11 @@ module FoodCritic
189
186
  def find_resources(ast, options = {})
190
187
  options = { type: :any }.merge!(options)
191
188
  return [] unless ast.respond_to?(:xpath)
189
+
192
190
  scope_type = ""
193
191
  unless options[:type] == :any
194
- type_array = Array(options[:type]).map { |x| "@value='#{x}'" }
195
- scope_type = "[#{type_array.join(' or ')}]"
192
+ type_array = Array(options[:type]).map { |x| "@value='#{x}'" }
193
+ scope_type = "[#{type_array.join(" or ")}]"
196
194
  end
197
195
 
198
196
  # TODO: Include nested resources (provider actions)
@@ -241,6 +239,7 @@ module FoodCritic
241
239
  # Searches with a query formed from a subexpression will be ignored.
242
240
  def literal_searches(ast)
243
241
  return [] unless ast.respond_to?(:xpath)
242
+
244
243
  ast.xpath("//method_add_arg[fcall/ident/@value = 'search' and
245
244
  count(descendant::string_embexpr) = 0]/descendant::tstring_content")
246
245
  end
@@ -250,23 +249,31 @@ module FoodCritic
250
249
  raise_unless_xpath!(node)
251
250
  pos = node.xpath("descendant::pos").first
252
251
  return nil if pos.nil?
252
+
253
253
  { matched: node.respond_to?(:name) ? node.name : "",
254
254
  line: pos["line"].to_i, column: pos["column"].to_i }
255
255
  end
256
256
 
257
+ # Returns a global LRU cache holding the AST of a file
258
+ # @since 16.1
259
+ # @param size [Integer] the size of the cache (will be resized)
260
+ def ast_cache(size = nil)
261
+ @@ast_cache ||= Rufus::Lru::Hash.new(size || 5)
262
+ if size && @@ast_cache.maxsize != size
263
+ @@ast_cache.maxsize = size
264
+ end
265
+ @@ast_cache
266
+ end
267
+
257
268
  # Read the AST for the given Ruby source file
258
269
  def read_ast(file)
259
- @ast_cache ||= Rufus::Lru::Hash.new(5)
260
- if @ast_cache.include?(file)
261
- @ast_cache[file]
262
- else
263
- @ast_cache[file] = uncached_read_ast(file)
264
- end
270
+ ast_cache[file] ||= uncached_read_ast(file)
265
271
  end
266
272
 
267
273
  # Retrieve a single-valued attribute from the specified resource.
268
274
  def resource_attribute(resource, name)
269
275
  raise ArgumentError, "Attribute name cannot be empty" if name.empty?
276
+
270
277
  resource_attributes(resource)[name.to_s]
271
278
  end
272
279
 
@@ -300,7 +307,8 @@ module FoodCritic
300
307
  if name.xpath("descendant::string_add").size == 1 &&
301
308
  name.xpath("descendant::string_literal").size == 1 &&
302
309
  name.xpath(
303
- "descendant::*[self::call or self::string_embexpr]").empty?
310
+ "descendant::*[self::call or self::string_embexpr]"
311
+ ).empty?
304
312
  name.xpath("descendant::tstring_content/@value").to_s
305
313
  else
306
314
  name
@@ -314,7 +322,7 @@ module FoodCritic
314
322
  # Resources in an AST, keyed by type.
315
323
  def resources_by_type(ast)
316
324
  raise_unless_xpath!(ast)
317
- result = Hash.new { |hash, key| hash[key] = Array.new }
325
+ result = Hash.new { |hash, key| hash[key] = [] }
318
326
  find_resources(ast).each do |resource|
319
327
  result[resource_type(resource)] << resource
320
328
  end
@@ -328,6 +336,7 @@ module FoodCritic
328
336
  if type.empty?
329
337
  raise ArgumentError, "Provided AST node is not a resource"
330
338
  end
339
+
331
340
  type
332
341
  end
333
342
 
@@ -344,6 +353,7 @@ module FoodCritic
344
353
  # Searches performed by the provided AST.
345
354
  def searches(ast)
346
355
  return [] unless ast.respond_to?(:xpath)
356
+
347
357
  ast.xpath("//fcall/ident[@value = 'search']")
348
358
  end
349
359
 
@@ -392,9 +402,10 @@ module FoodCritic
392
402
 
393
403
  def templates_included(all_templates, template_path, depth = 1)
394
404
  raise RecursedTooFarError.new(template_path) if depth > 10
405
+
395
406
  partials = read_ast(template_path).xpath('//*[self::command or
396
407
  child::fcall][descendant::ident/@value="render"]//args_add/
397
- string_literal//tstring_content/@value').map { |p| p.to_s }
408
+ string_literal//tstring_content/@value').map(&:to_s)
398
409
  Array(template_path) + partials.map do |included_partial|
399
410
  partial_path = Array(all_templates).find do |path|
400
411
  (Pathname.new(template_path).dirname + included_partial).to_s == path
@@ -410,10 +421,10 @@ module FoodCritic
410
421
  def template_paths(recipe_path)
411
422
  Dir.glob(Pathname.new(recipe_path).dirname.dirname + "templates" +
412
423
  "**/*", File::FNM_DOTMATCH).select do |path|
413
- File.file?(path)
414
- end.reject do |path|
415
- File.basename(path) == ".DS_Store" || File.extname(path) == ".swp"
416
- end
424
+ File.file?(path)
425
+ end.reject do |path|
426
+ File.basename(path) == ".DS_Store" || File.extname(path) == ".swp"
427
+ end
417
428
  end
418
429
 
419
430
  # Give a filename path it returns the hash of the JSON contents
@@ -441,11 +452,11 @@ module FoodCritic
441
452
  atts = {}
442
453
  resource.xpath("do_block/descendant::method_add_block[
443
454
  count(ancestor::do_block) = 1][brace_block | do_block]").each do |batt|
444
- att_name = batt.xpath("string(method_add_arg/fcall/ident/@value)")
445
- if att_name && !att_name.empty? && batt.children.length > 1
446
- atts[att_name] = batt.children[1]
455
+ att_name = batt.xpath("string(method_add_arg/fcall/ident/@value)")
456
+ if att_name && !att_name.empty? && batt.children.length > 1
457
+ atts[att_name] = batt.children[1]
458
+ end
447
459
  end
448
- end
449
460
  atts
450
461
  end
451
462
 
@@ -517,7 +528,7 @@ module FoodCritic
517
528
  # those exist in cookbooks, but are not longer part of chef 14+
518
529
  # this prevents false positives in FC019 anytime node.set is found
519
530
  def node_method?(meth, cookbook_dir)
520
- chef_dsl_methods.include?(meth) || meth == :set || meth == :set_unless ||
531
+ chef_node_methods.include?(meth) || meth == :set || meth == :set_unless ||
521
532
  patched_node_method?(meth, cookbook_dir)
522
533
  end
523
534
 
@@ -525,14 +536,15 @@ module FoodCritic
525
536
  atts = {}
526
537
  resource.xpath('do_block/descendant::*[self::command or
527
538
  self::method_add_arg][count(ancestor::do_block) >= 1]').each do |att|
528
- blocks = att.xpath("ancestor::method_add_block/method_add_arg/fcall")
529
- next if blocks.any? { |a| block_depth(a) > block_depth(resource) }
530
- att_name = att.xpath('string(ident/@value |
531
- fcall/ident[@value="variables"]/@value)')
532
- unless att_name.empty?
533
- atts[att_name] = extract_attribute_value(att, options)
539
+ blocks = att.xpath("ancestor::method_add_block/method_add_arg/fcall")
540
+ next if blocks.any? { |a| block_depth(a) > block_depth(resource) }
541
+
542
+ att_name = att.xpath('string(ident/@value |
543
+ fcall/ident[@value="variables"]/@value)')
544
+ unless att_name.empty?
545
+ atts[att_name] = extract_attribute_value(att, options)
546
+ end
534
547
  end
535
- end
536
548
  atts
537
549
  end
538
550
 
@@ -573,6 +585,7 @@ module FoodCritic
573
585
  class AttFilter
574
586
  def is_att_type(value)
575
587
  return [] unless value.respond_to?(:select)
588
+
576
589
  value.select do |n|
577
590
  %w{
578
591
  automatic_attrs
@@ -599,7 +612,7 @@ module FoodCritic
599
612
 
600
613
  def standard_attribute_access(ast, options)
601
614
  if options[:type] == :any
602
- [:string, :symbol].map do |type|
615
+ %i{string symbol}.map do |type|
603
616
  standard_attribute_access(ast, options.merge(type: type))
604
617
  end.inject(:+)
605
618
  else
@@ -644,13 +657,13 @@ module FoodCritic
644
657
  call.xpath("aref/args_add_block").size == 0 &&
645
658
  (call.xpath("descendant::ident").size > 1 &&
646
659
  !node_method?(call.xpath("ident/@value").to_s.to_sym,
647
- options[:cookbook_dir]))
660
+ options[:cookbook_dir]))
648
661
  end.sort
649
662
  end
650
663
 
651
664
  def word_list_values(ast, xpath = nil)
652
665
  # Find the node for the field argument variable. (e.g. given `foo d`, find `d`)
653
- var_ref = ast.xpath("#{xpath ? xpath + '/' : ''}descendant::var_ref/ident")
666
+ var_ref = ast.xpath("#{xpath ? xpath + "/" : ""}descendant::var_ref/ident")
654
667
  if var_ref.empty?
655
668
  # The field is either a static value, or took no arguments, or something
656
669
  # more complex than we care to evaluate.
@@ -659,7 +672,7 @@ module FoodCritic
659
672
  # Look back out the tree for a method_add_block which contains a block
660
673
  # variable matching the field argument variable, and then drill down to
661
674
  # all the literal content nodes.
662
- ast.xpath(%Q{ancestor::method_add_block[//block_var//ident/@value='#{var_ref.first['value']}']/call//tstring_content})
675
+ ast.xpath(%Q{ancestor::method_add_block[//block_var//ident/@value='#{var_ref.first["value"]}']/call//tstring_content})
663
676
  end
664
677
  end
665
678
  end
@@ -56,12 +56,12 @@ module FoodCritic
56
56
  metadata_path(ver)
57
57
  end.find { |m| File.exist?(m) }
58
58
  @dsl_metadata ||= FFI_Yajl::Parser.parse(IO.read(metadata_path),
59
- symbolize_keys: true)
59
+ symbolize_keys: true)
60
60
  end
61
61
 
62
62
  def metadata_path(chef_version)
63
63
  File.join(File.dirname(__FILE__), "..", "..",
64
- "chef_dsl_metadata/chef_#{chef_version}.json")
64
+ "chef_dsl_metadata/chef_#{chef_version}.json")
65
65
  end
66
66
 
67
67
  def resource_check?(key, resource_type, field)
@@ -94,9 +94,11 @@ module FoodCritic
94
94
  grammar_paths.inject(nil) do |parser, lucene_grammar|
95
95
  begin
96
96
  break parser unless parser.nil?
97
+
97
98
  # Don't instantiate custom nodes
98
99
  Treetop.load_from_string(
99
- IO.read(lucene_grammar).gsub(/<[^>]+>/, ""))
100
+ IO.read(lucene_grammar).gsub(/<[^>]+>/, "")
101
+ )
100
102
  LuceneParser.new
101
103
  rescue
102
104
  # Silently swallow and try the next grammar
@@ -24,74 +24,79 @@ module FoodCritic
24
24
  environment_paths: [],
25
25
  exclude_paths: ["test/**/*", "spec/**/*", "features/**/*"],
26
26
  progress: true,
27
+ ast_cache_size: 5,
27
28
  }
28
29
  @parser = OptionParser.new do |opts|
29
30
  opts.banner = "foodcritic [cookbook_paths]"
30
31
  opts.on("-t", "--tags TAGS",
31
- "Check against (or exclude ~) rules with the "\
32
- "specified tags.") do |t|
33
- @options[:tags] << t
34
- end
32
+ "Check against (or exclude ~) rules with the "\
33
+ "specified tags.") do |t|
34
+ @options[:tags] << t
35
+ end
35
36
  opts.on("-l", "--list",
36
- "List all enabled rules and their descriptions.") do |t|
37
- @options[:list] = t
38
- end
37
+ "List all enabled rules and their descriptions.") do |t|
38
+ @options[:list] = t
39
+ end
39
40
  opts.on("-f", "--epic-fail TAGS",
40
- "Fail the build based on tags. Default of 'any' to fail "\
41
- "on all warnings.") do |t|
42
- @options[:fail_tags] << t
43
- end
41
+ "Fail the build based on tags. Default of 'any' to fail "\
42
+ "on all warnings.") do |t|
43
+ @options[:fail_tags] << t
44
+ end
44
45
  opts.on("-c", "--chef-version VERSION",
45
- "Only check against rules valid for this version "\
46
- "of Chef.") do |c|
47
- @options[:chef_version] = c
48
- end
46
+ "Only check against rules valid for this version "\
47
+ "of Chef.") do |c|
48
+ @options[:chef_version] = c
49
+ end
49
50
  opts.on("-r", "--rule-file PATH",
50
- "Specify file with rules to be used or ignored ") do |c|
51
- @options[:rule_file] = c
52
- end
51
+ "Specify file with rules to be used or ignored ") do |c|
52
+ @options[:rule_file] = c
53
+ end
54
+ opts.on("-s", "--ast-cache-size NUM", Integer,
55
+ "Change the size of the AST cache.") do |s|
56
+ @options[:ast_cache_size] = s
57
+ end
53
58
  opts.on("-B", "--cookbook-path PATH",
54
- "Cookbook path(s) to check.") do |b|
55
- @options[:cookbook_paths] << b
56
- end
59
+ "Cookbook path(s) to check.") do |b|
60
+ @options[:cookbook_paths] << b
61
+ end
57
62
  opts.on("-C", "--[no-]context",
58
- "Show lines matched against rather than the "\
59
- "default summary.") do |c|
60
- @options[:context] = c
61
- end
63
+ "Show lines matched against rather than the "\
64
+ "default summary.") do |c|
65
+ @options[:context] = c
66
+ end
62
67
  opts.on("-E", "--environment-path PATH",
63
- "Environment path(s) to check.") do |e|
64
- @options[:environment_paths] << e
65
- end
68
+ "Environment path(s) to check.") do |e|
69
+ @options[:environment_paths] << e
70
+ end
66
71
  opts.on("-I", "--include PATH",
67
- "Additional rule file path(s) to load.") do |i|
68
- @options[:include_rules] << i
69
- end
72
+ "Additional rule file path(s) to load.") do |i|
73
+ @options[:include_rules] << i
74
+ end
70
75
  opts.on("-G", "--search-gems",
71
- "Search rubygems for rule files with the path "\
72
- "foodcritic/rules/**/*.rb") do |g|
73
- @options[:search_gems] = true
74
- end
76
+ "Search rubygems for rule files with the path "\
77
+ "foodcritic/rules/**/*.rb") do |g|
78
+ @options[:search_gems] = true
79
+ end
75
80
  opts.on("-P", "--[no-]progress",
76
- "Show progress of files being checked. default: true") do |q|
77
- @options[:progress] = q
78
- end
81
+ "Show progress of files being checked. default: true") do |q|
82
+ @options[:progress] = q
83
+ end
79
84
  opts.on("-R", "--role-path PATH",
80
- "Role path(s) to check.") do |r|
81
- @options[:role_paths] << r
82
- end
85
+ "Role path(s) to check.") do |r|
86
+ @options[:role_paths] << r
87
+ end
83
88
  opts.on("-S", "--search-grammar PATH",
84
- "Specify grammar to use when validating search syntax.") do |s|
85
- @options[:search_grammar] = s
86
- end
89
+ "Specify grammar to use when validating search syntax.") do |s|
90
+ @options[:search_grammar] = s
91
+ end
87
92
  opts.on("-V", "--version",
88
- "Display the foodcritic version.") do |v|
89
- @options[:version] = true
90
- end
93
+ "Display the foodcritic version.") do |v|
94
+ @options[:version] = true
95
+ end
91
96
  opts.on("-X", "--exclude PATH",
92
- "Exclude path(s) from being linted. PATH is relative to the cookbook, not an absolute PATH. Default test/**/*,spec/**/*,features/**/*") do |e|
93
- options[:exclude_paths] << e
94
- end
97
+ "Exclude path(s) from being linted. PATH is relative to the cookbook, not an absolute PATH. Default test/**/*,spec/**/*,features/**/*") do |e|
98
+ options[:exclude_paths] << e
99
+ end
95
100
  end
96
101
  # -v is not implemented but OptionParser gives the Foodcritic's version
97
102
  # if that flag is passed
@@ -106,6 +111,26 @@ module FoodCritic
106
111
  end
107
112
  end
108
113
 
114
+ # The end of life message.
115
+ #
116
+ # @return [String] A text to warn end users about foodcritic's end of life
117
+ def show_end_of_life_msg
118
+ "
119
+ *******************************************************************************
120
+ WARNING: Foodcritic will be end of life in April 2020
121
+ *******************************************************************************
122
+ Please use Cookstyle to ensure all of your Chef Infra code meets the latest
123
+ best practices.
124
+
125
+ Cookstyle is a code linting tool that helps you to write better Chef Infra
126
+ Cookbooks by detecting and automatically correct cookbook code.
127
+
128
+ For more information, use the command:
129
+
130
+ cookstyle -h
131
+ "
132
+ end
133
+
109
134
  # Show the command help to the end user?
110
135
  #
111
136
  # @return [Boolean] True if help should be shown.
@@ -113,11 +138,11 @@ module FoodCritic
113
138
  @args.length == 1 && @args.first == "--help"
114
139
  end
115
140
 
116
- # The help text.
141
+ # The help text plus the end of life message.
117
142
  #
118
143
  # @return [String] Help text describing the command-line options available.
119
144
  def help
120
- @parser.help
145
+ @parser.help + show_end_of_life_msg
121
146
  end
122
147
 
123
148
  # Show the current version to the end user?
@@ -157,6 +182,7 @@ module FoodCritic
157
182
  def valid_grammar?
158
183
  return true unless options.key?(:search_grammar)
159
184
  return false unless File.exist?(options[:search_grammar])
185
+
160
186
  search = FoodCritic::Chef::Search.new
161
187
  search.create_parser([options[:search_grammar]])
162
188
  search.parser?