rbs-dynamic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in rbs-dynamic.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/exe/rbs-dynamic ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/rbs/dynamic/cli.rb"
5
+ RBS::Dynamic::CLI.start(ARGV)
@@ -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