igata 0.2.0 → 0.3.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.
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Igata
4
+ module Extractors
5
+ # rubocop:disable Metrics/ClassLength, Lint/DuplicateBranch, Naming/PredicateMethod
6
+ class BoundaryValueGenerator
7
+ def self.extract(comparisons)
8
+ new(comparisons).extract
9
+ end
10
+
11
+ def initialize(comparisons)
12
+ @comparisons = comparisons
13
+ end
14
+
15
+ def extract
16
+ @comparisons.map do |comparison|
17
+ generate_boundary_values(comparison)
18
+ end.compact
19
+ end
20
+
21
+ private
22
+
23
+ def generate_boundary_values(comparison) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
24
+ right_value = comparison.right
25
+
26
+ # Determine value type and generate appropriate boundary values
27
+ test_values, description = if numeric?(right_value)
28
+ generate_numeric_boundaries(comparison)
29
+ elsif string_literal?(right_value)
30
+ generate_string_boundaries(comparison)
31
+ elsif nil_value?(right_value)
32
+ generate_nil_boundaries(comparison)
33
+ elsif boolean_value?(right_value)
34
+ generate_boolean_boundaries(comparison)
35
+ elsif symbol_value?(right_value)
36
+ generate_symbol_boundaries(comparison)
37
+ else
38
+ # Variable or method call - skip boundary value generation
39
+ return nil
40
+ end
41
+
42
+ return nil if test_values.nil? || test_values.empty?
43
+
44
+ Values::BoundaryValueInfo.new(
45
+ comparison: comparison,
46
+ test_values: test_values,
47
+ description: description
48
+ )
49
+ end
50
+
51
+ # Numeric boundary values
52
+ # rubocop:disable Metrics/MethodLength
53
+ def generate_numeric_boundaries(comparison)
54
+ value = parse_numeric(comparison.right)
55
+ operator = comparison.operator
56
+
57
+ test_values = case operator
58
+ when :>=
59
+ [value - 1, value, value + 1]
60
+ when :>
61
+ [value, value + 1]
62
+ when :<=
63
+ [value - 1, value, value + 1]
64
+ when :<
65
+ [value - 1, value]
66
+ when :==
67
+ [value - 1, value, value + 1]
68
+ when :!=
69
+ [value]
70
+ else
71
+ []
72
+ end
73
+
74
+ description = build_numeric_description(operator, value, test_values)
75
+ [test_values, description]
76
+ end
77
+ # rubocop:enable Metrics/MethodLength
78
+
79
+ # String literal boundary values
80
+ # rubocop:disable Metrics/MethodLength
81
+ def generate_string_boundaries(comparison)
82
+ str_value = parse_string(comparison.right)
83
+ operator = comparison.operator
84
+
85
+ test_values = case operator
86
+ when :>=, :>
87
+ # For string comparison: previous, same, next
88
+ [decrement_string(str_value), str_value, increment_string(str_value)]
89
+ when :<=, :<
90
+ [decrement_string(str_value), str_value, increment_string(str_value)]
91
+ when :==
92
+ [str_value, "different_string"]
93
+ when :!=
94
+ [str_value, "different_string"]
95
+ else
96
+ []
97
+ end
98
+
99
+ description = build_string_description(operator, str_value, test_values)
100
+ [test_values, description]
101
+ end
102
+ # rubocop:enable Metrics/MethodLength
103
+
104
+ # Nil boundary values
105
+ # rubocop:disable Metrics/MethodLength
106
+ def generate_nil_boundaries(comparison)
107
+ operator = comparison.operator
108
+
109
+ test_values = case operator
110
+ when :==
111
+ [nil, "some_value"]
112
+ when :!=
113
+ [nil, "some_value"]
114
+ else
115
+ []
116
+ end
117
+
118
+ description = "nil check: test with #{test_values.inspect}"
119
+ [test_values, description]
120
+ end
121
+ # rubocop:enable Metrics/MethodLength
122
+
123
+ # Boolean boundary values
124
+ def generate_boolean_boundaries(comparison)
125
+ operator = comparison.operator
126
+
127
+ test_values = case operator
128
+ when :==, :!=
129
+ [true, false]
130
+ else
131
+ []
132
+ end
133
+
134
+ description = "boolean check: test with #{test_values.inspect}"
135
+ [test_values, description]
136
+ end
137
+
138
+ # Symbol boundary values
139
+ # rubocop:disable Metrics/MethodLength
140
+ def generate_symbol_boundaries(comparison)
141
+ sym_value = parse_symbol(comparison.right)
142
+ operator = comparison.operator
143
+
144
+ test_values = case operator
145
+ when :==
146
+ [sym_value, ":other_symbol"]
147
+ when :!=
148
+ [sym_value, ":other_symbol"]
149
+ else
150
+ []
151
+ end
152
+
153
+ description = "symbol check: test with #{test_values.inspect}"
154
+ [test_values, description]
155
+ end
156
+ # rubocop:enable Metrics/MethodLength
157
+
158
+ # Type detection methods
159
+ def numeric?(str)
160
+ # Check if string is a numeric literal (integer or float)
161
+ return false if str.nil? || str.empty?
162
+
163
+ # Remove quotes if present (shouldn't be for numbers but just in case)
164
+ clean_str = str.gsub(/["']/, "")
165
+ # Match integer or float
166
+ clean_str.match?(/\A-?\d+(\.\d+)?\z/)
167
+ end
168
+
169
+ def string_literal?(str)
170
+ # Check if string is a string literal (starts and ends with quotes)
171
+ return false if str.nil? || str.empty?
172
+
173
+ str.match?(/\A["'].*["']\z/)
174
+ end
175
+
176
+ def nil_value?(str)
177
+ str == "nil"
178
+ end
179
+
180
+ def boolean_value?(str)
181
+ %w[true false].include?(str)
182
+ end
183
+
184
+ def symbol_value?(str)
185
+ return false if str.nil? || str.empty?
186
+
187
+ str.start_with?(":")
188
+ end
189
+
190
+ # Parsing methods
191
+ def parse_numeric(str)
192
+ clean_str = str.gsub(/["']/, "")
193
+ clean_str.include?(".") ? clean_str.to_f : clean_str.to_i
194
+ end
195
+
196
+ def parse_string(str)
197
+ # Remove surrounding quotes
198
+ str.gsub(/\A["']|["']\z/, "")
199
+ end
200
+
201
+ def parse_boolean(str)
202
+ str == "true"
203
+ end
204
+
205
+ def parse_symbol(str)
206
+ str # Keep as is with colon
207
+ end
208
+
209
+ # String manipulation for boundary values
210
+ # rubocop:disable Metrics/MethodLength
211
+ def increment_string(str)
212
+ # Simple increment: "A" -> "B", "Z" -> "AA"
213
+ if str.empty?
214
+ "A"
215
+ elsif str == "Z"
216
+ "AA"
217
+ else
218
+ last_char = str[-1]
219
+ if last_char == "z"
220
+ "#{str[0..-2]}aa"
221
+ else
222
+ str[0..-2] + last_char.next
223
+ end
224
+ end
225
+ end
226
+ # rubocop:enable Metrics/MethodLength
227
+
228
+ def decrement_string(str)
229
+ # Simple decrement: "B" -> "A", "A" -> ""
230
+ return "" if str.empty?
231
+ return "" if str == "A"
232
+
233
+ last_char = str[-1]
234
+ if last_char == "a"
235
+ str[0..-2]
236
+ else
237
+ str[0..-2] + (last_char.ord - 1).chr
238
+ end
239
+ end
240
+
241
+ # Description builders
242
+ def build_numeric_description(operator, value, test_values) # rubocop:disable Metrics/MethodLength
243
+ case operator
244
+ when :>=
245
+ "#{value - 1} (below), #{value} (boundary), #{value + 1} (above)"
246
+ when :>
247
+ "#{value} (boundary), #{value + 1} (above)"
248
+ when :<=
249
+ "#{value - 1} (below), #{value} (boundary), #{value + 1} (above)"
250
+ when :<
251
+ "#{value - 1} (below), #{value} (boundary)"
252
+ when :==
253
+ "#{value - 1} (not equal), #{value} (equal), #{value + 1} (not equal)"
254
+ when :!=
255
+ "#{value} (equal)"
256
+ else
257
+ test_values.inspect
258
+ end
259
+ end
260
+
261
+ def build_string_description(operator, str_value, test_values)
262
+ case operator
263
+ when :>=, :>, :<=, :<
264
+ "string comparison: #{test_values.inspect}"
265
+ when :==
266
+ "equal: #{str_value.inspect}, not equal: different string"
267
+ when :!=
268
+ "equal: #{str_value.inspect}, not equal: different string"
269
+ else
270
+ test_values.inspect
271
+ end
272
+ end
273
+ end
274
+ # rubocop:enable Metrics/ClassLength, Lint/DuplicateBranch, Naming/PredicateMethod
275
+ end
276
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  class Igata
4
4
  module Extractors
5
+ # rubocop:disable Metrics/ClassLength, Lint/DuplicateBranch
5
6
  class BranchAnalyzer
6
7
  def self.extract(method_node)
7
8
  new(method_node).extract
@@ -54,6 +55,22 @@ class Igata
54
55
  # Traverse when branches
55
56
  traverse_node(node.body, branches) if node.respond_to?(:body)
56
57
  traverse_node(node.else, branches) if node.respond_to?(:else)
58
+ # Handle RescueNode (container node for rescue clauses)
59
+ elsif node.is_a?(Kanayago::RescueNode)
60
+ # Traverse head (main body before rescue)
61
+ if node.respond_to?(:head) && node.head
62
+ if node.head.is_a?(Array)
63
+ node.head.each { |child| traverse_node(child, branches) }
64
+ else
65
+ traverse_node(node.head, branches)
66
+ end
67
+ end
68
+ traverse_node(node.resq, branches) if node.respond_to?(:resq)
69
+ traverse_node(node.else, branches) if node.respond_to?(:else)
70
+ # Handle RescueBodyNode
71
+ elsif node.is_a?(Kanayago::RescueBodyNode)
72
+ traverse_node(node.body, branches) if node.respond_to?(:body)
73
+ traverse_node(node.next, branches) if node.respond_to?(:next)
57
74
  # Handle ScopeNode (container node)
58
75
  elsif node.is_a?(Kanayago::ScopeNode)
59
76
  traverse_node(node.body, branches) if node.respond_to?(:body)
@@ -92,7 +109,7 @@ class Igata
92
109
  elsif node.is_a?(Kanayago::IntegerNode)
93
110
  node.val.to_s
94
111
  elsif node.is_a?(Kanayago::StringNode)
95
- "\"#{node.val}\""
112
+ "\"#{node.ptr}\""
96
113
  elsif node.is_a?(Kanayago::SymbolNode)
97
114
  ":#{node.ptr}"
98
115
  elsif node.is_a?(Kanayago::OperatorCallNode)
@@ -121,5 +138,6 @@ class Igata
121
138
  end
122
139
  end
123
140
  end
141
+ # rubocop:enable Metrics/ClassLength, Lint/DuplicateBranch
124
142
  end
125
143
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  class Igata
4
4
  module Extractors
5
+ # rubocop:disable Lint/DuplicateBranch
5
6
  class ComparisonAnalyzer
6
7
  COMPARISON_OPERATORS = %i[>= <= > < == !=].freeze
7
8
 
@@ -55,6 +56,22 @@ class Igata
55
56
  elsif node.is_a?(Kanayago::CaseNode)
56
57
  traverse_node(node.body, comparisons) if node.respond_to?(:body)
57
58
  traverse_node(node.else, comparisons) if node.respond_to?(:else)
59
+ # Handle RescueNode (container node for rescue clauses)
60
+ elsif node.is_a?(Kanayago::RescueNode)
61
+ # Traverse head (main body before rescue)
62
+ if node.respond_to?(:head) && node.head
63
+ if node.head.is_a?(Array)
64
+ node.head.each { |child| traverse_node(child, comparisons) }
65
+ else
66
+ traverse_node(node.head, comparisons)
67
+ end
68
+ end
69
+ traverse_node(node.resq, comparisons) if node.respond_to?(:resq)
70
+ traverse_node(node.else, comparisons) if node.respond_to?(:else)
71
+ # Handle RescueBodyNode
72
+ elsif node.is_a?(Kanayago::RescueBodyNode)
73
+ traverse_node(node.body, comparisons) if node.respond_to?(:body)
74
+ traverse_node(node.next, comparisons) if node.respond_to?(:next)
58
75
  # Handle ScopeNode (container node)
59
76
  elsif node.is_a?(Kanayago::ScopeNode)
60
77
  traverse_node(node.body, comparisons) if node.respond_to?(:body)
@@ -78,7 +95,7 @@ class Igata
78
95
  elsif node.is_a?(Kanayago::IntegerNode)
79
96
  node.val.to_s
80
97
  elsif node.is_a?(Kanayago::StringNode)
81
- "\"#{node.val}\""
98
+ "\"#{node.ptr}\""
82
99
  elsif node.is_a?(Kanayago::SymbolNode)
83
100
  ":#{node.ptr}"
84
101
  elsif node.respond_to?(:mid)
@@ -97,5 +114,6 @@ class Igata
97
114
  "#{left} #{operator} #{right}"
98
115
  end
99
116
  end
117
+ # rubocop:enable Lint/DuplicateBranch
100
118
  end
101
119
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  class Igata
4
4
  module Extractors
5
+ # rubocop:disable Metrics/ClassLength
5
6
  class ConstantPath
6
7
  def self.extract(ast)
7
8
  new(ast).extract
@@ -9,15 +10,28 @@ class Igata
9
10
 
10
11
  def initialize(ast)
11
12
  @ast = ast
13
+ # If ast.body is BlockNode, find the ClassNode inside it
14
+ @class_node = find_class_node(ast.body)
12
15
  end
13
16
 
14
- def extract # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
17
+ def find_class_node(node)
18
+ if node.is_a?(Kanayago::ClassNode) || node.is_a?(Kanayago::ModuleNode)
19
+ node
20
+ elsif node.is_a?(Kanayago::BlockNode) && node.respond_to?(:find)
21
+ node.find { |n| n.is_a?(Kanayago::ClassNode) || n.is_a?(Kanayago::ModuleNode) }
22
+ end
23
+ end
24
+
25
+ def extract # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
26
+ return nil unless @class_node
27
+ return nil unless @class_node.respond_to?(:cpath) && @class_node.cpath
28
+
15
29
  if compact_nested? && nested?
16
30
  # Mixed pattern: class App::User; class Profile; end; end
17
31
  # Inner class may also be compact nested: class App::Model; class User::Profile; end
18
32
  # May be deeply nested: class App::Model; class Admin::User; class Profile; end; end; end
19
33
  compact_path = extract_compact_nested_path
20
- nested_path = build_nested_path(@ast.body)
34
+ nested_path = build_nested_path(@class_node)
21
35
  path = "#{compact_path}::#{nested_path}"
22
36
 
23
37
  Values::ConstantPath.new(
@@ -37,8 +51,8 @@ class Igata
37
51
  # Regular nested pattern: class User; class Profile; end; end
38
52
  # Inner class may also be compact nested: class User; class App::Profile; end
39
53
  # May be deeply nested: class User; class Admin::User; class Profile; end; end; end
40
- namespace = @ast.body.cpath.mid.to_s
41
- nested_path = build_nested_path(@ast.body)
54
+ namespace = @class_node.cpath.mid.to_s
55
+ nested_path = build_nested_path(@class_node)
42
56
  path = "#{namespace}::#{nested_path}"
43
57
 
44
58
  Values::ConstantPath.new(
@@ -49,7 +63,7 @@ class Igata
49
63
  else
50
64
  # Simple pattern: class User
51
65
  Values::ConstantPath.new(
52
- path: @ast.body.cpath.mid.to_s,
66
+ path: @class_node.cpath.mid.to_s,
53
67
  nested: false,
54
68
  compact: false
55
69
  )
@@ -60,12 +74,14 @@ class Igata
60
74
 
61
75
  def compact_nested?
62
76
  # For compact nested pattern (class User::Profile), cpath is Colon2Node with non-nil head
63
- @ast.body.cpath.is_a?(Kanayago::Colon2Node) && !@ast.body.cpath.head.nil?
77
+ return false unless @class_node.respond_to?(:cpath)
78
+
79
+ @class_node.cpath.is_a?(Kanayago::Colon2Node) && !@class_node.cpath.head.nil?
64
80
  end
65
81
 
66
82
  def extract_compact_nested_path
67
83
  # Recursively traverse cpath to build complete path
68
- build_constant_path(@ast.body.cpath)
84
+ build_constant_path(@class_node.cpath)
69
85
  end
70
86
 
71
87
  def build_constant_path(node) # rubocop:disable Metrics/MethodLength
@@ -87,7 +103,10 @@ class Igata
87
103
  end
88
104
 
89
105
  def nested?
90
- class_body = @ast.body.body.body
106
+ return false unless @class_node.respond_to?(:body)
107
+ return false unless @class_node.body.respond_to?(:body)
108
+
109
+ class_body = @class_node.body.body
91
110
  # For empty classes, class_body is BeginNode which doesn't have any?
92
111
  return false unless class_body.respond_to?(:any?)
93
112
 
@@ -123,7 +142,7 @@ class Igata
123
142
 
124
143
  def find_nested_constant_node
125
144
  # Recursively find the deepest (innermost) class/module definition
126
- find_deepest_nested_constant_node(@ast.body)
145
+ find_deepest_nested_constant_node(@class_node)
127
146
  end
128
147
 
129
148
  def find_deepest_nested_constant_node(parent_node)
@@ -138,5 +157,6 @@ class Igata
138
157
  deeper_child || direct_child
139
158
  end
140
159
  end
160
+ # rubocop:enable Metrics/ClassLength
141
161
  end
142
162
  end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Igata
4
+ module Extractors
5
+ # rubocop:disable Metrics/ClassLength, Lint/DuplicateBranch
6
+ class ExceptionAnalyzer
7
+ def self.extract(method_node)
8
+ new(method_node).extract
9
+ end
10
+
11
+ def initialize(method_node)
12
+ @method_node = method_node
13
+ end
14
+
15
+ def extract
16
+ return [] unless @method_node
17
+ return [] unless @method_node.respond_to?(:defn)
18
+
19
+ exceptions = []
20
+ # DefinitionNode has a defn field that contains ScopeNode
21
+ defn_node = @method_node.defn
22
+ traverse_node(defn_node, exceptions) if defn_node
23
+ exceptions
24
+ end
25
+
26
+ private
27
+
28
+ def traverse_node(node, exceptions) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
29
+ return unless node
30
+
31
+ # Handle raise statement (FunctionCallNode with mid == :raise)
32
+ if node.is_a?(Kanayago::FunctionCallNode) && node.respond_to?(:mid) && node.mid.to_s == "raise"
33
+ exceptions << extract_raise_info(node)
34
+ # Handle rescue clause
35
+ elsif node.is_a?(Kanayago::RescueNode)
36
+ extract_rescue_info(node, exceptions)
37
+ # Continue traversing head (main body before rescue), rescue clause, and else
38
+ if node.respond_to?(:head) && node.head
39
+ if node.head.is_a?(Array)
40
+ node.head.each { |child| traverse_node(child, exceptions) }
41
+ else
42
+ traverse_node(node.head, exceptions)
43
+ end
44
+ end
45
+ traverse_node(node.resq, exceptions) if node.respond_to?(:resq)
46
+ traverse_node(node.else, exceptions) if node.respond_to?(:else)
47
+ # Handle RescueBody node (contains exception class list)
48
+ elsif node.is_a?(Kanayago::RescueBodyNode)
49
+ extract_rescue_body_info(node, exceptions)
50
+ traverse_node(node.body, exceptions) if node.respond_to?(:body)
51
+ traverse_node(node.next, exceptions) if node.respond_to?(:next)
52
+ # Handle IfStatementNode (raise might be inside if)
53
+ elsif node.is_a?(Kanayago::IfStatementNode)
54
+ traverse_node(node.body, exceptions) if node.respond_to?(:body)
55
+ traverse_node(node.elsif, exceptions) if node.respond_to?(:elsif)
56
+ traverse_node(node.else, exceptions) if node.respond_to?(:else)
57
+ # Handle UnlessStatementNode
58
+ elsif node.is_a?(Kanayago::UnlessStatementNode)
59
+ traverse_node(node.body, exceptions) if node.respond_to?(:body)
60
+ traverse_node(node.else, exceptions) if node.respond_to?(:else)
61
+ # Handle CaseNode
62
+ elsif node.is_a?(Kanayago::CaseNode)
63
+ traverse_node(node.body, exceptions) if node.respond_to?(:body)
64
+ traverse_node(node.else, exceptions) if node.respond_to?(:else)
65
+ # Handle ScopeNode (container node)
66
+ elsif node.is_a?(Kanayago::ScopeNode)
67
+ traverse_node(node.body, exceptions) if node.respond_to?(:body)
68
+ # Handle BlockNode (container for multiple statements)
69
+ elsif node.is_a?(Kanayago::BlockNode)
70
+ node.each { |child| traverse_node(child, exceptions) } if node.respond_to?(:each)
71
+ # Handle other container nodes
72
+ elsif node.respond_to?(:body) && node.body.respond_to?(:each)
73
+ node.body.each { |child| traverse_node(child, exceptions) }
74
+ end
75
+ end
76
+
77
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockNesting
78
+ def extract_raise_info(call_node)
79
+ # Extract exception class and message from raise statement
80
+ # raise ArgumentError, "message" => args is ListNode with val array
81
+ exception_class = "StandardError" # default
82
+ message = nil
83
+
84
+ if call_node.respond_to?(:args) && call_node.args
85
+ args_node = call_node.args
86
+
87
+ # ListNode contains exception class and message in val array
88
+ if args_node.is_a?(Kanayago::ListNode) && args_node.respond_to?(:val)
89
+ args_array = args_node.val
90
+
91
+ if args_array.is_a?(Array)
92
+ # First argument is exception class (if constant) or message (if string)
93
+ first_arg = args_array[0]
94
+ if first_arg
95
+ if first_arg.is_a?(Kanayago::ConstantNode)
96
+ exception_class = first_arg.vid.to_s
97
+ elsif first_arg.is_a?(Kanayago::StringNode)
98
+ # raise "message" - just a string message (StandardError)
99
+ message = first_arg.ptr
100
+ end
101
+ end
102
+
103
+ # Second argument is message (if exists)
104
+ second_arg = args_array[1]
105
+ message = second_arg.ptr if second_arg.is_a?(Kanayago::StringNode)
106
+ end
107
+ end
108
+ end
109
+
110
+ context = build_raise_context(exception_class, message)
111
+ Values::ExceptionInfo.new(
112
+ type: :raise,
113
+ exception_class: exception_class,
114
+ message: message,
115
+ context: context
116
+ )
117
+ end
118
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockNesting
119
+
120
+ def extract_rescue_info(rescue_node, exceptions)
121
+ # RescueNode might contain multiple rescue bodies
122
+ # We'll handle this via RescueBodyNode traversal
123
+ end
124
+
125
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
126
+ def extract_rescue_body_info(rescue_body_node, exceptions)
127
+ # Extract exception class from rescue clause
128
+ # rescue ArgumentError => e
129
+ # rescue ArgumentError, StandardError => e
130
+ exception_classes = []
131
+
132
+ if rescue_body_node.respond_to?(:args) && rescue_body_node.args
133
+ args_node = rescue_body_node.args
134
+
135
+ # args is ListNode with val array containing exception classes
136
+ if args_node.is_a?(Kanayago::ListNode) && args_node.respond_to?(:val)
137
+ args_array = args_node.val
138
+
139
+ if args_array.is_a?(Array)
140
+ args_array.each do |arg|
141
+ exception_classes << arg.vid.to_s if arg.is_a?(Kanayago::ConstantNode)
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ # If no exceptions specified, it catches StandardError
148
+ exception_classes = ["StandardError"] if exception_classes.empty?
149
+
150
+ exception_classes.each do |exc_class|
151
+ context = "rescue #{exc_class}"
152
+ exceptions << Values::ExceptionInfo.new(
153
+ type: :rescue,
154
+ exception_class: exc_class,
155
+ message: nil,
156
+ context: context
157
+ )
158
+ end
159
+ end
160
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
161
+
162
+ def build_raise_context(exception_class, message)
163
+ if message
164
+ "raise #{exception_class}, \"#{message}\""
165
+ else
166
+ "raise #{exception_class}"
167
+ end
168
+ end
169
+ end
170
+ # rubocop:enable Metrics/ClassLength, Lint/DuplicateBranch
171
+ end
172
+ end
@@ -11,6 +11,7 @@ class Igata
11
11
  @class_node = class_node
12
12
  end
13
13
 
14
+ # rubocop:disable Metrics/MethodLength
14
15
  def extract
15
16
  class_body = @class_node.body.body
16
17
  # For empty classes, class_body is BeginNode which doesn't have filter_map
@@ -25,9 +26,22 @@ class Igata
25
26
  # Extract comparison information for each method
26
27
  comparisons = ComparisonAnalyzer.extract(node)
27
28
 
28
- Values::MethodInfo.new(name: node.mid.to_s, branches: branches, comparisons: comparisons)
29
+ # Extract exception information for each method
30
+ exceptions = ExceptionAnalyzer.extract(node)
31
+
32
+ # Extract boundary value suggestions for each method
33
+ boundary_values = BoundaryValueGenerator.extract(comparisons)
34
+
35
+ Values::MethodInfo.new(
36
+ name: node.mid.to_s,
37
+ branches: branches,
38
+ comparisons: comparisons,
39
+ exceptions: exceptions,
40
+ boundary_values: boundary_values
41
+ )
29
42
  end
30
43
  end
44
+ # rubocop:enable Metrics/MethodLength
31
45
  end
32
46
  end
33
47
  end