leftovers 0.5.5 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -2
  3. data/README.md +22 -1
  4. data/docs/Configuration.md +173 -42
  5. data/docs/Custom-Precompilers.md +38 -0
  6. data/leftovers.gemspec +8 -5
  7. data/lib/config/actionmailer.yml +11 -11
  8. data/lib/config/actionpack.yml +5 -3
  9. data/lib/config/activesupport.yml +1 -1
  10. data/lib/config/haml.yml +4 -2
  11. data/lib/config/rails.yml +1 -1
  12. data/lib/config/railties.yml +11 -0
  13. data/lib/config/ruby.yml +76 -8
  14. data/lib/config/slim.yml +4 -2
  15. data/lib/leftovers/ast/node.rb +16 -11
  16. data/lib/leftovers/cli.rb +5 -0
  17. data/lib/leftovers/collector.rb +5 -2
  18. data/lib/leftovers/config.rb +3 -38
  19. data/lib/leftovers/config_loader/argument_position_schema.rb +11 -0
  20. data/lib/leftovers/config_loader/argumentless_transform_schema.rb +21 -0
  21. data/lib/leftovers/config_loader/attribute.rb +56 -0
  22. data/lib/leftovers/config_loader/built_in_precompiler_schema.rb +13 -0
  23. data/lib/leftovers/config_loader/document_schema.rb +48 -0
  24. data/lib/leftovers/config_loader/dynamic_schema.rb +17 -0
  25. data/lib/leftovers/config_loader/has_argument_schema.rb +13 -0
  26. data/lib/leftovers/config_loader/has_value_schema.rb +18 -0
  27. data/lib/leftovers/config_loader/inherit_schema_attributes.rb +22 -0
  28. data/lib/leftovers/config_loader/keep_test_only_schema.rb +13 -0
  29. data/lib/leftovers/config_loader/node.rb +106 -0
  30. data/lib/leftovers/config_loader/object_schema.rb +117 -0
  31. data/lib/leftovers/config_loader/precompile_schema.rb +12 -0
  32. data/lib/leftovers/config_loader/precompiler_schema.rb +11 -0
  33. data/lib/leftovers/config_loader/privacy_processor_schema.rb +10 -0
  34. data/lib/leftovers/config_loader/privacy_schema.rb +15 -0
  35. data/lib/leftovers/config_loader/require_schema.rb +11 -0
  36. data/lib/leftovers/config_loader/rule_pattern_schema.rb +18 -0
  37. data/lib/leftovers/config_loader/scalar_argument_schema.rb +14 -0
  38. data/lib/leftovers/config_loader/scalar_value_schema.rb +14 -0
  39. data/lib/leftovers/config_loader/schema.rb +23 -0
  40. data/lib/leftovers/config_loader/string_enum_schema.rb +62 -0
  41. data/lib/leftovers/config_loader/string_pattern_schema.rb +14 -0
  42. data/lib/leftovers/config_loader/string_schema.rb +14 -0
  43. data/lib/leftovers/config_loader/string_value_processor_schema.rb +11 -0
  44. data/lib/leftovers/config_loader/suggester.rb +22 -0
  45. data/lib/leftovers/config_loader/transform_schema.rb +26 -0
  46. data/lib/leftovers/config_loader/true_schema.rb +18 -0
  47. data/lib/leftovers/config_loader/value_matcher_schema.rb +18 -0
  48. data/lib/leftovers/config_loader/value_or_array_schema.rb +66 -0
  49. data/lib/leftovers/config_loader/value_or_object_schema.rb +40 -0
  50. data/lib/leftovers/config_loader/value_processor_schema.rb +14 -0
  51. data/lib/leftovers/config_loader/value_type_schema.rb +17 -0
  52. data/lib/leftovers/config_loader.rb +86 -0
  53. data/lib/leftovers/definition.rb +1 -1
  54. data/lib/leftovers/definition_node.rb +6 -17
  55. data/lib/leftovers/definition_node_set.rb +11 -0
  56. data/lib/leftovers/definition_to_add.rb +31 -0
  57. data/lib/leftovers/dynamic_processors/call.rb +4 -7
  58. data/lib/leftovers/dynamic_processors/call_definition.rb +14 -11
  59. data/lib/leftovers/dynamic_processors/definition.rb +8 -7
  60. data/lib/leftovers/dynamic_processors/set_default_privacy.rb +18 -0
  61. data/lib/leftovers/dynamic_processors/set_privacy.rb +23 -0
  62. data/lib/leftovers/dynamic_processors.rb +2 -0
  63. data/lib/leftovers/file.rb +12 -12
  64. data/lib/leftovers/file_collector/comments_processor.rb +57 -0
  65. data/lib/leftovers/file_collector/node_processor.rb +131 -0
  66. data/lib/leftovers/file_collector.rb +66 -169
  67. data/lib/leftovers/matcher_builders/and_not.rb +7 -5
  68. data/lib/leftovers/matcher_builders/document.rb +13 -0
  69. data/lib/leftovers/matcher_builders/name.rb +18 -17
  70. data/lib/leftovers/matcher_builders/node.rb +48 -28
  71. data/lib/leftovers/matcher_builders/node_has_argument.rb +48 -53
  72. data/lib/leftovers/matcher_builders/node_has_keyword_argument.rb +13 -10
  73. data/lib/leftovers/matcher_builders/node_has_positional_argument.rb +29 -15
  74. data/lib/leftovers/matcher_builders/node_privacy.rb +13 -0
  75. data/lib/leftovers/matcher_builders/node_type.rb +4 -4
  76. data/lib/leftovers/matcher_builders/node_value.rb +28 -23
  77. data/lib/leftovers/matcher_builders/or.rb +50 -50
  78. data/lib/leftovers/matcher_builders/string_pattern.rb +14 -5
  79. data/lib/leftovers/matcher_builders.rb +2 -0
  80. data/lib/leftovers/matchers/all.rb +0 -4
  81. data/lib/leftovers/matchers/and.rb +0 -4
  82. data/lib/leftovers/matchers/any.rb +0 -4
  83. data/lib/leftovers/matchers/node_has_any_keyword_argument.rb +1 -7
  84. data/lib/leftovers/matchers/node_has_any_positional_argument_with_value.rb +1 -7
  85. data/lib/leftovers/matchers/node_has_positional_argument_with_value.rb +0 -4
  86. data/lib/leftovers/matchers/node_name.rb +0 -4
  87. data/lib/leftovers/matchers/node_pair_value.rb +0 -4
  88. data/lib/leftovers/matchers/node_path.rb +0 -4
  89. data/lib/leftovers/matchers/node_privacy.rb +19 -0
  90. data/lib/leftovers/matchers/node_scalar_value.rb +0 -4
  91. data/lib/leftovers/matchers/node_type.rb +0 -4
  92. data/lib/leftovers/matchers/not.rb +0 -4
  93. data/lib/leftovers/matchers/or.rb +0 -4
  94. data/lib/leftovers/matchers.rb +1 -0
  95. data/lib/leftovers/merged_config.rb +15 -33
  96. data/lib/leftovers/precompilers/erb.rb +22 -0
  97. data/lib/leftovers/precompilers/haml.rb +15 -0
  98. data/lib/leftovers/precompilers/json.rb +27 -0
  99. data/lib/leftovers/precompilers/precompiler.rb +28 -0
  100. data/lib/leftovers/precompilers/slim.rb +15 -0
  101. data/lib/leftovers/precompilers/yaml.rb +75 -0
  102. data/lib/leftovers/precompilers.rb +50 -0
  103. data/lib/leftovers/processor_builders/action.rb +48 -39
  104. data/lib/leftovers/processor_builders/add_prefix.rb +2 -2
  105. data/lib/leftovers/processor_builders/add_suffix.rb +2 -2
  106. data/lib/leftovers/processor_builders/argument.rb +8 -11
  107. data/lib/leftovers/processor_builders/dynamic.rb +51 -16
  108. data/lib/leftovers/processor_builders/each.rb +2 -2
  109. data/lib/leftovers/processor_builders/each_action.rb +33 -33
  110. data/lib/leftovers/processor_builders/each_dynamic.rb +4 -8
  111. data/lib/leftovers/processor_builders/each_for_definition_set.rb +25 -21
  112. data/lib/leftovers/processor_builders/keyword.rb +3 -4
  113. data/lib/leftovers/processor_builders/transform.rb +4 -4
  114. data/lib/leftovers/processor_builders/transform_chain.rb +16 -8
  115. data/lib/leftovers/processor_builders/transform_set.rb +32 -28
  116. data/lib/leftovers/rake_task.rb +1 -1
  117. data/lib/leftovers/todo_reporter.rb +10 -35
  118. data/lib/leftovers/value_processors/add_dynamic_prefix.rb +3 -10
  119. data/lib/leftovers/value_processors/add_dynamic_suffix.rb +3 -10
  120. data/lib/leftovers/value_processors/delete_prefix.rb +0 -6
  121. data/lib/leftovers/value_processors/delete_suffix.rb +0 -6
  122. data/lib/leftovers/value_processors/each.rb +1 -1
  123. data/lib/leftovers/value_processors/each_for_definition_set.rb +4 -10
  124. data/lib/leftovers/value_processors/each_keyword.rb +1 -1
  125. data/lib/leftovers/value_processors/each_keyword_argument.rb +1 -1
  126. data/lib/leftovers/value_processors/each_positional_argument.rb +3 -2
  127. data/lib/leftovers/value_processors/keyword.rb +3 -11
  128. data/lib/leftovers/value_processors/keyword_argument.rb +2 -10
  129. data/lib/leftovers/value_processors/return_definition_node.rb +14 -0
  130. data/lib/leftovers/value_processors/{return_string.rb → return_sym.rb} +1 -1
  131. data/lib/leftovers/value_processors/split.rb +2 -2
  132. data/lib/leftovers/value_processors.rb +2 -2
  133. data/lib/leftovers/version.rb +1 -1
  134. data/lib/leftovers.rb +66 -23
  135. metadata +98 -39
  136. data/lib/config/actioncable.yml +0 -4
  137. data/lib/leftovers/backports.rb +0 -40
  138. data/lib/leftovers/config_validator/error_processor.rb +0 -196
  139. data/lib/leftovers/config_validator/schema_hash.rb +0 -530
  140. data/lib/leftovers/config_validator.rb +0 -60
  141. data/lib/leftovers/erb.rb +0 -20
  142. data/lib/leftovers/haml.rb +0 -21
  143. data/lib/leftovers/slim.rb +0 -21
  144. data/lib/leftovers/value_processors/return_definition.rb +0 -26
@@ -1,19 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
- require 'parser'
5
4
 
6
5
  module Leftovers
7
- class FileCollector < ::Parser::AST::Processor # rubocop:disable Metrics/ClassLength
8
- attr_reader :calls, :definitions
6
+ class FileCollector
7
+ autoload(:CommentsProcessor, "#{__dir__}/file_collector/comments_processor.rb")
8
+ autoload(:NodeProcessor, "#{__dir__}/file_collector/node_processor.rb")
9
9
 
10
- def initialize(ruby, file) # rubocop:disable Lint/MissingSuper
10
+ attr_reader :calls, :allow_lines, :test_lines, :dynamic_lines
11
+ attr_accessor :default_method_privacy
12
+
13
+ def initialize(ruby, file)
11
14
  @calls = []
12
- @definitions = []
15
+ @definitions_to_add = {}
13
16
  @allow_lines = Set.new.compare_by_identity
14
17
  @test_lines = Set.new.compare_by_identity
18
+ @dynamic_lines = {}
15
19
  @ruby = ruby
16
20
  @file = file
21
+ @default_method_privacy = :public
22
+ @definition_sets_to_add = []
17
23
  end
18
24
 
19
25
  def filename
@@ -21,170 +27,57 @@ module Leftovers
21
27
  end
22
28
 
23
29
  def to_h
24
- squash!
25
-
26
- {
27
- test?: @file.test?,
28
- calls: calls,
29
- definitions: definitions
30
- }
30
+ { test?: @file.test?, calls: squash!(calls), definitions: squash!(definitions) }
31
31
  end
32
32
 
33
- def squash!
34
- calls.flatten!
35
- calls.compact!
36
- calls.uniq!
37
- definitions.flatten!
38
- definitions.compact!
39
- definitions.uniq!
40
- definitions.reject! { |v| v == :keep }
33
+ def squash!(list)
34
+ list.flatten!
35
+ list.compact!
36
+ list.uniq!
37
+ list
41
38
  end
42
39
 
43
40
  def collect
44
41
  ast, comments = Leftovers::Parser.parse_with_comments(@ruby, @file.relative_path)
45
- process_comments(comments)
46
- process(ast)
42
+ CommentsProcessor.process(comments, self)
43
+ NodeProcessor.new(self).process(ast)
47
44
  rescue ::Parser::SyntaxError => e
48
- Leftovers.warn "\e[31m#{filename}:#{e.diagnostic.location.line}:#{e.diagnostic.location.column} SyntaxError: #{e.message}\e[0m" # rubocop:disable Layout/LineLength
49
- end
50
-
51
- METHOD_NAME_RE = /[[:alpha:]_][[:alnum:]_]*\b[?!=]?/.freeze
52
- NON_ALNUM_METHOD_NAME_RE = Regexp.union(%w{
53
- []= [] ** ~ +@ -@ * / % + - >> << &
54
- ^ | <=> <= >= < > === == != =~ !~ !
55
- }.map { |op| /#{Regexp.escape(op)}/ })
56
- CONSTANT_NAME_RE = /[[:upper:]][[:alnum:]_]*\b/.freeze
57
- NAME_RE = Regexp.union(METHOD_NAME_RE, NON_ALNUM_METHOD_NAME_RE, CONSTANT_NAME_RE)
58
- LEFTOVERS_CALL_RE = /\bleftovers:call(?:s|ed|er|ers|) (#{NAME_RE}(?:[, :]+#{NAME_RE})*)/.freeze
59
- LEFTOVERS_ALLOW_RE = /\bleftovers:(?:keeps?|skip(?:s|ped|)|allow(?:s|ed|))\b/.freeze
60
- LEFTOVERS_TEST_RE = /\bleftovers:(?:for_tests?|tests?|testing|test_only)\b/.freeze
61
- def process_comments(comments) # rubocop:disable Metrics/AbcSize
62
- comments.each do |comment|
63
- @allow_lines << comment.loc.line if comment.text.match?(LEFTOVERS_ALLOW_RE)
64
- @test_lines << comment.loc.line if comment.text.match?(LEFTOVERS_TEST_RE)
65
-
66
- next unless (match = comment.text.match(LEFTOVERS_CALL_RE))
67
-
68
- match[1].scan(NAME_RE).each { |s| add_call(s.to_sym) }
69
- end
45
+ Leftovers.warn(
46
+ "\e[31m#{filename}:#{e.diagnostic.location.line}:#{e.diagnostic.location.column} " \
47
+ "SyntaxError: #{e.message}\e[0m"
48
+ )
70
49
  end
71
50
 
72
51
  # grab method definitions
73
- def on_def(node)
74
- add_definition(node)
75
- super
76
- end
77
-
78
- def on_ivasgn(node)
79
- collect_variable_assign(node)
80
- super
81
- end
82
-
83
- def on_gvasgn(node)
84
- collect_variable_assign(node)
85
- super
86
- end
87
-
88
- def on_cvasgn(node)
89
- collect_variable_assign(node)
90
- super
91
- end
92
-
93
- def on_ivar(node)
94
- add_call(node.name)
95
- super
96
- end
97
-
98
- def on_gvar(node)
99
- add_call(node.name)
100
- super
101
- end
102
-
103
- def on_cvar(node)
104
- add_call(node.name)
105
- super
106
- end
107
-
108
- def on_op_asgn(node)
109
- collect_op_asgn(node)
110
- super
111
- end
112
-
113
- def on_and_asgn(node)
114
- collect_op_asgn(node)
115
- super
116
- end
117
-
118
- def on_or_asgn(node)
119
- collect_op_asgn(node)
120
- super
121
- end
122
-
123
- # grab method calls
124
- def on_send(node)
125
- super
126
- collect_send(node)
127
- end
128
-
129
- def on_csend(node)
130
- super
131
- collect_send(node)
132
- end
133
-
134
- def on_const(node)
135
- super
136
- add_call(node.name)
137
- end
138
-
139
- # grab e.g. :to_s in each(&:to_s)
140
- def on_block_pass(node)
141
- super
142
- add_call(node.children.first.to_sym) if node.children.first.string_or_symbol?
143
- end
144
-
145
- # grab class Constant or module Constant
146
- def on_class(node)
147
- # don't call super so we don't process the class name
148
- # !!! (# wtf does this mean dana? what would happen instead?)
149
- process_all(node.children.drop(1))
150
-
151
- node = node.children.first
152
-
153
- add_definition(node)
52
+ def add_definition(node, name: node.name, loc: node.loc.name)
53
+ @definitions_to_add[name] = ::Leftovers::DefinitionToAdd.new(node, name: name, location: loc)
154
54
  end
155
- alias_method :on_module, :on_class
156
55
 
157
- # grab Constant = Class.new or CONSTANT = 'string'.freeze
158
- def on_casgn(node)
159
- super
160
- add_definition(node)
161
- collect_dynamic(node)
56
+ def add_definition_set(definition_node_set)
57
+ @definition_sets_to_add << definition_node_set.definitions.map do |definition_node|
58
+ ::Leftovers::DefinitionToAdd.new(definition_node, location: definition_node.loc)
59
+ end
162
60
  end
163
61
 
164
- # grab calls to `alias new_method original_method`
165
- def on_alias(node)
166
- super
167
- new_method, original_method = node.children
168
- add_definition(new_method, name: new_method.children.first, loc: new_method.loc.expression)
169
- add_call(original_method.children.first)
62
+ def set_privacy(name, to)
63
+ @definitions_to_add[name]&.privacy = to
170
64
  end
171
65
 
172
- private
66
+ def definitions
67
+ @definitions ||= @definitions_to_add.each_value.map { |d| d.to_definition(self) } +
68
+ @definition_sets_to_add.map do |definition_set|
69
+ next if definition_set.any? { |d| d.keep?(self) }
173
70
 
174
- def test_line?(loc)
175
- @file.test? ||
176
- @test_lines.include?(loc.line)
71
+ ::Leftovers::DefinitionSet.new(definition_set.map { |d| d.to_definition(self) })
72
+ end
177
73
  end
178
74
 
179
- def test_node?(node, loc)
180
- test_line?(loc) || ::Leftovers.config.test_only === node
75
+ def test_line?(line)
76
+ @file.test? || @test_lines.include?(line)
181
77
  end
182
78
 
183
- def add_definition(node, name: node.name, loc: node.loc.name)
184
- return if @allow_lines.include?(loc.line)
185
- return if Leftovers.config.keep === node
186
-
187
- definitions << Leftovers::Definition.new(name, location: loc, test: test_node?(node, loc))
79
+ def keep_line?(line)
80
+ @allow_lines.include?(line)
188
81
  end
189
82
 
190
83
  def add_call(name)
@@ -196,42 +89,46 @@ module Leftovers
196
89
  collect_dynamic(node)
197
90
  end
198
91
 
199
- # just collects the call, super will collect the definition
200
- def collect_var_op_asgn(node)
201
- name = node.children.first
202
-
203
- add_call(name)
204
- end
205
-
206
- def collect_send_op_asgn(node)
207
- name = node.children[1]
208
-
209
- add_call(:"#{name}=")
210
- end
211
-
212
92
  def collect_variable_assign(node)
213
93
  add_definition(node)
214
-
215
94
  collect_dynamic(node)
216
95
  end
217
96
 
218
97
  def collect_op_asgn(node)
219
- node = node.children.first
220
- # :nocov: # don't need else, it's exhaustive for callers
98
+ node = node.first
221
99
  case node.type
100
+ # just collects the :call=, super will collect the :call
101
+ when :send, :csend then add_call(:"#{node.name}=")
102
+ # just collects the call, super will collect the definition
103
+ when :ivasgn, :gvasgn, :cvasgn then add_call(node.name)
104
+ when :lvasgn then nil # we don't care about lvasgn
222
105
  # :nocov:
223
- when :send then collect_send_op_asgn(node)
224
- when :ivasgn, :gvasgn, :cvasgn then collect_var_op_asgn(node)
106
+ else raise Leftovers::UnexpectedCase, "Unhandled value #{node.type.inspect}"
107
+ # :nocov:
225
108
  end
226
109
  end
227
110
 
228
- def collect_dynamic(node) # rubocop:disable Metrics/AbcSize
111
+ def collect_commented_dynamic(node)
112
+ @dynamic_lines[node.loc.line]&.each do |fake_method_name|
113
+ collect_dynamic(build_send_wrapper_for(node, fake_method_name))
114
+ end
115
+ end
116
+
117
+ def collect_dynamic(node)
229
118
  node.keep_line = @allow_lines.include?(node.loc.line)
230
- node.test_line = test_line?(node.loc) unless node.keep_line?
231
119
 
232
120
  Leftovers.config.dynamic.process(node, self)
233
121
  rescue StandardError => e
234
- raise ::Leftovers::Error, "#{e.class}: #{e.message}\nwhen processing #{node} at #{filename}:#{node.loc.line}:#{node.loc.column}", e.backtrace # rubocop:disable Layout/LineLength
122
+ raise ::Leftovers::Error, "#{e.class}: #{e.message}\n" \
123
+ "when processing #{node} at #{filename}:#{node.loc.line}:#{node.loc.column}", e.backtrace
124
+ end
125
+
126
+ private
127
+
128
+ def build_send_wrapper_for(node, name)
129
+ ::Leftovers::AST::Node.new(
130
+ :send, [nil, name.to_sym, *node.arguments], location: node.location
131
+ )
235
132
  end
236
133
  end
237
134
  end
@@ -3,11 +3,13 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module AndNot
6
- def self.build(positive_matcher, negative_matcher)
7
- ::Leftovers::MatcherBuilders::And.build([
8
- positive_matcher,
9
- ::Leftovers::MatcherBuilders::Unless.build(negative_matcher)
10
- ])
6
+ class << self
7
+ def build(positive_matcher, negative_matcher)
8
+ ::Leftovers::MatcherBuilders::And.build([
9
+ positive_matcher,
10
+ ::Leftovers::MatcherBuilders::Unless.build(negative_matcher)
11
+ ])
12
+ end
11
13
  end
12
14
  end
13
15
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ module MatcherBuilders
5
+ module Document
6
+ def self.build(true_arg)
7
+ return unless true_arg
8
+
9
+ ::Leftovers::Matchers::NodeName.new(:__leftovers_document)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -3,26 +3,27 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module Name
6
- def self.build(patterns) # rubocop:disable Metrics/MethodLength
7
- ::Leftovers::MatcherBuilders::Or.each_or_self(patterns) do |pat|
8
- case pat
9
- when nil
10
- when ::Array
11
- ::Leftovers::MatcherBuilders::Name.build(pat)
12
- when ::String
13
- ::Leftovers::MatcherBuilders::String.build(pat)
14
- when ::Hash
15
- unless_arg = pat.delete(:unless_arg)
16
-
17
- ::Leftovers::MatcherBuilders::AndNot.build(
18
- ::Leftovers::MatcherBuilders::StringPattern.build(**pat),
19
- ::Leftovers::MatcherBuilders::Name.build(unless_arg)
20
- )
21
- # :nocov:
22
- else raise
6
+ class << self
7
+ def build(patterns)
8
+ ::Leftovers::MatcherBuilders::Or.each_or_self(patterns) do |pat|
9
+ case pat
10
+ when ::String then ::Leftovers::MatcherBuilders::String.build(pat)
11
+ when ::Hash then build_from_hash(**pat)
23
12
  # :nocov:
13
+ else raise Leftovers::UnexpectedCase, "Unhandled value #{pat.inspect}"
14
+ # :nocov:
15
+ end
24
16
  end
25
17
  end
18
+
19
+ private
20
+
21
+ def build_from_hash(unless_arg: nil, **pattern)
22
+ ::Leftovers::MatcherBuilders::AndNot.build(
23
+ ::Leftovers::MatcherBuilders::StringPattern.build(**pattern),
24
+ ::Leftovers::MatcherBuilders::Name.build(unless_arg)
25
+ )
26
+ end
26
27
  end
27
28
  end
28
29
  end
@@ -3,39 +3,59 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module Node
6
- def self.build(pattern)
7
- ::Leftovers::MatcherBuilders::Or.each_or_self(pattern) do |pat|
8
- case pat
9
- when ::String
10
- ::Leftovers::MatcherBuilders::NodeName.build(pat)
11
- when ::Hash
12
- build_from_hash(**pat)
13
- # :nocov:
14
- else raise
15
- # :nocov:
6
+ class << self
7
+ def build(patterns)
8
+ ::Leftovers::MatcherBuilders::Or.each_or_self(patterns) do |pattern|
9
+ case pattern
10
+ when ::String then ::Leftovers::MatcherBuilders::NodeName.build(pattern)
11
+ when ::Hash then build_from_hash(**pattern)
12
+ # :nocov:
13
+ else raise Leftovers::UnexpectedCase, "Unhandled value #{pattern.inspect}"
14
+ # :nocov:
15
+ end
16
16
  end
17
17
  end
18
- end
19
18
 
20
- def self.build_from_hash( # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
21
- names: nil, match: nil, has_prefix: nil, has_suffix: nil,
22
- paths: nil,
23
- has_arguments: nil,
24
- has_receiver: nil,
25
- unless_arg: nil
26
- )
27
- ::Leftovers::MatcherBuilders::And.build([
28
- ::Leftovers::MatcherBuilders::NodeName.build([
29
- names,
30
- { match: match, has_prefix: has_prefix, has_suffix: has_suffix }.compact
31
- ]),
32
- ::Leftovers::MatcherBuilders::NodePath.build(paths),
33
- ::Leftovers::MatcherBuilders::NodeHasArgument.build(has_arguments),
34
- ::Leftovers::MatcherBuilders::NodeHasReceiver.build(has_receiver),
19
+ private
20
+
21
+ def build_node_name_matcher(names, match, has_prefix, has_suffix)
22
+ ::Leftovers::MatcherBuilders::Or.build([
23
+ ::Leftovers::MatcherBuilders::NodeName.build(names),
24
+ ::Leftovers::MatcherBuilders::NodeName.build(
25
+ match: match, has_prefix: has_prefix, has_suffix: has_suffix
26
+ )
27
+ ])
28
+ end
29
+
30
+ def build_unless_matcher(unless_arg)
31
+ return unless unless_arg
32
+
35
33
  ::Leftovers::MatcherBuilders::Unless.build(
36
- (::Leftovers::MatcherBuilders::Node.build(unless_arg) if unless_arg)
34
+ ::Leftovers::MatcherBuilders::Node.build(unless_arg)
37
35
  )
38
- ])
36
+ end
37
+
38
+ def build_from_hash( # rubocop:disable Metrics/ParameterLists
39
+ names: nil, match: nil, has_prefix: nil, has_suffix: nil,
40
+ document: false,
41
+ paths: nil,
42
+ has_arguments: nil,
43
+ has_receiver: nil,
44
+ type: nil,
45
+ privacy: nil,
46
+ unless_arg: nil
47
+ )
48
+ ::Leftovers::MatcherBuilders::And.build([
49
+ build_node_name_matcher(names, match, has_prefix, has_suffix),
50
+ ::Leftovers::MatcherBuilders::Document.build(document),
51
+ ::Leftovers::MatcherBuilders::NodePath.build(paths),
52
+ ::Leftovers::MatcherBuilders::NodeHasArgument.build(has_arguments),
53
+ ::Leftovers::MatcherBuilders::NodeHasReceiver.build(has_receiver),
54
+ ::Leftovers::MatcherBuilders::NodePrivacy.build(privacy),
55
+ ::Leftovers::MatcherBuilders::NodeType.build(type),
56
+ build_unless_matcher(unless_arg)
57
+ ])
58
+ end
39
59
  end
40
60
  end
41
61
  end
@@ -3,72 +3,67 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasArgument
6
- def self.build(patterns) # rubocop:disable Metrics/MethodLength
7
- ::Leftovers::MatcherBuilders::Or.each_or_self(patterns) do |pat|
8
- case pat
9
- when ::String
10
- ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(pat, nil)
11
- when ::Integer
12
- ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(pat, nil)
13
- when ::Hash
14
- build_from_hash(**pat)
15
- # :nocov:
16
- else
17
- raise
6
+ class << self
7
+ def build(patterns)
8
+ ::Leftovers::MatcherBuilders::Or.each_or_self(patterns) do |pat|
9
+ case pat
10
+ when ::String
11
+ ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(pat, nil)
12
+ when ::Integer
13
+ ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(pat, nil)
14
+ when ::Hash then build_from_hash(**pat)
18
15
  # :nocov:
16
+ else raise Leftovers::UnexpectedCase, "Unhandled value #{pat.inspect}"
17
+ # :nocov:
18
+ end
19
19
  end
20
20
  end
21
- end
22
21
 
23
- def self.separate_argument_types(at) # rubocop:disable Metrics/MethodLength
24
- keys = []
25
- positions = []
22
+ private
26
23
 
27
- ::Leftovers.each_or_self(at) do |k|
28
- case k
29
- when '*'
30
- positions << k
31
- when ::String, ::Hash
32
- keys << k
33
- when ::Integer
34
- positions << k
35
- # :nocov:
36
- else raise
37
- # :nocov:
38
- end
39
- end
24
+ def build_from_hash(at: nil, has_value: nil, unless_arg: nil)
25
+ value_matcher = ::Leftovers::MatcherBuilders::NodeValue.build(has_value)
40
26
 
41
- keys = nil if keys.empty?
42
- positions = nil if positions.empty?
27
+ ::Leftovers::MatcherBuilders::AndNot.build(
28
+ build_argument_matcher(value_matcher, **separate_argument_types(at)),
29
+ ::Leftovers::MatcherBuilders::NodeHasArgument.build(unless_arg)
30
+ )
31
+ end
43
32
 
44
- [keys, positions]
45
- end
33
+ def separate_argument_types(at)
34
+ groups = ::Leftovers.each_or_self(at).group_by do |index|
35
+ case index
36
+ when '*', ::Integer then :positions
37
+ when ::String, ::Hash then :keys
38
+ # :nocov:
39
+ else raise Leftovers::UnexpectedCase, "Unhandled value #{index.inspect}"
40
+ # :nocov:
41
+ end
42
+ end
46
43
 
47
- def self.build_from_hash( # rubocop:disable Metrics/MethodLength
48
- at: nil,
49
- has_value: nil,
50
- unless_arg: nil
51
- )
52
- keys, positions = separate_argument_types(at)
44
+ groups.transform_values { |v| Leftovers.unwrap_array(v) }
45
+ end
53
46
 
54
- value_matcher = ::Leftovers::MatcherBuilders::NodeValue.build(has_value)
55
- matcher = if (keys && positions) || (!keys && !positions)
56
- ::Leftovers::MatcherBuilders::Or.build([
57
- ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(positions, value_matcher),
58
- ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher)
59
- ])
60
- elsif keys
47
+ def build_has_keyword_argument(keys, value_matcher)
61
48
  ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher)
62
- elsif positions
49
+ end
50
+
51
+ def build_has_positional_argument(positions, value_matcher)
63
52
  ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(positions, value_matcher)
64
- # :nocov:
65
- else raise
66
- # :nocov:
67
53
  end
68
54
 
69
- ::Leftovers::MatcherBuilders::AndNot.build(
70
- matcher, ::Leftovers::MatcherBuilders::NodeHasArgument.build(unless_arg)
71
- )
55
+ def build_argument_matcher(value_matcher, keys: nil, positions: nil)
56
+ if keys && !positions
57
+ build_has_keyword_argument(keys, value_matcher)
58
+ elsif positions && !keys
59
+ build_has_positional_argument(positions, value_matcher)
60
+ else
61
+ ::Leftovers::MatcherBuilders::Or.build([
62
+ build_has_positional_argument(positions, value_matcher),
63
+ build_has_keyword_argument(keys, value_matcher)
64
+ ])
65
+ end
66
+ end
72
67
  end
73
68
  end
74
69
  end
@@ -4,21 +4,24 @@ module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasKeywordArgument
6
6
  class << self
7
- def build(keywords, value_matcher) # rubocop:disable Metrics/MethodLength
7
+ def build(keywords, value_matcher)
8
8
  value_matcher = ::Leftovers::MatcherBuilders::NodePairValue.build(value_matcher)
9
- keyword_matcher = if ::Leftovers.each_or_self(keywords).any? { |x| x == '**' }
9
+ keyword_matcher = build_keyword_matcher(keywords)
10
+ pair_matcher = ::Leftovers::MatcherBuilders::And.build([keyword_matcher, value_matcher])
11
+
12
+ return unless pair_matcher
13
+
14
+ ::Leftovers::Matchers::NodeHasAnyKeywordArgument.new(pair_matcher)
15
+ end
16
+
17
+ private
18
+
19
+ def build_keyword_matcher(keywords)
20
+ if ::Leftovers.each_or_self(keywords).include?('**')
10
21
  ::Leftovers::Matchers::NodeType.new(:pair)
11
22
  else
12
23
  ::Leftovers::MatcherBuilders::NodePairName.build(keywords)
13
24
  end
14
-
15
- pair_matcher = ::Leftovers::MatcherBuilders::And.build([
16
- keyword_matcher, value_matcher
17
- ])
18
-
19
- return nil unless pair_matcher
20
-
21
- ::Leftovers::Matchers::NodeHasAnyKeywordArgument.new(pair_matcher)
22
25
  end
23
26
  end
24
27
  end
@@ -4,22 +4,36 @@ module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasPositionalArgument
6
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
19
-
20
- ::Leftovers::Matchers::NodeHasPositionalArgument.new(pos)
7
+ def build(positions, value_matcher)
8
+ if positions && !all_positions?(positions) && value_matcher
9
+ build_has_positional_value_matcher(positions, value_matcher)
10
+ elsif positions && !value_matcher
11
+ build_has_position_matcher(positions)
21
12
  elsif value_matcher
22
- ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
13
+ build_has_any_positional_value_matcher(value_matcher)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def all_positions?(positions)
20
+ ::Leftovers.each_or_self(positions).include?('*')
21
+ end
22
+
23
+ def build_has_position_matcher(positions)
24
+ position = 0 if all_positions?(positions)
25
+ position ||= ::Leftovers.each_or_self(positions).min
26
+
27
+ ::Leftovers::Matchers::NodeHasPositionalArgument.new(position)
28
+ end
29
+
30
+ def build_has_any_positional_value_matcher(value_matcher)
31
+ ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
32
+ end
33
+
34
+ def build_has_positional_value_matcher(positions, value_matcher)
35
+ ::Leftovers::MatcherBuilders::Or.each_or_self(positions) do |position|
36
+ ::Leftovers::Matchers::NodeHasPositionalArgumentWithValue.new(position, value_matcher)
23
37
  end
24
38
  end
25
39
  end