rbs-dynamic 0.1.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 +7 -0
- data/.rspec +3 -0
- data/Gemfile +10 -0
- data/README.md +357 -0
- data/Rakefile +8 -0
- data/exe/rbs-dynamic +5 -0
- data/lib/bs/dynamic/trace.rb +1 -0
- data/lib/rbs/dynamic/builder/methods.rb +144 -0
- data/lib/rbs/dynamic/builder/types.rb +194 -0
- data/lib/rbs/dynamic/builder.rb +216 -0
- data/lib/rbs/dynamic/cli.rb +38 -0
- data/lib/rbs/dynamic/config.rb +67 -0
- data/lib/rbs/dynamic/converter/called_method_to_sigunature.rb +78 -0
- data/lib/rbs/dynamic/converter/called_method_to_with_interface.rb +44 -0
- data/lib/rbs/dynamic/converter/trace_to_rbs.rb +144 -0
- data/lib/rbs/dynamic/refine/basic_object_with_kernel.rb +11 -0
- data/lib/rbs/dynamic/refine/each_called_method.rb +34 -0
- data/lib/rbs/dynamic/refine/signature_merger.rb +175 -0
- data/lib/rbs/dynamic/refine/trace_point.rb +211 -0
- data/lib/rbs/dynamic/trace.rb +30 -0
- data/lib/rbs/dynamic/tracer/called_method.rb +256 -0
- data/lib/rbs/dynamic/tracer.rb +11 -0
- data/lib/rbs/dynamic/version.rb +7 -0
- data/lib/rbs/dynamic.rb +49 -0
- data/rbs-dynamic.gemspec +38 -0
- data/sample/csv/sample.rb +17 -0
- data/sample/fizzbuzz/sample.rb +36 -0
- data/sample/fizzbuzz2/sample.rb +66 -0
- data/sig/rbs/dynamic.rbs +6 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b65ad7101c34b64bef15a470f524106c0f62d09dc0cb3370396e4c7f53e02abe
|
4
|
+
data.tar.gz: a0b6bf528bc54a8c4611d49525d0d43913094ec01895cb0cc0cdd742d1245be0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 36166e8bab6c6feffad55cde495670ba6448723554ef37b3bca086fa43f4a55cdaeebc91d65e0fdd65a9c92a17cec1c38f33081835017b3c63614e7d7d8f278e
|
7
|
+
data.tar.gz: a4d7c083c7b5b60e85b567ca3c319af49b605c065a236d7baa942e5bb82d0d850f53ff57f3ae340b669223d95c9d4e639b57eb24117d0a5f90e8daeddf64b000
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,357 @@
|
|
1
|
+
[](https://github.com/osyo-manga/gem-rbs-dynamic/actions/workflows/main.yml)
|
2
|
+
|
3
|
+
# RBS::Dynamic
|
4
|
+
|
5
|
+
`RBS::Dynamic` is a tool to dynamically analyze Ruby code and generate RBS
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install the gem and add to the application's Gemfile by executing:
|
10
|
+
|
11
|
+
$ bundle add rbs-dynamic
|
12
|
+
|
13
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
14
|
+
|
15
|
+
$ gem install rbs-dynamic
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Execute any Ruby script file with the `rbs-dynamic` command and generate RBS based on the executed information.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# sample.rb
|
23
|
+
class FizzBuzz
|
24
|
+
def initialize(value)
|
25
|
+
@value = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def value; @value end
|
29
|
+
|
30
|
+
def apply
|
31
|
+
value % 15 == 0 ? "FizzBuzz"
|
32
|
+
: value % 3 == 0 ? "Fizz"
|
33
|
+
: value % 5 == 0 ? "Buzz"
|
34
|
+
: value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
p (1..20).map { FizzBuzz.new(_1).apply }
|
39
|
+
```
|
40
|
+
|
41
|
+
```shell
|
42
|
+
$ rbs-dynamic trace sample.rb
|
43
|
+
# RBS dynamic trace 0.1.0
|
44
|
+
|
45
|
+
class FizzBuzz
|
46
|
+
private def initialize: (Integer value) -> Integer
|
47
|
+
|
48
|
+
def apply: () -> (Integer | String)
|
49
|
+
|
50
|
+
def value: () -> Integer
|
51
|
+
|
52
|
+
@value: Integer
|
53
|
+
end
|
54
|
+
$
|
55
|
+
```
|
56
|
+
|
57
|
+
NOTE: In this case, no standard output is done when Ruby is executed.
|
58
|
+
|
59
|
+
|
60
|
+
#### Type supported by rbs-dynamic
|
61
|
+
|
62
|
+
* [x] class / module
|
63
|
+
* [x] super class
|
64
|
+
* [x] `include / prepend / extend`
|
65
|
+
* [x] instance variables
|
66
|
+
* [x] constant variables
|
67
|
+
* [ ] class variables
|
68
|
+
* [x] Method
|
69
|
+
* argument types
|
70
|
+
* return type
|
71
|
+
* block
|
72
|
+
* visibility
|
73
|
+
* class methods
|
74
|
+
* [x] literal types (e.g. `1` `:hoge`)
|
75
|
+
* `String` literals are not supported.
|
76
|
+
* [x] Generics types
|
77
|
+
* [x] `Array`
|
78
|
+
* [x] `Hash`
|
79
|
+
* [x] `Range`
|
80
|
+
* [ ] `Enumerable`
|
81
|
+
* [ ] `Struct`
|
82
|
+
* [ ] Record types
|
83
|
+
* [ ] Tuple types
|
84
|
+
|
85
|
+
|
86
|
+
### commandline options
|
87
|
+
|
88
|
+
```shell
|
89
|
+
$ rbs-dynamic --help trace
|
90
|
+
Usage:
|
91
|
+
rbs-dynamic trace [filename]
|
92
|
+
|
93
|
+
Options:
|
94
|
+
[--root-path=ROOT-PATH] # Rooting path. Default: current dir
|
95
|
+
# Default: /home/mayu/Dropbox/work/software/development/gem/rbs-dynamic
|
96
|
+
[--target-filepath-pattern=TARGET-FILEPATH-PATTERN] # Target filepath pattern. e.g. hoge\|foo\|bar. Default '.*'
|
97
|
+
# Default: .*
|
98
|
+
[--ignore-filepath-pattern=IGNORE-FILEPATH-PATTERN] # Ignore filepath pattern. Priority over `target-filepath-pattern`. e.g. hoge\|foo\|bar. Default ''
|
99
|
+
[--target-classname-pattern=TARGET-CLASSNAME-PATTERN] # Target class name pattern. e.g. RBS::Dynamic. Default '.*'
|
100
|
+
# Default: .*
|
101
|
+
[--ignore-classname-pattern=IGNORE-CLASSNAME-PATTERN] # Ignore class name pattern. Priority over `target-classname-pattern`. e.g. PP\|PrettyPrint. Default ''
|
102
|
+
[--ignore-class-members=one two three]
|
103
|
+
# Possible values: inclued_modules, prepended_modules, extended_modules, constant_variables, instance_variables, singleton_methods, methods
|
104
|
+
[--method-defined-calsses=one two three] # Which class defines method type. Default: defined_class and receiver_class
|
105
|
+
# Possible values: defined_class, receiver_class
|
106
|
+
[--show-method-location], [--no-show-method-location] # Show source_location and called_location in method comments. Default: no
|
107
|
+
[--use-literal-type], [--no-use-literal-type] # Integer and Symbol as literal types. e.g func(:hoge, 42). Default: no
|
108
|
+
[--with-literal-type], [--no-with-literal-type] # Integer and Symbol with literal types. e.g func(Symbol | :hoge | :foo). Default: no
|
109
|
+
[--use-interface-method-argument], [--no-use-interface-method-argument] # Define method arguments in interface. Default: no
|
110
|
+
[--stdout], [--no-stdout] # stdout at runtime. Default: no
|
111
|
+
[--trace-c-api-method], [--no-trace-c-api-method] # Trace C API method. Default: no
|
112
|
+
```
|
113
|
+
|
114
|
+
#### `--method-defined-calsses`
|
115
|
+
|
116
|
+
Specify the class to be defined.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class Base
|
120
|
+
def func; end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Sub1 < Base
|
124
|
+
end
|
125
|
+
|
126
|
+
class Sub2 < Base
|
127
|
+
end
|
128
|
+
|
129
|
+
Sub1.new.func
|
130
|
+
Sub2.new.func
|
131
|
+
```
|
132
|
+
|
133
|
+
```shell
|
134
|
+
# defined_class and receiver_class
|
135
|
+
$ rbs-dynamic trace sample.rb
|
136
|
+
# RBS dynamic trace 0.1.0
|
137
|
+
|
138
|
+
class Base
|
139
|
+
def func: () -> NilClass
|
140
|
+
end
|
141
|
+
|
142
|
+
class Sub1 < Base
|
143
|
+
def func: () -> NilClass
|
144
|
+
end
|
145
|
+
|
146
|
+
class Sub2 < Base
|
147
|
+
def func: () -> NilClass
|
148
|
+
end
|
149
|
+
$
|
150
|
+
```
|
151
|
+
|
152
|
+
```shell
|
153
|
+
# only defined class
|
154
|
+
$ rbs-dynamic trace sample.rb --method-defined-calsses=defined_class
|
155
|
+
# RBS dynamic trace 0.1.0
|
156
|
+
|
157
|
+
class Base
|
158
|
+
def func: () -> NilClass
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
```shell
|
163
|
+
# only receiver class
|
164
|
+
$ rbs-dynamic trace sample.rb --method-defined-calsses=receiver_class
|
165
|
+
# RBS dynamic trace 0.1.0
|
166
|
+
|
167
|
+
class Base
|
168
|
+
end
|
169
|
+
|
170
|
+
class Sub1 < Base
|
171
|
+
def func: () -> NilClass
|
172
|
+
end
|
173
|
+
|
174
|
+
class Sub2 < Base
|
175
|
+
def func: () -> NilClass
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
#### `--show-method-location`
|
180
|
+
|
181
|
+
Add method definition location and reference location to comments.
|
182
|
+
|
183
|
+
```
|
184
|
+
# sample.rb
|
185
|
+
class X
|
186
|
+
def func1(a)
|
187
|
+
end
|
188
|
+
|
189
|
+
def func2
|
190
|
+
func1(42)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
x = X.new
|
195
|
+
x.func1("homu")
|
196
|
+
x.func2
|
197
|
+
```
|
198
|
+
|
199
|
+
```shell
|
200
|
+
$ rbs-dynamic trace sample.rb --show-method-location
|
201
|
+
# RBS dynamic trace 0.1.0
|
202
|
+
|
203
|
+
class X
|
204
|
+
# source location: sample.rb:2
|
205
|
+
# reference location:
|
206
|
+
# func1(String a) -> NilClass sample.rb:11
|
207
|
+
# func1(Integer a) -> NilClass sample.rb:6
|
208
|
+
def func1: (String | Integer a) -> NilClass
|
209
|
+
|
210
|
+
# source location: sample.rb:5
|
211
|
+
# reference location:
|
212
|
+
# func2() -> NilClass sample.rb:12
|
213
|
+
def func2: () -> NilClass
|
214
|
+
end
|
215
|
+
$
|
216
|
+
```
|
217
|
+
|
218
|
+
#### `--use-literal-type`
|
219
|
+
|
220
|
+
Use Symbol literal or Integer literal as type.
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
# sample.rb
|
224
|
+
class X
|
225
|
+
def func(a)
|
226
|
+
a.to_s
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
x = X.new
|
231
|
+
x.func(1)
|
232
|
+
x.func(2)
|
233
|
+
x.func(:hoge)
|
234
|
+
x.func(:foo)
|
235
|
+
```
|
236
|
+
|
237
|
+
```shell
|
238
|
+
# Not used options
|
239
|
+
$ ./exe/rbs-dynamic trace sample.rb
|
240
|
+
# RBS dynamic trace 0.1.0
|
241
|
+
|
242
|
+
class X
|
243
|
+
def func: (Integer | Symbol a) -> String
|
244
|
+
end
|
245
|
+
$
|
246
|
+
```
|
247
|
+
|
248
|
+
```shell
|
249
|
+
# Used options
|
250
|
+
$ rbs-dynamic trace sample.rb --use-literal-type
|
251
|
+
# RBS dynamic trace 0.1.0
|
252
|
+
|
253
|
+
class X
|
254
|
+
def func: (1 | 2 | :hoge | :foo a) -> String
|
255
|
+
end
|
256
|
+
rbs-dynamic $
|
257
|
+
$
|
258
|
+
```
|
259
|
+
|
260
|
+
#### `--with-literal-type`
|
261
|
+
|
262
|
+
Use Symbol literal or Integer literal as type and union original type
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
# sample.rb
|
266
|
+
class X
|
267
|
+
def func(a)
|
268
|
+
a.to_s
|
269
|
+
end
|
270
|
+
|
271
|
+
def func2(a)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
x = X.new
|
276
|
+
x.func(1)
|
277
|
+
x.func(2)
|
278
|
+
x.func(:hoge)
|
279
|
+
x.func(:foo)
|
280
|
+
x.func2({ id: 1, name: "homu", age: 14 })
|
281
|
+
```
|
282
|
+
|
283
|
+
```shell
|
284
|
+
$ rbs-dynamic trace sample.rb --with-literal-type
|
285
|
+
# RBS dynamic trace 0.1.0
|
286
|
+
|
287
|
+
class X
|
288
|
+
def func: (Integer | Symbol | 1 | 2 | :hoge | :foo a) -> String
|
289
|
+
|
290
|
+
def func2: (Hash[Symbol | :id | :name | :age, Integer | String | 1 | 14] a) -> NilClass
|
291
|
+
end
|
292
|
+
$
|
293
|
+
```
|
294
|
+
|
295
|
+
|
296
|
+
#### `--use-interface-method-argument`
|
297
|
+
|
298
|
+
Define and use interface type.
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
# sample.rb
|
302
|
+
class Output
|
303
|
+
def my_puts(a)
|
304
|
+
puts a.to_s
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
class Cat
|
309
|
+
def to_s
|
310
|
+
"Cat"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
class Dog
|
315
|
+
def to_s
|
316
|
+
"Dog"
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
output = Output.new
|
321
|
+
|
322
|
+
output.my_puts Cat.new
|
323
|
+
output.my_puts Dog.new
|
324
|
+
```
|
325
|
+
|
326
|
+
```shell
|
327
|
+
$ rbs-dynamic trace sample.rb --use-interface-method-argument
|
328
|
+
# RBS dynamic trace 0.1.0
|
329
|
+
|
330
|
+
class Output
|
331
|
+
def my_puts: (_Interface_have__to_s__1 a) -> NilClass
|
332
|
+
|
333
|
+
interface _Interface_have__to_s__1
|
334
|
+
def to_s: () -> String
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
class Cat
|
339
|
+
def to_s: () -> String
|
340
|
+
end
|
341
|
+
|
342
|
+
class Dog
|
343
|
+
def to_s: () -> String
|
344
|
+
end
|
345
|
+
$
|
346
|
+
```
|
347
|
+
|
348
|
+
|
349
|
+
## Development
|
350
|
+
|
351
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
352
|
+
|
353
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
354
|
+
|
355
|
+
## Contributing
|
356
|
+
|
357
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/osyo-manga/gem-rbs-dynamic.
|
data/Rakefile
ADDED
data/exe/rbs-dynamic
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "../../rbs/dynamic/trace.rb"
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rbs"
|
4
|
+
require_relative "./../refine/signature_merger.rb"
|
5
|
+
require_relative "./types.rb"
|
6
|
+
|
7
|
+
module RBS module Dynamic module Builder
|
8
|
+
class Methods
|
9
|
+
using Refine::SignatureMerger
|
10
|
+
using Refine::SignatureMerger::AsA
|
11
|
+
|
12
|
+
using Module.new {
|
13
|
+
refine String do
|
14
|
+
def comment
|
15
|
+
RBS::AST::Comment.new(string: self, location: nil)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
refine Array do
|
20
|
+
def func_params
|
21
|
+
map { |param| param.func_param }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
refine Hash do
|
26
|
+
def func_param(name: self[:name])
|
27
|
+
RBS::Types::Function::Param.new(
|
28
|
+
type: has_key?(:type) ? Types.new(self[:type]).build : Types::ANY,
|
29
|
+
name: name,
|
30
|
+
location: nil
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def required_positionals
|
35
|
+
self[:required_positionals]&.func_params || []
|
36
|
+
end
|
37
|
+
|
38
|
+
def optional_positionals
|
39
|
+
self[:optional_positionals]&.func_params || []
|
40
|
+
end
|
41
|
+
|
42
|
+
def rest_positionals
|
43
|
+
self[:rest_positionals]&.func_params || []
|
44
|
+
end
|
45
|
+
|
46
|
+
def trailing_positionals
|
47
|
+
self[:trailing_positionals]&.func_params || []
|
48
|
+
end
|
49
|
+
|
50
|
+
def required_keywords
|
51
|
+
self[:required_keywords]&.to_h { |sig|
|
52
|
+
[sig[:name], sig.func_param(name: nil)]
|
53
|
+
} || []
|
54
|
+
end
|
55
|
+
|
56
|
+
def optional_keywords
|
57
|
+
self[:optional_keywords]&.to_h { |sig|
|
58
|
+
[sig[:name], sig.func_param(name: nil)]
|
59
|
+
} || []
|
60
|
+
end
|
61
|
+
|
62
|
+
def rest_keywords
|
63
|
+
self[:rest_keywords]&.func_params || []
|
64
|
+
end
|
65
|
+
|
66
|
+
def return_type
|
67
|
+
Types.new(self.fetch(:return_type, []).as_a).build || Types::VOID
|
68
|
+
end
|
69
|
+
|
70
|
+
def block_type
|
71
|
+
return if self[:block].nil? || self[:block].empty?
|
72
|
+
|
73
|
+
RBS::Types::Block.new(
|
74
|
+
type: self[:block].zip_sig!.function_type,
|
75
|
+
required: false
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def function_type
|
80
|
+
RBS::Types::Function.new(
|
81
|
+
required_positionals: required_positionals,
|
82
|
+
optional_positionals: optional_positionals,
|
83
|
+
rest_positionals: rest_positionals.first,
|
84
|
+
trailing_positionals: trailing_positionals,
|
85
|
+
required_keywords: required_keywords,
|
86
|
+
optional_keywords: optional_keywords,
|
87
|
+
rest_keywords: rest_keywords.first,
|
88
|
+
return_type: return_type
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def method_type
|
93
|
+
RBS::MethodType.new(
|
94
|
+
type_params: [],
|
95
|
+
type: function_type,
|
96
|
+
block: block_type,
|
97
|
+
location: nil
|
98
|
+
)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
}
|
102
|
+
|
103
|
+
attr_reader :sigs
|
104
|
+
attr_reader :name
|
105
|
+
attr_reader :kind
|
106
|
+
|
107
|
+
def initialize(name, kind: :instance)
|
108
|
+
@name = name
|
109
|
+
@sigs = []
|
110
|
+
@kind = kind
|
111
|
+
end
|
112
|
+
|
113
|
+
def <<(sig)
|
114
|
+
sigs << sig
|
115
|
+
end
|
116
|
+
|
117
|
+
def build
|
118
|
+
RBS::AST::Members::MethodDefinition.new(
|
119
|
+
name: name,
|
120
|
+
kind: kind,
|
121
|
+
types: types,
|
122
|
+
annotations: [],
|
123
|
+
location: nil,
|
124
|
+
comment: comment,
|
125
|
+
overload: false,
|
126
|
+
visibility: sigs.last[:visibility]
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def types
|
131
|
+
sigs.zip_sig.map { |sig|
|
132
|
+
sig.method_type
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def comment
|
137
|
+
<<~EOS.comment if sigs.last[:source_location] && sigs.any? { _1[:reference_location] }
|
138
|
+
source location: #{sigs.last[:source_location]}
|
139
|
+
reference location:
|
140
|
+
#{sigs.map { " #{name}#{_1.method_type} #{_1[:reference_location]}" }.uniq.join("\n")}
|
141
|
+
EOS
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end end end
|