igata 0.2.1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -0
- data/QUICKSTART.md +37 -0
- data/README.md +104 -1
- data/Rakefile +135 -0
- data/USAGE.md +441 -0
- data/exe/igata +2 -2
- data/lib/igata/extractors/argument_extractor.rb +99 -0
- data/lib/igata/extractors/boundary_value_generator.rb +276 -0
- data/lib/igata/extractors/branch_analyzer.rb +19 -1
- data/lib/igata/extractors/comparison_analyzer.rb +19 -1
- data/lib/igata/extractors/constant_path.rb +29 -9
- data/lib/igata/extractors/exception_analyzer.rb +172 -0
- data/lib/igata/extractors/method_names.rb +15 -1
- data/lib/igata/formatters/base.rb +17 -1
- data/lib/igata/formatters/minitest.rb +0 -17
- data/lib/igata/formatters/minitest_spec.rb +15 -0
- data/lib/igata/formatters/rspec.rb +0 -17
- data/lib/igata/formatters/templates/minitest/method.erb +36 -0
- data/lib/igata/formatters/templates/minitest_spec/class.erb +13 -0
- data/lib/igata/formatters/templates/minitest_spec/method.erb +47 -0
- data/lib/igata/formatters/templates/rspec/method.erb +36 -0
- data/lib/igata/values.rb +35 -5
- data/lib/igata/version.rb +1 -1
- data/lib/igata.rb +46 -6
- metadata +10 -3
data/USAGE.md
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# Real-World Usage Scenarios
|
|
2
|
+
|
|
3
|
+
This document describes practical scenarios where Igata helps with test development in real projects.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Scenario 1: TDD for New Features](#scenario-1-tdd-for-new-features)
|
|
8
|
+
- [Scenario 2: Adding Tests to Legacy Code](#scenario-2-adding-tests-to-legacy-code)
|
|
9
|
+
- [Scenario 3: Rails Application Testing](#scenario-3-rails-application-testing)
|
|
10
|
+
- [Scenario 4: Refactoring with Safety Net](#scenario-4-refactoring-with-safety-net)
|
|
11
|
+
- [Scenario 5: Code Review - Test Coverage Check](#scenario-5-code-review---test-coverage-check)
|
|
12
|
+
- [Scenario 6: Team Development - Standardizing Test Skeletons](#scenario-6-team-development---standardizing-test-skeletons)
|
|
13
|
+
- [Scenario 7: LLM-Assisted Test Implementation](#scenario-7-llm-assisted-test-implementation)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Scenario 1: TDD for New Features
|
|
18
|
+
|
|
19
|
+
**Context**: You're implementing a new feature using Test-Driven Development.
|
|
20
|
+
|
|
21
|
+
**Workflow**:
|
|
22
|
+
|
|
23
|
+
1. Design your class interface
|
|
24
|
+
2. Generate test skeleton with Igata
|
|
25
|
+
3. Write test logic based on generated hints
|
|
26
|
+
4. Implement the feature to pass tests
|
|
27
|
+
|
|
28
|
+
**Example**:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# 1. Create class interface
|
|
32
|
+
cat > lib/payment_validator.rb << 'EOF'
|
|
33
|
+
class PaymentValidator
|
|
34
|
+
def initialize(amount, currency)
|
|
35
|
+
@amount = amount
|
|
36
|
+
@currency = currency
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def valid?
|
|
40
|
+
return false if @amount <= 0
|
|
41
|
+
return false unless supported_currency?
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def supported_currency?
|
|
46
|
+
@currency == "USD" || @currency == "EUR" || @currency == "JPY"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
EOF
|
|
50
|
+
|
|
51
|
+
# 2. Generate test skeleton
|
|
52
|
+
igata lib/payment_validator.rb > test/payment_validator_test.rb
|
|
53
|
+
|
|
54
|
+
# 3. Review generated hints
|
|
55
|
+
cat test/payment_validator_test.rb
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Output shows:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
def test_valid?
|
|
62
|
+
# Branches: if (@amount <= 0), unless (supported_currency?)
|
|
63
|
+
# Comparisons: <= (@amount <= 0)
|
|
64
|
+
skip "Not implemented yet"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_supported_currency?
|
|
68
|
+
# Comparisons: == (@currency == "USD"), == (@currency == "EUR"), == (@currency == "JPY")
|
|
69
|
+
skip "Not implemented yet"
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Benefits**:
|
|
74
|
+
- Branch hints suggest testing both positive and negative amounts
|
|
75
|
+
- Comparison hints suggest boundary value testing (0, negative, positive)
|
|
76
|
+
- Currency comparison hints suggest testing each supported currency plus unsupported ones
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Scenario 2: Adding Tests to Legacy Code
|
|
81
|
+
|
|
82
|
+
**Context**: You need to add tests to existing code without tests.
|
|
83
|
+
|
|
84
|
+
**Workflow**:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Generate test skeleton for existing file
|
|
88
|
+
igata lib/legacy/order_processor.rb > test/legacy/order_processor_test.rb
|
|
89
|
+
|
|
90
|
+
# Review complexity from generated comments
|
|
91
|
+
# High number of branches/comparisons → complex method needing refactoring
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Example**:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
# Generated test reveals complexity
|
|
98
|
+
def test_process_order
|
|
99
|
+
# Branches: if (order.paid?), if (order.shippable?), case (order.status), if (inventory.available?(order.items)), unless (order.address.valid?)
|
|
100
|
+
# Comparisons: > (order.total > 1000), >= (customer.age >= 18), == (order.priority == "high")
|
|
101
|
+
skip "Not implemented yet"
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Benefits**:
|
|
106
|
+
- Quickly see method complexity from comment density
|
|
107
|
+
- Branch/comparison hints guide edge case testing
|
|
108
|
+
- Identify refactoring opportunities before writing tests
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Scenario 3: Rails Application Testing
|
|
113
|
+
|
|
114
|
+
**Context**: Testing ActiveRecord models and service objects.
|
|
115
|
+
|
|
116
|
+
### ActiveRecord Models
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Generate test for User model
|
|
120
|
+
igata app/models/user.rb > test/models/user_test.rb
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Example Model**:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
class User < ApplicationRecord
|
|
127
|
+
def adult?
|
|
128
|
+
age >= 18
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def display_name
|
|
132
|
+
if name.present?
|
|
133
|
+
name
|
|
134
|
+
else
|
|
135
|
+
"Guest #{id}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def subscription_active?
|
|
140
|
+
return false unless subscription_ends_at
|
|
141
|
+
subscription_ends_at > Time.current
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Generated Test**:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
def test_adult?
|
|
150
|
+
# Comparisons: >= (age >= 18)
|
|
151
|
+
skip "Not implemented yet"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def test_display_name
|
|
155
|
+
# Branches: if (name.present?)
|
|
156
|
+
skip "Not implemented yet"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_subscription_active?
|
|
160
|
+
# Branches: unless (subscription_ends_at)
|
|
161
|
+
# Comparisons: > (subscription_ends_at > Time.current)
|
|
162
|
+
skip "Not implemented yet"
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Benefits**:
|
|
167
|
+
- Hints suggest testing boundary ages (17, 18, 19)
|
|
168
|
+
- Branch hints suggest testing with/without name
|
|
169
|
+
- Comparison hints suggest testing time boundaries (expired, current, future)
|
|
170
|
+
|
|
171
|
+
### Service Objects
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Generate RSpec test for service object
|
|
175
|
+
igata app/services/subscription_renewal_service.rb -f rspec > spec/services/subscription_renewal_service_spec.rb
|
|
176
|
+
|
|
177
|
+
# Or generate Minitest Spec test (RSpec-style DSL with Minitest)
|
|
178
|
+
igata app/services/subscription_renewal_service.rb -f minitest_spec > test/services/subscription_renewal_service_spec.rb
|
|
179
|
+
|
|
180
|
+
# Or generate traditional Minitest test
|
|
181
|
+
igata app/services/subscription_renewal_service.rb > test/services/subscription_renewal_service_test.rb
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Scenario 4: Refactoring with Safety Net
|
|
187
|
+
|
|
188
|
+
**Context**: You need to refactor complex methods but want test coverage first.
|
|
189
|
+
|
|
190
|
+
**Workflow**:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# 1. Generate test for current implementation
|
|
194
|
+
igata lib/report_generator.rb > test/report_generator_test.rb
|
|
195
|
+
|
|
196
|
+
# 2. Implement tests based on current behavior
|
|
197
|
+
# 3. Run tests → all pass
|
|
198
|
+
# 4. Refactor code
|
|
199
|
+
# 5. Run tests → ensure behavior unchanged
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Benefits**:
|
|
203
|
+
- Establish baseline behavior before refactoring
|
|
204
|
+
- Branch/comparison hints ensure edge cases are covered
|
|
205
|
+
- Confidence in refactoring safety
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Scenario 5: Code Review - Test Coverage Check
|
|
210
|
+
|
|
211
|
+
**Context**: Reviewing a pull request to ensure adequate test coverage.
|
|
212
|
+
|
|
213
|
+
**Workflow**:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# Generate test skeleton for changed files
|
|
217
|
+
igata lib/new_feature.rb > /tmp/expected_tests.rb
|
|
218
|
+
|
|
219
|
+
# Compare with actual tests in PR
|
|
220
|
+
diff /tmp/expected_tests.rb test/new_feature_test.rb
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Review Checklist**:
|
|
224
|
+
- Are all methods from generated skeleton tested?
|
|
225
|
+
- Are branch cases from comments covered?
|
|
226
|
+
- Are boundary values from comparison comments tested?
|
|
227
|
+
|
|
228
|
+
**Benefits**:
|
|
229
|
+
- Objective test coverage assessment
|
|
230
|
+
- Identify missing test cases quickly
|
|
231
|
+
- Standardized review criteria
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Scenario 6: Team Development - Standardizing Test Skeletons
|
|
236
|
+
|
|
237
|
+
**Context**: Team wants consistent test structure across projects.
|
|
238
|
+
|
|
239
|
+
**Setup**:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# Add to team's coding guidelines
|
|
243
|
+
echo "Generate test skeletons with: igata <file> > test/<file>_test.rb" >> CONTRIBUTING.md
|
|
244
|
+
|
|
245
|
+
# CI check for test existence
|
|
246
|
+
# .github/workflows/ci.yml
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```yaml
|
|
250
|
+
- name: Check test coverage
|
|
251
|
+
run: |
|
|
252
|
+
for file in lib/**/*.rb; do
|
|
253
|
+
test_file="test/$(basename $file .rb)_test.rb"
|
|
254
|
+
if [ ! -f "$test_file" ]; then
|
|
255
|
+
echo "Missing test: $test_file"
|
|
256
|
+
exit 1
|
|
257
|
+
fi
|
|
258
|
+
done
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Benefits**:
|
|
262
|
+
- Consistent test structure across team
|
|
263
|
+
- New members quickly learn testing patterns
|
|
264
|
+
- Automated enforcement of test existence
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Scenario 7: LLM-Assisted Test Implementation
|
|
269
|
+
|
|
270
|
+
**Context**: Using GitHub Copilot, Claude, or ChatGPT to accelerate test writing.
|
|
271
|
+
|
|
272
|
+
**Workflow**:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
# 1. Generate test skeleton with detailed hints
|
|
276
|
+
igata lib/calculator.rb > test/calculator_test.rb
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Generated**:
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
def test_divide
|
|
283
|
+
# Branches: if (divisor == 0)
|
|
284
|
+
# Comparisons: == (divisor == 0)
|
|
285
|
+
skip "Not implemented yet"
|
|
286
|
+
end
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**2. LLM reads comments and suggests**:
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
def test_divide
|
|
293
|
+
# Branches: if (divisor == 0)
|
|
294
|
+
# Comparisons: == (divisor == 0)
|
|
295
|
+
|
|
296
|
+
# Test normal division
|
|
297
|
+
calc = Calculator.new
|
|
298
|
+
assert_equal 2, calc.divide(4, 2)
|
|
299
|
+
|
|
300
|
+
# Test division by zero (branch hint)
|
|
301
|
+
assert_raises(ZeroDivisionError) { calc.divide(4, 0) }
|
|
302
|
+
|
|
303
|
+
# Test boundary cases (comparison hint)
|
|
304
|
+
assert_equal 0, calc.divide(0, 2)
|
|
305
|
+
assert_equal Float::INFINITY, calc.divide(1.0, 0)
|
|
306
|
+
end
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Benefits**:
|
|
310
|
+
- Comments provide context for LLM suggestions
|
|
311
|
+
- More accurate test generation from LLM
|
|
312
|
+
- Branch/comparison hints guide comprehensive coverage
|
|
313
|
+
- Faster test implementation with AI assistance
|
|
314
|
+
|
|
315
|
+
**LLM Tools Compatible**:
|
|
316
|
+
- GitHub Copilot
|
|
317
|
+
- Claude Code
|
|
318
|
+
- ChatGPT
|
|
319
|
+
- Amazon CodeWhisperer
|
|
320
|
+
- Cody (Sourcegraph)
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Integration with Development Tools
|
|
325
|
+
|
|
326
|
+
### Pre-commit Hook
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# .git/hooks/pre-commit
|
|
330
|
+
#!/bin/bash
|
|
331
|
+
for file in $(git diff --cached --name-only --diff-filter=AM | grep '^lib/.*\.rb$'); do
|
|
332
|
+
test_file="test/$(basename $file .rb)_test.rb"
|
|
333
|
+
if [ ! -f "$test_file" ]; then
|
|
334
|
+
echo "Generating test skeleton: $test_file"
|
|
335
|
+
igata "$file" > "$test_file"
|
|
336
|
+
git add "$test_file"
|
|
337
|
+
fi
|
|
338
|
+
done
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Rake Task
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
# lib/tasks/igata.rake
|
|
345
|
+
namespace :igata do
|
|
346
|
+
desc "Generate missing test files"
|
|
347
|
+
task :generate_missing do
|
|
348
|
+
Dir.glob("lib/**/*.rb").each do |file|
|
|
349
|
+
test_file = file.sub("lib/", "test/").sub(".rb", "_test.rb")
|
|
350
|
+
unless File.exist?(test_file)
|
|
351
|
+
puts "Generating: #{test_file}"
|
|
352
|
+
system("igata #{file} > #{test_file}")
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Editor Integration (VS Code)
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
// .vscode/tasks.json
|
|
363
|
+
{
|
|
364
|
+
"version": "2.0.0",
|
|
365
|
+
"tasks": [
|
|
366
|
+
{
|
|
367
|
+
"label": "Generate Test with Igata",
|
|
368
|
+
"type": "shell",
|
|
369
|
+
"command": "igata ${file} > test/$(basename ${file} .rb)_test.rb",
|
|
370
|
+
"presentation": {
|
|
371
|
+
"reveal": "always"
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
]
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Best Practices
|
|
381
|
+
|
|
382
|
+
### 1. Use Branch/Comparison Hints as Test Case Guide
|
|
383
|
+
|
|
384
|
+
**Generated hint**:
|
|
385
|
+
```ruby
|
|
386
|
+
# Branches: if (@age >= 18), unless (@verified)
|
|
387
|
+
# Comparisons: >= (@age >= 18)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**Write tests for**:
|
|
391
|
+
- `@age >= 18` boundary: age = 17, 18, 19
|
|
392
|
+
- `@verified` states: true, false
|
|
393
|
+
- Combinations: (age < 18, verified), (age >= 18, not verified), etc.
|
|
394
|
+
|
|
395
|
+
### 2. Remove `skip`/`pending` After Implementation
|
|
396
|
+
|
|
397
|
+
```ruby
|
|
398
|
+
# Before
|
|
399
|
+
def test_adult?
|
|
400
|
+
# Comparisons: >= (@age >= 18)
|
|
401
|
+
skip "Not implemented yet"
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# After
|
|
405
|
+
def test_adult?
|
|
406
|
+
# Comparisons: >= (@age >= 18)
|
|
407
|
+
user = User.new(name: "Alice", age: 17)
|
|
408
|
+
refute user.adult?
|
|
409
|
+
|
|
410
|
+
user = User.new(name: "Bob", age: 18)
|
|
411
|
+
assert user.adult?
|
|
412
|
+
end
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 3. Keep Comments for Future Reference
|
|
416
|
+
|
|
417
|
+
Comments help:
|
|
418
|
+
- New team members understand test intent
|
|
419
|
+
- Future refactoring efforts
|
|
420
|
+
- LLM tools provide better suggestions
|
|
421
|
+
|
|
422
|
+
### 4. Regenerate After Major Refactoring
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
# Compare old and new structure
|
|
426
|
+
igata lib/refactored_class.rb > /tmp/new_tests.rb
|
|
427
|
+
diff test/refactored_class_test.rb /tmp/new_tests.rb
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## Summary
|
|
433
|
+
|
|
434
|
+
Igata accelerates test development by:
|
|
435
|
+
- **Generating boilerplate** - Focus on test logic, not structure
|
|
436
|
+
- **Providing hints** - Branch/comparison comments guide edge case testing
|
|
437
|
+
- **Standardizing structure** - Consistent tests across team/projects
|
|
438
|
+
- **Enabling LLM assistance** - Comments provide context for better AI suggestions
|
|
439
|
+
- **Revealing complexity** - Comment density indicates refactoring needs
|
|
440
|
+
|
|
441
|
+
Choose the scenarios that fit your workflow and adapt them to your team's practices.
|
data/exe/igata
CHANGED
|
@@ -9,8 +9,8 @@ OptionParser.new do |opts|
|
|
|
9
9
|
opts.banner = "Usage: igata [options] [file]"
|
|
10
10
|
opts.version = Igata::VERSION
|
|
11
11
|
|
|
12
|
-
opts.on("-f", "--formatter FORMATTER", %i[minitest rspec],
|
|
13
|
-
"Test framework formatter (minitest, rspec)") do |formatter|
|
|
12
|
+
opts.on("-f", "--formatter FORMATTER", %i[minitest rspec minitest_spec],
|
|
13
|
+
"Test framework formatter (minitest, rspec, minitest_spec)") do |formatter|
|
|
14
14
|
options[:formatter] = formatter
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Igata
|
|
4
|
+
module Extractors
|
|
5
|
+
# Extracts argument information from method definitions using script_lines
|
|
6
|
+
class ArgumentExtractor
|
|
7
|
+
def self.extract(method_info, script_lines)
|
|
8
|
+
new(method_info, script_lines).extract
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(method_info, script_lines)
|
|
12
|
+
@method_info = method_info
|
|
13
|
+
@script_lines = script_lines
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def extract
|
|
17
|
+
# Find the method definition line
|
|
18
|
+
method_line = find_method_line(@method_info.name)
|
|
19
|
+
return nil unless method_line
|
|
20
|
+
|
|
21
|
+
# Parse arguments
|
|
22
|
+
parse_arguments(method_line)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def find_method_line(method_name)
|
|
28
|
+
@script_lines.find do |line|
|
|
29
|
+
line.strip.start_with?("def #{method_name}")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parse_arguments(method_line)
|
|
34
|
+
# Extract arguments part from method definition
|
|
35
|
+
if method_line =~ /def\s+\w+\s*\((.*?)\)/
|
|
36
|
+
args_str = ::Regexp.last_match(1)
|
|
37
|
+
parse_args_string(args_str)
|
|
38
|
+
else
|
|
39
|
+
# Method without parentheses (no arguments)
|
|
40
|
+
Values::ArgumentInfo.new(args: [])
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def parse_args_string(args_str)
|
|
45
|
+
return Values::ArgumentInfo.new(args: []) if args_str.strip.empty?
|
|
46
|
+
|
|
47
|
+
args = args_str.split(",").map(&:strip).map do |arg|
|
|
48
|
+
parse_single_arg(arg)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
Values::ArgumentInfo.new(args: args)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# rubocop:disable Metrics/MethodLength
|
|
55
|
+
def parse_single_arg(arg)
|
|
56
|
+
case arg
|
|
57
|
+
when /^(\w+)\s*=\s*(.+)$/ # Optional argument: name = "default"
|
|
58
|
+
Values::ArgDetail.new(
|
|
59
|
+
name: ::Regexp.last_match(1),
|
|
60
|
+
type: :optional,
|
|
61
|
+
default: ::Regexp.last_match(2)
|
|
62
|
+
)
|
|
63
|
+
when /^(\w+):\s*(.+)$/ # Keyword argument: verified: false
|
|
64
|
+
Values::ArgDetail.new(
|
|
65
|
+
name: ::Regexp.last_match(1),
|
|
66
|
+
type: :keyword,
|
|
67
|
+
default: ::Regexp.last_match(2)
|
|
68
|
+
)
|
|
69
|
+
when /^(\w+):$/ # Required keyword argument: verified:
|
|
70
|
+
Values::ArgDetail.new(
|
|
71
|
+
name: ::Regexp.last_match(1),
|
|
72
|
+
type: :required_keyword
|
|
73
|
+
)
|
|
74
|
+
when /^\*\*(\w+)$/ # Keyword rest: **kwargs
|
|
75
|
+
Values::ArgDetail.new(
|
|
76
|
+
name: ::Regexp.last_match(1),
|
|
77
|
+
type: :keyrest
|
|
78
|
+
)
|
|
79
|
+
when /^\*(\w+)$/ # Rest: *args
|
|
80
|
+
Values::ArgDetail.new(
|
|
81
|
+
name: ::Regexp.last_match(1),
|
|
82
|
+
type: :rest
|
|
83
|
+
)
|
|
84
|
+
when /^&(\w+)$/ # Block: &block
|
|
85
|
+
Values::ArgDetail.new(
|
|
86
|
+
name: ::Regexp.last_match(1),
|
|
87
|
+
type: :block
|
|
88
|
+
)
|
|
89
|
+
else # Required argument: name
|
|
90
|
+
Values::ArgDetail.new(
|
|
91
|
+
name: arg,
|
|
92
|
+
type: :required
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
# rubocop:enable Metrics/MethodLength
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|