leftovers 0.4.1 → 0.5.1

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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +29 -0
  3. data/README.md +30 -0
  4. data/docs/Configuration.md +46 -11
  5. data/leftovers.gemspec +1 -0
  6. data/lib/config/actioncable.yml +4 -0
  7. data/lib/config/actionmailer.yml +22 -0
  8. data/lib/config/actionpack.yml +190 -0
  9. data/lib/config/actionview.yml +64 -0
  10. data/lib/config/activejob.yml +29 -0
  11. data/lib/config/activemodel.yml +74 -0
  12. data/lib/config/activerecord.yml +179 -0
  13. data/lib/config/activesupport.yml +98 -0
  14. data/lib/config/haml.yml +2 -0
  15. data/lib/config/rails.yml +11 -450
  16. data/lib/config/ruby.yml +6 -0
  17. data/lib/leftovers/ast/node.rb +14 -0
  18. data/lib/leftovers/cli.rb +13 -4
  19. data/lib/leftovers/collector.rb +2 -1
  20. data/lib/leftovers/config.rb +8 -0
  21. data/lib/leftovers/config_validator/schema_hash.rb +50 -5
  22. data/lib/leftovers/definition.rb +4 -4
  23. data/lib/leftovers/erb.rb +2 -2
  24. data/lib/leftovers/file.rb +2 -3
  25. data/lib/leftovers/matcher_builders/node.rb +2 -0
  26. data/lib/leftovers/matcher_builders/node_has_argument.rb +11 -7
  27. data/lib/leftovers/matcher_builders/node_has_keyword_argument.rb +14 -10
  28. data/lib/leftovers/matcher_builders/node_has_positional_argument.rb +17 -13
  29. data/lib/leftovers/matcher_builders/node_has_receiver.rb +15 -0
  30. data/lib/leftovers/matcher_builders/node_type.rb +7 -6
  31. data/lib/leftovers/matcher_builders/node_value.rb +50 -0
  32. data/lib/leftovers/matcher_builders.rb +3 -2
  33. data/lib/leftovers/matchers/node_has_any_positional_argument_with_value.rb +4 -1
  34. data/lib/leftovers/matchers/node_has_positional_argument.rb +0 -4
  35. data/lib/leftovers/matchers/node_has_receiver.rb +20 -0
  36. data/lib/leftovers/matchers/predicate.rb +19 -0
  37. data/lib/leftovers/matchers.rb +2 -0
  38. data/lib/leftovers/merged_config.rb +24 -1
  39. data/lib/leftovers/reporter.rb +56 -4
  40. data/lib/leftovers/todo_reporter.rb +127 -0
  41. data/lib/leftovers/version.rb +1 -1
  42. data/lib/leftovers.rb +93 -96
  43. metadata +31 -4
  44. data/lib/leftovers/matcher_builders/argument_node_value.rb +0 -21
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Leftovers
4
4
  class Definition
5
- attr_reader :name, :test, :location_s
5
+ attr_reader :name, :test, :location_s, :source_line
6
6
  alias_method :names, :name
7
7
 
8
8
  alias_method :test?, :test
@@ -14,7 +14,7 @@ module Leftovers
14
14
  )
15
15
  @name = name
16
16
  @path = location.source_buffer.name.to_s
17
- @location_source_line = location.source_line.to_s
17
+ @source_line = location.source_line.to_s
18
18
  @location_column_range_begin = location.column_range.begin.to_i
19
19
  @location_column_range_end = location.column_range.end.to_i
20
20
  @location_source = location.source.to_s
@@ -29,9 +29,9 @@ module Leftovers
29
29
  end
30
30
 
31
31
  def highlighted_source(highlight = "\e[31m", normal = "\e[0m")
32
- @location_source_line[0...@location_column_range_begin].lstrip +
32
+ @source_line[0...@location_column_range_begin].lstrip +
33
33
  highlight + @location_source + normal +
34
- @location_source_line[@location_column_range_end..-1].rstrip
34
+ @source_line[@location_column_range_end..-1].rstrip
35
35
  end
36
36
 
37
37
  def in_collection?
data/lib/leftovers/erb.rb CHANGED
@@ -10,11 +10,11 @@ module Leftovers
10
10
  end
11
11
 
12
12
  def add_insert_cmd(out, content) # leftovers:keep
13
- out.push("#{content}\n")
13
+ out.push("\n#{content}\n")
14
14
  end
15
15
 
16
16
  def add_put_cmd(out, _content) # leftovers:keep
17
- out
17
+ out.push("\n")
18
18
  end
19
19
  end
20
20
  end
@@ -15,10 +15,9 @@ module Leftovers
15
15
  end
16
16
 
17
17
  def ruby
18
- case extname
19
- when '.haml'
18
+ if Leftovers.config.haml_paths.allowed?(relative_path)
20
19
  ::Leftovers::Haml.precompile(read, self)
21
- when '.rhtml', '.rjs', '.erb'
20
+ elsif Leftovers.config.erb_paths.allowed?(relative_path)
22
21
  ::Leftovers::ERB.precompile(read)
23
22
  else
24
23
  read
@@ -21,6 +21,7 @@ module Leftovers
21
21
  names: nil, match: nil, has_prefix: nil, has_suffix: nil,
22
22
  paths: nil,
23
23
  has_arguments: nil,
24
+ has_receiver: nil,
24
25
  unless_arg: nil
25
26
  )
26
27
  ::Leftovers::MatcherBuilders::And.build([
@@ -30,6 +31,7 @@ module Leftovers
30
31
  ]),
31
32
  ::Leftovers::MatcherBuilders::NodePath.build(paths),
32
33
  ::Leftovers::MatcherBuilders::NodeHasArgument.build(has_arguments),
34
+ ::Leftovers::MatcherBuilders::NodeHasReceiver.build(has_receiver),
33
35
  ::Leftovers::MatcherBuilders::Unless.build(
34
36
  (::Leftovers::MatcherBuilders::Node.build(unless_arg) if unless_arg)
35
37
  )
@@ -26,6 +26,8 @@ module Leftovers
26
26
 
27
27
  ::Leftovers.each_or_self(at) do |k|
28
28
  case k
29
+ when '*'
30
+ positions << k
29
31
  when ::String, ::Hash
30
32
  keys << k
31
33
  when ::Integer
@@ -35,23 +37,25 @@ module Leftovers
35
37
  # :nocov:
36
38
  end
37
39
  end
40
+
38
41
  keys = nil if keys.empty?
39
42
  positions = nil if positions.empty?
40
43
 
41
44
  [keys, positions]
42
45
  end
43
46
 
44
- def self.build_from_hash(at: nil, has_value: nil, has_value_type: nil, unless_arg: nil) # rubocop:disable Metrics/MethodLength
47
+ def self.build_from_hash( # rubocop:disable Metrics/MethodLength
48
+ at: nil,
49
+ has_value: nil,
50
+ unless_arg: nil
51
+ )
45
52
  keys, positions = separate_argument_types(at)
46
53
 
47
- value_matcher = ::Leftovers::MatcherBuilders::And.build([
48
- ::Leftovers::MatcherBuilders::ArgumentNodeValue.build(has_value),
49
- ::Leftovers::MatcherBuilders::NodeType.build(has_value_type)
50
- ])
54
+ value_matcher = ::Leftovers::MatcherBuilders::NodeValue.build(has_value)
51
55
  matcher = if (keys && positions) || (!keys && !positions)
52
56
  ::Leftovers::MatcherBuilders::Or.build([
53
- ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher),
54
- ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(positions, value_matcher)
57
+ ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(positions, value_matcher),
58
+ ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher)
55
59
  ])
56
60
  elsif keys
57
61
  ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher)
@@ -3,19 +3,23 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasKeywordArgument
6
- def self.build(keywords, value_matcher)
7
- value_matcher = ::Leftovers::MatcherBuilders::NodePairValue.build(value_matcher)
8
- keyword_matcher = ::Leftovers::MatcherBuilders::NodePairName.build(keywords)
6
+ class << self
7
+ def build(keywords, value_matcher) # rubocop:disable Metrics/MethodLength
8
+ value_matcher = ::Leftovers::MatcherBuilders::NodePairValue.build(value_matcher)
9
+ keyword_matcher = if ::Leftovers.each_or_self(keywords).any? { |x| x == '**' }
10
+ ::Leftovers::Matchers::NodeType.new(:pair)
11
+ else
12
+ ::Leftovers::MatcherBuilders::NodePairName.build(keywords)
13
+ end
9
14
 
10
- pair_matcher = ::Leftovers::MatcherBuilders::And.build([
11
- keyword_matcher, value_matcher
12
- ])
13
- # :nocov:
14
- raise unless pair_matcher
15
+ pair_matcher = ::Leftovers::MatcherBuilders::And.build([
16
+ keyword_matcher, value_matcher
17
+ ])
15
18
 
16
- # :nocov:
19
+ return nil unless pair_matcher
17
20
 
18
- ::Leftovers::Matchers::NodeHasAnyKeywordArgument.new(pair_matcher)
21
+ ::Leftovers::Matchers::NodeHasAnyKeywordArgument.new(pair_matcher)
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -3,20 +3,24 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasPositionalArgument
6
- def self.build(positions, value_matcher) # rubocop:disable Metrics/MethodLength
7
- if positions && value_matcher
8
- ::Leftovers::MatcherBuilders::Or.each_or_self(positions) do |position|
9
- ::Leftovers::Matchers::NodeHasPositionalArgumentWithValue.new(position, value_matcher)
10
- end
11
- elsif positions
12
- position = positions.is_a?(Array) ? positions.min : positions
6
+ class << self
7
+ def build(positions, value_matcher) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/MethodLength
8
+ if positions && value_matcher
9
+ ::Leftovers::MatcherBuilders::Or.each_or_self(positions) do |pos|
10
+ if pos == '*'
11
+ ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
12
+ else
13
+ ::Leftovers::Matchers::NodeHasPositionalArgumentWithValue.new(pos, value_matcher)
14
+ end
15
+ end
16
+ elsif positions
17
+ pos = 0 if ::Leftovers.each_or_self(positions).any? { |x| x == '*' }
18
+ pos ||= ::Leftovers.each_or_self(positions).min
13
19
 
14
- ::Leftovers::Matchers::NodeHasPositionalArgument.new(position)
15
- elsif value_matcher
16
- ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
17
- # :nocov:
18
- else raise
19
- # :nocov:
20
+ ::Leftovers::Matchers::NodeHasPositionalArgument.new(pos)
21
+ elsif value_matcher
22
+ ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
23
+ end
20
24
  end
21
25
  end
22
26
  end
@@ -0,0 +1,15 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Leftovers
4
+ module MatcherBuilders
5
+ module NodeHasReceiver
6
+ class << self
7
+ def build(pattern)
8
+ matcher = ::Leftovers::MatcherBuilders::NodeValue.build(pattern)
9
+
10
+ ::Leftovers::Matchers::NodeHasReceiver.new(matcher) if matcher
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,18 +5,19 @@ require 'set'
5
5
  module Leftovers
6
6
  module MatcherBuilders
7
7
  module NodeType
8
- def self.build(types_pattern)
8
+ def self.build(types_pattern) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
9
9
  ::Leftovers::MatcherBuilders::Or.each_or_self(types_pattern) do |type|
10
10
  case type
11
11
  when 'Symbol' then ::Leftovers::Matchers::NodeType.new(:sym)
12
12
  when 'String' then ::Leftovers::Matchers::NodeType.new(:str)
13
13
  when 'Integer' then ::Leftovers::Matchers::NodeType.new(:int)
14
14
  when 'Float' then ::Leftovers::Matchers::NodeType.new(:float)
15
- # these would be neat but i can't think of a use-case
16
- # when 'Array' then :array
17
- # when 'Hash' then :hash
18
- # when 'Method' then Set[:send, :csend, :def]
19
- # when 'Constant' then Set[:const, :class, :module]
15
+ when 'Array' then ::Leftovers::Matchers::NodeType.new(:array)
16
+ when 'Hash' then ::Leftovers::Matchers::NodeType.new(:hash)
17
+
18
+ when 'Proc' then ::Leftovers::Matchers::Predicate.new(:proc?)
19
+ # when 'Method' then ::Leftovers::Matchers::NodeType.new(Set[:send, :csend, :def])
20
+ # when 'Constant' then ::Leftovers::Matchers::NodeType.new(Set[:const, :class, :module])
20
21
  # :nocov:
21
22
  else raise
22
23
  # :nocov:
@@ -0,0 +1,50 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Leftovers
4
+ module MatcherBuilders
5
+ module NodeValue
6
+ class << self
7
+ def build(pattern) # rubocop:disable Metrics/MethodLength
8
+ ::Leftovers::MatcherBuilders::Or.each_or_self(pattern) do |pat|
9
+ case pat
10
+ when ::Integer, true, false, nil
11
+ ::Leftovers::Matchers::NodeScalarValue.new(pat)
12
+ when ::String
13
+ ::Leftovers::MatcherBuilders::NodeName.build(pat)
14
+ when ::Hash
15
+ build_from_hash(**pat)
16
+ # :nocov:
17
+ else raise
18
+ # :nocov:
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def build_from_hash( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
26
+ at: nil, has_value: nil,
27
+ match: nil, has_prefix: nil, has_suffix: nil,
28
+ type: nil,
29
+ has_receiver: nil,
30
+ unless_arg: nil
31
+ )
32
+ matcher = ::Leftovers::MatcherBuilders::And.build([
33
+ ::Leftovers::MatcherBuilders::NodeHasArgument.build(
34
+ at: at, has_value: has_value
35
+ ),
36
+ ::Leftovers::MatcherBuilders::NodeName.build(
37
+ match: match, has_prefix: has_prefix, has_suffix: has_suffix
38
+ ),
39
+ ::Leftovers::MatcherBuilders::NodeType.build(type),
40
+ ::Leftovers::MatcherBuilders::NodeHasReceiver.build(has_receiver)
41
+ ])
42
+
43
+ ::Leftovers::MatcherBuilders::AndNot.build(
44
+ matcher, ::Leftovers::MatcherBuilders::NodeValue.build(unless_arg)
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -4,17 +4,18 @@ module Leftovers
4
4
  module MatcherBuilders
5
5
  autoload(:AndNot, "#{__dir__}/matcher_builders/and_not")
6
6
  autoload(:And, "#{__dir__}/matcher_builders/and")
7
- autoload(:ArgumentNodeValue, "#{__dir__}/matcher_builders/argument_node_value")
8
7
  autoload(:Name, "#{__dir__}/matcher_builders/name")
8
+ autoload(:Node, "#{__dir__}/matcher_builders/node")
9
9
  autoload(:NodeHasArgument, "#{__dir__}/matcher_builders/node_has_argument")
10
10
  autoload(:NodeHasKeywordArgument, "#{__dir__}/matcher_builders/node_has_keyword_argument")
11
11
  autoload(:NodeHasPositionalArgument, "#{__dir__}/matcher_builders/node_has_positional_argument")
12
+ autoload(:NodeHasReceiver, "#{__dir__}/matcher_builders/node_has_receiver")
12
13
  autoload(:NodeName, "#{__dir__}/matcher_builders/node_name")
13
14
  autoload(:NodePairName, "#{__dir__}/matcher_builders/node_pair_name")
14
15
  autoload(:NodePairValue, "#{__dir__}/matcher_builders/node_pair_value")
15
16
  autoload(:NodePath, "#{__dir__}/matcher_builders/node_path")
16
17
  autoload(:NodeType, "#{__dir__}/matcher_builders/node_type")
17
- autoload(:Node, "#{__dir__}/matcher_builders/node")
18
+ autoload(:NodeValue, "#{__dir__}/matcher_builders/node_value")
18
19
  autoload(:Or, "#{__dir__}/matcher_builders/or")
19
20
  autoload(:Path, "#{__dir__}/matcher_builders/path")
20
21
  autoload(:StringPattern, "#{__dir__}/matcher_builders/string_pattern")
@@ -14,7 +14,10 @@ module Leftovers
14
14
  end
15
15
 
16
16
  def ===(node)
17
- node.positional_arguments.any? do |value|
17
+ args = node.positional_arguments
18
+ return false unless args
19
+
20
+ args.any? do |value|
18
21
  @matcher === value
19
22
  end
20
23
  end
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodeHasPositionalArgument
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(position)
11
7
  @position = position
12
8
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ module Matchers
5
+ class NodeHasReceiver
6
+ def initialize(matcher)
7
+ @matcher = matcher
8
+
9
+ freeze
10
+ end
11
+
12
+ def ===(node)
13
+ receiver = node.receiver
14
+ @matcher === receiver if receiver
15
+ end
16
+
17
+ freeze
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ module Matchers
5
+ class Predicate
6
+ def initialize(predicate)
7
+ @predicate = predicate
8
+
9
+ freeze
10
+ end
11
+
12
+ def ===(node)
13
+ node.send(@predicate)
14
+ end
15
+
16
+ freeze
17
+ end
18
+ end
19
+ end
@@ -15,6 +15,7 @@ module Leftovers
15
15
  "#{__dir__}/matchers/node_has_positional_argument_with_value"
16
16
  )
17
17
  autoload(:NodeHasPositionalArgument, "#{__dir__}/matchers/node_has_positional_argument")
18
+ autoload(:NodeHasReceiver, "#{__dir__}/matchers/node_has_receiver")
18
19
  autoload(:NodeName, "#{__dir__}/matchers/node_name")
19
20
  autoload(:NodePairValue, "#{__dir__}/matchers/node_pair_value")
20
21
  autoload(:NodePath, "#{__dir__}/matchers/node_path")
@@ -22,5 +23,6 @@ module Leftovers
22
23
  autoload(:NodeType, "#{__dir__}/matchers/node_type")
23
24
  autoload(:Not, "#{__dir__}/matchers/not")
24
25
  autoload(:Or, "#{__dir__}/matchers/or")
26
+ autoload(:Predicate, "#{__dir__}/matchers/predicate")
25
27
  end
26
28
  end
@@ -12,6 +12,7 @@ module Leftovers
12
12
 
13
13
  self << :ruby
14
14
  self << project_config
15
+ self << project_todo
15
16
  load_bundled_gem_config
16
17
  end
17
18
 
@@ -32,10 +33,16 @@ module Leftovers
32
33
  Leftovers::Config.new(:'.leftovers.yml', path: Leftovers.pwd + '.leftovers.yml')
33
34
  end
34
35
 
35
- def unmemoize
36
+ def project_todo
37
+ Leftovers::Config.new(:'.leftovers_todo.yml', path: Leftovers.pwd + '.leftovers_todo.yml')
38
+ end
39
+
40
+ def unmemoize # rubocop:disable Metrics/CyclomaticComplexity
36
41
  remove_instance_variable(:@exclude_paths) if defined?(@exclude_paths)
37
42
  remove_instance_variable(:@include_paths) if defined?(@include_paths)
38
43
  remove_instance_variable(:@test_paths) if defined?(@test_paths)
44
+ remove_instance_variable(:@haml_paths) if defined?(@haml_paths)
45
+ remove_instance_variable(:@erb_paths) if defined?(@erb_paths)
39
46
  remove_instance_variable(:@dynamic) if defined?(@dynamic)
40
47
  remove_instance_variable(:@keep) if defined?(@keep)
41
48
  end
@@ -56,6 +63,22 @@ module Leftovers
56
63
  )
57
64
  end
58
65
 
66
+ def haml_paths
67
+ @haml_paths ||= FastIgnore.new(
68
+ include_rules: @configs.flat_map(&:haml_paths),
69
+ gitignore: false,
70
+ root: Leftovers.pwd
71
+ )
72
+ end
73
+
74
+ def erb_paths
75
+ @erb_paths ||= FastIgnore.new(
76
+ include_rules: @configs.flat_map(&:erb_paths),
77
+ gitignore: false,
78
+ root: Leftovers.pwd
79
+ )
80
+ end
81
+
59
82
  def dynamic
60
83
  @dynamic ||= ::Leftovers::ProcessorBuilders::EachDynamic.build(@configs.map(&:dynamic))
61
84
  end
@@ -2,10 +2,62 @@
2
2
 
3
3
  module Leftovers
4
4
  class Reporter
5
- def call(definition)
6
- Leftovers.puts(
7
- "\e[36m#{definition.location_s}\e[0m #{definition} \e[2m#{definition.highlighted_source("\e[33m", "\e[0;2m")}\e[0m" # rubocop:disable Layout/LineLength
8
- )
5
+ def prepare; end
6
+
7
+ def report(only_test:, none:)
8
+ report_list('Only directly called in tests:', only_test)
9
+ report_list('Not directly called at all:', none)
10
+ report_instructions
11
+
12
+ 1
13
+ end
14
+
15
+ def report_success
16
+ puts green('Everything is used')
17
+
18
+ 0
19
+ end
20
+
21
+ private
22
+
23
+ def report_instructions
24
+ puts <<~HELP
25
+
26
+ how to resolve: #{green Leftovers.resolution_instructions_link}
27
+ HELP
28
+ end
29
+
30
+ def report_list(title, list)
31
+ return if list.empty?
32
+
33
+ puts red(title)
34
+ list.each { |d| print_definition(d) }
35
+ end
36
+
37
+ def print_definition(definition)
38
+ puts "#{aqua definition.location_s} "\
39
+ "#{definition} "\
40
+ "#{grey definition.highlighted_source("\e[33m", "\e[0;2m")}"
41
+ end
42
+
43
+ def puts(string)
44
+ Leftovers.puts(string)
45
+ end
46
+
47
+ def red(string)
48
+ "\e[31m#{string}\e[0m"
49
+ end
50
+
51
+ def green(string)
52
+ "\e[32m#{string}\e[0m"
53
+ end
54
+
55
+ def aqua(string)
56
+ "\e[36m#{string}\e[0m"
57
+ end
58
+
59
+ def grey(string)
60
+ "\e[2m#{string}\e[0m"
9
61
  end
10
62
  end
11
63
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ module Leftovers
6
+ class TodoReporter
7
+ def prepare
8
+ return unless path.exist?
9
+
10
+ puts "Removing previous #{path.basename} file"
11
+ puts ''
12
+ path.delete
13
+ end
14
+
15
+ def report(only_test:, none:)
16
+ path.write(generate_file_body(only_test, none))
17
+ report_instructions
18
+
19
+ 0
20
+ end
21
+
22
+ def report_success
23
+ puts "No #{path.basename} file generated, everything is used"
24
+
25
+ 0
26
+ end
27
+
28
+ private
29
+
30
+ def report_instructions
31
+ puts <<~MESSAGE
32
+ generated #{path.basename}.
33
+ running leftovers again will read this file and not alert you to any unused items mentioned in it.
34
+
35
+ commit this file so you/your team can gradually address these items while still having leftovers alert you to any newly unused items.
36
+ MESSAGE
37
+ end
38
+
39
+ def path
40
+ Leftovers.pwd.join('.leftovers_todo.yml')
41
+ end
42
+
43
+ def generate_file_body(only_test, none)
44
+ <<~YML.chomp
45
+ #{generation_message.chomp}
46
+ #
47
+ #{resolution_instructions}
48
+ #{todo_data(only_test, none).chomp}
49
+ YML
50
+ end
51
+
52
+ def generation_message
53
+ <<~YML
54
+ # This file was generated by `leftovers --write-todo`
55
+ # Generated at: #{Time.now.utc.strftime('%F %T')} UTC
56
+ YML
57
+ end
58
+
59
+ def resolution_instructions
60
+ <<~YML
61
+ # for instructions on how to address these
62
+ # see #{Leftovers.resolution_instructions_link}
63
+ YML
64
+ end
65
+
66
+ def todo_data(only_test, none)
67
+ none_test = none.select(&:test?)
68
+ none_non_test = none.reject(&:test?)
69
+ [
70
+ test_only_data(none_test),
71
+ keep_data(only_test, none_non_test)
72
+ ].compact.join
73
+ end
74
+
75
+ def test_only_data(list)
76
+ return if list.empty?
77
+
78
+ <<~YML
79
+ test_only:
80
+ #{generate_list('Defined in tests:', list).chomp}
81
+ YML
82
+ end
83
+
84
+ def keep_data(only_test, none_non_test)
85
+ return if only_test.empty? && none_non_test.empty?
86
+
87
+ <<~YML.chomp
88
+ keep:
89
+ #{keep_test_called_data(only_test)}#{keep_never_called_data(none_non_test)}
90
+ YML
91
+ end
92
+
93
+ def keep_test_called_data(list)
94
+ return if list.empty?
95
+
96
+ generate_list('Only directly called in tests:', list)
97
+ end
98
+
99
+ def keep_never_called_data(list)
100
+ return if list.empty?
101
+
102
+ generate_list('Not directly called at all:', list)
103
+ end
104
+
105
+ def generate_list(title, list)
106
+ <<~YML
107
+ # #{title}
108
+ #{print_definition_list(list)}
109
+
110
+ YML
111
+ end
112
+
113
+ def print_definition_list(definition_list)
114
+ definition_list.map { |definition| print_definition(definition) }.join("\n")
115
+ end
116
+
117
+ def print_definition(definition)
118
+ return print_definition_list(definition.definitions) if definition.is_a?(DefinitionSet)
119
+
120
+ " - #{definition.to_s.inspect} # #{definition.location_s} #{definition.source_line.strip}"
121
+ end
122
+
123
+ def puts(string)
124
+ Leftovers.puts(string)
125
+ end
126
+ end
127
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leftovers
4
- VERSION = '0.4.1'
4
+ VERSION = '0.5.1'
5
5
  end