rbs-dynamic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Ruby](https://github.com/osyo-manga/gem-rbs-dynamic/actions/workflows/main.yml/badge.svg)](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
|