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 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