leftovers 0.4.0 → 0.5.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +37 -4
  4. data/docs/Configuration.md +75 -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 +18 -4
  18. data/lib/leftovers/cli.rb +13 -4
  19. data/lib/leftovers/collector.rb +2 -1
  20. data/lib/leftovers/config.rb +12 -0
  21. data/lib/leftovers/config_validator/schema_hash.rb +57 -11
  22. data/lib/leftovers/definition.rb +6 -6
  23. data/lib/leftovers/definition_node.rb +36 -0
  24. data/lib/leftovers/definition_set.rb +15 -8
  25. data/lib/leftovers/file.rb +2 -3
  26. data/lib/leftovers/file_collector.rb +10 -5
  27. data/lib/leftovers/matcher_builders/node.rb +2 -0
  28. data/lib/leftovers/matcher_builders/node_has_argument.rb +11 -7
  29. data/lib/leftovers/matcher_builders/node_has_keyword_argument.rb +14 -10
  30. data/lib/leftovers/matcher_builders/node_has_positional_argument.rb +17 -13
  31. data/lib/leftovers/matcher_builders/node_has_receiver.rb +15 -0
  32. data/lib/leftovers/matcher_builders/node_type.rb +7 -6
  33. data/lib/leftovers/matcher_builders/node_value.rb +42 -0
  34. data/lib/leftovers/matcher_builders.rb +3 -2
  35. data/lib/leftovers/matchers/node_has_any_positional_argument_with_value.rb +4 -1
  36. data/lib/leftovers/matchers/node_has_positional_argument.rb +0 -4
  37. data/lib/leftovers/matchers/node_has_receiver.rb +20 -0
  38. data/lib/leftovers/matchers/predicate.rb +19 -0
  39. data/lib/leftovers/matchers.rb +2 -0
  40. data/lib/leftovers/merged_config.rb +28 -1
  41. data/lib/leftovers/reporter.rb +56 -4
  42. data/lib/leftovers/todo_reporter.rb +127 -0
  43. data/lib/leftovers/value_processors/each_for_definition_set.rb +2 -6
  44. data/lib/leftovers/value_processors/return_definition.rb +5 -3
  45. data/lib/leftovers/version.rb +1 -1
  46. data/lib/leftovers.rb +94 -96
  47. metadata +31 -5
  48. data/lib/leftovers/matcher_builders/argument_node_value.rb +0 -21
@@ -91,7 +91,7 @@ module Leftovers
91
91
  },
92
92
  'valueType' => {
93
93
  'type' => 'string',
94
- 'enum' => %w{String Symbol Integer Float}
94
+ 'enum' => %w{String Symbol Integer Float Array Hash Proc}
95
95
  },
96
96
  'valueTypeList' => {
97
97
  'anyOf' => [
@@ -110,7 +110,41 @@ module Leftovers
110
110
  { 'type' => 'integer' },
111
111
  { 'type' => 'number' },
112
112
  { 'type' => 'boolean' },
113
- { 'type' => 'null' }
113
+ { 'type' => 'null' },
114
+ { 'allOf' => [
115
+ { '$ref' => '#/definitions/stringPattern' },
116
+ {
117
+ 'type' => 'object',
118
+ 'properties' => {
119
+ 'match' => true, 'matches' => true,
120
+ 'has_prefix' => true, 'has_suffix' => true,
121
+ 'at' => { '$ref' => '#/definitions/argumentPositionList' },
122
+ 'has_value' => { '$ref' => '#/definitions/hasValueList' },
123
+ 'has_receiver' => { '$ref' => '#/definitions/hasValueList' },
124
+ 'type' => { '$ref' => '#/definitions/valueTypeList' },
125
+ 'unless' => { '$ref' => '#/definitions/hasValueList' }
126
+ },
127
+ 'minProperties' => 1,
128
+ 'additionalProperties' => false,
129
+ 'allOf' => [
130
+ # incompatible groups
131
+ { 'not' => { 'required' => %w{match at} } },
132
+ { 'not' => { 'required' => %w{match has_value} } },
133
+ { 'not' => { 'required' => %w{match type} } },
134
+ { 'not' => { 'required' => %w{matches at} } },
135
+ { 'not' => { 'required' => %w{matches has_value} } },
136
+ { 'not' => { 'required' => %w{matches type} } },
137
+ { 'not' => { 'required' => %w{has_prefix at} } },
138
+ { 'not' => { 'required' => %w{has_prefix has_value} } },
139
+ { 'not' => { 'required' => %w{has_prefix type} } },
140
+ { 'not' => { 'required' => %w{has_suffix at} } },
141
+ { 'not' => { 'required' => %w{has_suffix has_value} } },
142
+ { 'not' => { 'required' => %w{has_suffix type} } },
143
+ { 'not' => { 'required' => %w{at type} } },
144
+ { 'not' => { 'required' => %w{has_value type} } }
145
+ ]
146
+ }
147
+ ] }
114
148
  ]
115
149
  },
116
150
  'hasValueList' => {
@@ -133,11 +167,14 @@ module Leftovers
133
167
  'properties' => {
134
168
  'at' => { '$ref' => '#/definitions/argumentPositionList' },
135
169
  'has_value' => { '$ref' => '#/definitions/hasValueList' },
136
- 'has_value_type' => { '$ref' => '#/definitions/valueTypeList' },
137
170
  'unless' => { '$ref' => '#/definitions/hasArgumentList' }
138
171
  },
139
172
  'minProperties' => 1,
140
- 'additionalProperties' => false
173
+ 'additionalProperties' => false,
174
+ 'allOf' => [
175
+ # synonyms
176
+ { 'not' => { 'required' => %w{has_argument has_arguments} } }
177
+ ]
141
178
  }
142
179
  ]
143
180
  },
@@ -160,7 +197,8 @@ module Leftovers
160
197
  'path' => { '$ref' => '#/definitions/stringList' },
161
198
  'paths' => { '$ref' => '#/definitions/stringList' },
162
199
  'has_argument' => { '$ref' => '#/definitions/hasArgumentList' },
163
- 'has_arguments' => { '$ref' => '#/definitions/hasArgumentList' }
200
+ 'has_arguments' => { '$ref' => '#/definitions/hasArgumentList' },
201
+ 'has_receiver' => { '$ref' => '#/definitions/hasValueList' }
164
202
  },
165
203
  'minProperties' => 1,
166
204
  'allOf' => [
@@ -390,6 +428,7 @@ module Leftovers
390
428
  { 'required' => ['name'] }, { 'required' => ['names'] },
391
429
  { 'required' => ['path'] }, { 'required' => ['paths'] },
392
430
  { 'required' => ['has_argument'] }, { 'required' => ['has_arguments'] },
431
+ { 'required' => ['has_receiver'] },
393
432
  { 'required' => ['unless'] }
394
433
  ] },
395
434
  {
@@ -399,6 +438,7 @@ module Leftovers
399
438
  'name' => true, 'names' => true,
400
439
  'path' => true, 'paths' => true,
401
440
  'has_argument' => true, 'has_arguments' => true,
441
+ 'has_receiver' => true,
402
442
  'unless' => { '$ref' => '#/definitions/ruleMatcherList' }
403
443
 
404
444
  },
@@ -415,6 +455,7 @@ module Leftovers
415
455
  { 'required' => ['name'] }, { 'required' => ['names'] },
416
456
  { 'required' => ['path'] }, { 'required' => ['paths'] },
417
457
  { 'required' => ['has_argument'] }, { 'required' => ['has_arguments'] },
458
+ { 'required' => ['has_receiver'] },
418
459
  { 'required' => ['unless'] }
419
460
  ] },
420
461
  {
@@ -424,6 +465,7 @@ module Leftovers
424
465
  'name' => true, 'names' => true,
425
466
  'path' => true, 'paths' => true,
426
467
  'has_argument' => true, 'has_arguments' => true,
468
+ 'has_receiver' => true,
427
469
  'unless' => { '$ref' => '#/definitions/ruleMatcherList' },
428
470
 
429
471
  'call' => true, 'calls' => true,
@@ -445,7 +487,7 @@ module Leftovers
445
487
  { '$ref' => '#/definitions/dynamic' }
446
488
  ]
447
489
  },
448
- 'keep' => {
490
+ 'keepTestOnly' => {
449
491
  'anyOf' => [
450
492
  { '$ref' => '#/definitions/string' },
451
493
  {
@@ -460,7 +502,8 @@ module Leftovers
460
502
  'has_prefix' => true, 'has_suffix' => true, 'matches' => true,
461
503
  'path' => true, 'paths' => true,
462
504
  'has_argument' => true, 'has_arguments' => true,
463
- 'unless' => { '$ref' => '#/definitions/keepList' }
505
+ 'has_receiver' => true,
506
+ 'unless' => { '$ref' => '#/definitions/keepTestOnlyList' }
464
507
  },
465
508
  'additionalProperties' => false,
466
509
  'minProperties' => 1
@@ -469,15 +512,15 @@ module Leftovers
469
512
  }
470
513
  ]
471
514
  },
472
- 'keepList' => {
515
+ 'keepTestOnlyList' => {
473
516
  'anyOf' => [
474
517
  {
475
518
  'type' => 'array',
476
- 'items' => { '$ref' => '#/definitions/keep' },
519
+ 'items' => { '$ref' => '#/definitions/keepTestOnly' },
477
520
  'minItems' => 1,
478
521
  'uniqueItems' => true
479
522
  },
480
- { '$ref' => '#/definitions/keep' }
523
+ { '$ref' => '#/definitions/keepTestOnly' }
481
524
  ]
482
525
  }
483
526
  },
@@ -485,9 +528,12 @@ module Leftovers
485
528
  'include_paths' => { '$ref' => '#/definitions/stringList' },
486
529
  'exclude_paths' => { '$ref' => '#/definitions/stringList' },
487
530
  'test_paths' => { '$ref' => '#/definitions/stringList' },
531
+ 'haml_paths' => { '$ref' => '#/definitions/stringList' },
532
+ 'erb_paths' => { '$ref' => '#/definitions/stringList' },
488
533
  'requires' => { '$ref' => '#/definitions/stringList' },
489
534
  'gems' => { '$ref' => '#/definitions/stringList' },
490
- 'keep' => { '$ref' => '#/definitions/keepList' },
535
+ 'keep' => { '$ref' => '#/definitions/keepTestOnlyList' },
536
+ 'test_only' => { '$ref' => '#/definitions/keepTestOnlyList' },
491
537
  'dynamic' => { '$ref' => '#/definitions/dynamicList' }
492
538
  }
493
539
  }.freeze
@@ -2,19 +2,19 @@
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
9
9
 
10
10
  def initialize(
11
11
  name,
12
- method_node: nil,
13
12
  location: method_node.loc.expression,
14
- test: method_node.test?
13
+ test: method_node.test_line?
15
14
  )
16
15
  @name = name
17
- @location_source_line = location.source_line.to_s
16
+ @path = location.source_buffer.name.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?
@@ -0,0 +1,36 @@
1
+ # frozen-string-literal: true
2
+
3
+ # To give to matchers before creating a Definition
4
+
5
+ module Leftovers
6
+ class DefinitionNode
7
+ attr_reader :path, :name
8
+
9
+ def initialize(name, path)
10
+ @name = name
11
+ @path = path
12
+
13
+ freeze
14
+ end
15
+
16
+ # these are the methods checked by things in lib/leftovers/matchers
17
+ def kwargs
18
+ nil
19
+ end
20
+
21
+ def positional_arguments
22
+ nil
23
+ end
24
+
25
+ # these two i'm not sure are possible with the current config flags
26
+ # :nocov:
27
+ def scalar?
28
+ false
29
+ end
30
+
31
+ def type
32
+ :leftovers_definition
33
+ end
34
+ # :nocov:
35
+ end
36
+ end
@@ -1,18 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leftovers
4
- class DefinitionSet < ::Leftovers::Definition
4
+ class DefinitionSet
5
5
  attr_reader :definitions
6
6
 
7
- def initialize(
8
- definitions,
9
- method_node: nil,
10
- location: method_node.loc.expression,
11
- test: method_node.test?
12
- )
7
+ def initialize(definitions)
13
8
  @definitions = definitions
14
9
 
15
- super
10
+ freeze
16
11
  end
17
12
 
18
13
  def names
@@ -23,10 +18,22 @@ module Leftovers
23
18
  @definitions.map(&:to_s).join(', ')
24
19
  end
25
20
 
21
+ def location_s
22
+ @definitions.first.location_s
23
+ end
24
+
25
+ def highlighted_source(*args)
26
+ @definitions.first.highlighted_source(*args)
27
+ end
28
+
26
29
  def in_collection?
27
30
  @definitions.any?(&:in_collection?)
28
31
  end
29
32
 
33
+ def test?
34
+ @definitions.any?(&:test?)
35
+ end
36
+
30
37
  def in_test_collection?
31
38
  @definitions.any?(&:in_test_collection?)
32
39
  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
@@ -57,7 +57,7 @@ module Leftovers
57
57
  NAME_RE = Regexp.union(METHOD_NAME_RE, NON_ALNUM_METHOD_NAME_RE, CONSTANT_NAME_RE)
58
58
  LEFTOVERS_CALL_RE = /\bleftovers:call(?:s|ed|er|ers|) (#{NAME_RE}(?:[, :]+#{NAME_RE})*)/.freeze
59
59
  LEFTOVERS_ALLOW_RE = /\bleftovers:(?:keeps?|skip(?:s|ped|)|allow(?:s|ed|))\b/.freeze
60
- LEFTOVERS_TEST_RE = /\bleftovers:(?:for_tests?|tests?|testing)\b/.freeze
60
+ LEFTOVERS_TEST_RE = /\bleftovers:(?:for_tests?|tests?|testing|test_only)\b/.freeze
61
61
  def process_comments(comments) # rubocop:disable Metrics/AbcSize
62
62
  comments.each do |comment|
63
63
  @allow_lines << comment.loc.line if comment.text.match?(LEFTOVERS_ALLOW_RE)
@@ -171,15 +171,20 @@ module Leftovers
171
171
 
172
172
  private
173
173
 
174
- def test?(loc)
175
- @file.test? || @test_lines.include?(loc.line)
174
+ def test_line?(loc)
175
+ @file.test? ||
176
+ @test_lines.include?(loc.line)
177
+ end
178
+
179
+ def test_node?(node, loc)
180
+ test_line?(loc) || ::Leftovers.config.test_only === node
176
181
  end
177
182
 
178
183
  def add_definition(node, name: node.name, loc: node.loc.name)
179
184
  return if @allow_lines.include?(loc.line)
180
185
  return if Leftovers.config.keep === node
181
186
 
182
- definitions << Leftovers::Definition.new(name, location: loc, test: test?(loc))
187
+ definitions << Leftovers::Definition.new(name, location: loc, test: test_node?(node, loc))
183
188
  end
184
189
 
185
190
  def add_call(name)
@@ -222,7 +227,7 @@ module Leftovers
222
227
 
223
228
  def collect_dynamic(node) # rubocop:disable Metrics/AbcSize
224
229
  node.keep_line = @allow_lines.include?(node.loc.line)
225
- node.test = test?(node.loc) unless node.keep_line?
230
+ node.test_line = test_line?(node.loc) unless node.keep_line?
226
231
 
227
232
  Leftovers.config.dynamic.process(node, self)
228
233
  rescue StandardError => e
@@ -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,42 @@
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(pat) # rubocop:disable Metrics/MethodLength
26
+ matcher = ::Leftovers::MatcherBuilders::And.build([
27
+ ::Leftovers::MatcherBuilders::NodeHasArgument.build(pat.slice(:at, :has_value)),
28
+ ::Leftovers::MatcherBuilders::NodeName.build(
29
+ pat.slice(:match, :has_prefix, :has_suffix)
30
+ ),
31
+ ::Leftovers::MatcherBuilders::NodeType.build(pat[:type]),
32
+ ::Leftovers::MatcherBuilders::NodeHasReceiver.build(pat[:has_receiver])
33
+ ])
34
+
35
+ ::Leftovers::MatcherBuilders::AndNot.build(
36
+ matcher, ::Leftovers::MatcherBuilders::NodeValue.build(pat[:unless_arg])
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+ 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