rbs2ts 1.0.0.pre.alpha.3 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb1b221a5b895071e46b77dcf36fd1df3e789b0af6f91cb78adbfef7aa633c1c
4
- data.tar.gz: 1a452ee8992929e84d0cfde11006e1b9918a4dbfb5cd597506523d6491199c7f
3
+ metadata.gz: 4743b73de60833c61821721455c9c6f23cb7e6f9616179df62af629ad9ceb99c
4
+ data.tar.gz: '097d7ccc401a1c0684c433b4fc11279409ff75d6bf99e2b2340953f456d40986'
5
5
  SHA512:
6
- metadata.gz: c6242c6655d8ab4d43267c4fb4d7a99471d27047b5b1ea04025aada0066f272b61ffa353a06bcd238a11890d3a65ff5bf32cb6471543a00d5036b734e5b84cba
7
- data.tar.gz: 9a9d8d78d4eadd2cd9999063d841d878259f5bc52ccfbb0ea02b4691ef0a3133d8c8ab4cb954767925d0564e0c930697b1cd0017a4d476704a8f08515933bfd1
6
+ metadata.gz: d0b28b0b98c1cb73b247f397da3b64cc0c9c2d9bebd71f54c5a377fbbb9301b5ffd2c337810be8c61e2d71eca5df613707a3b266144816e8dcce31d26c55a095
7
+ data.tar.gz: 5ee48310c33f8ede00253b84037ccfab31a7cbd4cc1262aa041bab223a431b991b6792875d327724f71503c84a81ba1460fe9ad05130f13802a8cdded6cf71f6
@@ -0,0 +1 @@
1
+ README.md
@@ -10,7 +10,7 @@
10
10
  "request": "launch",
11
11
  "program": "${workspaceRoot}/exe/rbs2ts",
12
12
  "useBundler": true,
13
- "args": ["convert", "spec/sample_rbs/test.rbs"]
13
+ "args": ["convert", "spec/fixtures/test.rbs"]
14
14
  }
15
15
  ]
16
16
  }
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Rbs2ts
2
2
 
3
- [![mugi_uno](https://circleci.com/<VCS>/<ORG_NAME>/<PROJECT_NAME>.svg?style=svg)](LINK)
4
-
5
- Convert RBS to TypeScript type definition.
3
+ A RubyGem that converts Ruby RBS to TypeScript definitions.
6
4
 
7
5
  ## Installation
8
6
 
@@ -16,16 +14,195 @@ gem install rbs2ts
16
14
  rbs2ts convert type.rbs
17
15
  ```
18
16
 
19
- ---
17
+ ## Example
18
+
19
+ from RBS
20
+
21
+ ```
22
+ type TypeofInteger = Integer
23
+ type TypeofFloat = Float
24
+ type TypeofNumeric = Numeric
25
+ type TypeofString = String
26
+ type TypeofBool = Bool
27
+ type TypeofVoid = void
28
+ type TypeofUntyped = untyped
29
+ type TypeofNil = nil
30
+
31
+ type IntegerLiteral = 123
32
+ type StringLiteral = 'abc'
33
+ type TrueLiteral = true
34
+ type FalseLiteral = false
35
+
36
+ type IntersectionType = String & Integer & Bool
37
+ type UnionType = String | Integer | Bool
38
+
39
+ type ArrayType = Array[String]
40
+
41
+ type TupleType = [ ]
42
+ type TupleEmptyType = [String, Integer]
43
+
44
+ type OptionalType = String?
45
+
46
+ type RecordType = {
47
+ s: String,
48
+ next: {
49
+ i: Integer,
50
+ f: Float
51
+ }?
52
+ }
53
+
54
+ class Klass
55
+ @val: String
56
+ attr_accessor a: String
57
+ attr_reader b: Integer
58
+ attr_writer c: Bool
59
+
60
+ attr_reader r: {
61
+ d: String,
62
+ e: {
63
+ f: String,
64
+ g: String?
65
+ }?
66
+ }
67
+
68
+ def to_tuple: () -> [{ s: String, f: Float }?]
69
+ def required_positional: (String) -> void
70
+ def required_positional_name: (String str) -> void
71
+ def optional_positional: (?String) -> void
72
+ def optional_positional_name: (?String? str) -> void
73
+ def rest_positional: (*String) -> void
74
+ def rest_positional_name: (*String str) -> void
75
+ def rest_positional_with_trailing: (*String, Integer) -> void
76
+ def rest_positional_name_with_trailing: (*String str, Integer trailing) -> void
77
+ def required_keyword: (str: String) -> void
78
+ def optional_keyword: (?str: String?) -> void
79
+ def rest_keywords: (**String) -> void
80
+ def rest_keywords_name: (**String rest) -> void
81
+
82
+ def all_arguments: (
83
+ String required_positional,
84
+ ?Integer? optional_positional,
85
+ *String rest_positionals_s,
86
+ Integer trailing_positional_s,
87
+ required_keyword: String,
88
+ ?optional_keyword: Integer?,
89
+ **String rest_keywords
90
+ ) -> [{ s: String, f: Float }?]
91
+ end
92
+
93
+ module Module
94
+ @val: String
95
+ type AliasType = String
96
+
97
+ def func: (String, Integer) -> { str: String, int: Integer }
98
+
99
+ class NestedClass
100
+ attr_accessor a: AliasType
101
+ end
102
+ end
103
+
104
+ interface _Interface
105
+ def func: (String, Integer) -> { str: String, int: Integer }
106
+ end
107
+ ```
108
+
109
+ to TypeScript
110
+
111
+ ```typescript
112
+ export type TypeofInteger = number;
20
113
 
21
- ## ToDo
114
+ export type TypeofFloat = number;
22
115
 
23
- - [x] Literal type
24
- - [ ] Interface type
25
- - [ ] Literal type
26
- - [ ] Tuple Type
27
- - [ ] Base Types
28
- - [ ] Method Type (Argument Types and Return Types)
29
- - [ ] Class declaration
30
- - [ ] Module declaration
31
- - [ ] Interface declaration
116
+ export type TypeofNumeric = number;
117
+
118
+ export type TypeofString = string;
119
+
120
+ export type TypeofBool = boolean;
121
+
122
+ export type TypeofVoid = void;
123
+
124
+ export type TypeofUntyped = any;
125
+
126
+ export type TypeofNil = null;
127
+
128
+ export type IntegerLiteral = 123;
129
+
130
+ export type StringLiteral = "abc";
131
+
132
+ export type TrueLiteral = true;
133
+
134
+ export type FalseLiteral = false;
135
+
136
+ export type IntersectionType = string & number & boolean;
137
+
138
+ export type UnionType = string | number | boolean;
139
+
140
+ export type ArrayType = string[];
141
+
142
+ export type TupleType = [];
143
+
144
+ export type TupleEmptyType = [string, number];
145
+
146
+ export type OptionalType = string | null | undefined;
147
+
148
+ export type RecordType = {
149
+ s: string;
150
+ next: {
151
+ i: number;
152
+ f: number;
153
+ } | null | undefined;
154
+ };
155
+
156
+ export declare class Klass {
157
+ val: string;
158
+ a: string;
159
+ readonly b: number;
160
+ c: boolean;
161
+ readonly r: {
162
+ d: string;
163
+ e: {
164
+ f: string;
165
+ g: string | null | undefined;
166
+ } | null | undefined;
167
+ };
168
+ toTuple(): [({
169
+ s: string;
170
+ f: number;
171
+ } | null | undefined)];
172
+ requiredPositional(arg1: string): void;
173
+ requiredPositionalName(str: string): void;
174
+ optionalPositional(arg1?: string): void;
175
+ optionalPositionalName(str?: string | null | undefined): void;
176
+ restPositional(...arg1: string[]): void;
177
+ restPositionalName(...str: string[]): void;
178
+ restPositionalWithTrailing(arg1: string[], arg2: number): void;
179
+ restPositionalNameWithTrailing(str: string[], trailing: number): void;
180
+ requiredKeyword(arg1: { str: string }): void;
181
+ optionalKeyword(arg1: { str?: string | null | undefined }): void;
182
+ restKeywords(arg1: { [key: string]: unknown; }): void;
183
+ restKeywordsName(arg1: { [key: string]: unknown; }): void;
184
+ allArguments(requiredPositional: string, optionalPositional?: number | null | undefined, restPositionalsS?: string[], trailingPositionalS?: number, arg1?: { requiredKeyword: string, optionalKeyword?: number | null | undefined, [key: string]: unknown; }): [({
185
+ s: string;
186
+ f: number;
187
+ } | null | undefined)];
188
+ };
189
+
190
+ export namespace Module {
191
+ export declare let val: string;
192
+ export type AliasType = string;
193
+ export declare function func(arg1: string, arg2: number): {
194
+ str: string;
195
+ int: number;
196
+ };
197
+ export declare class NestedClass {
198
+ a: AliasType;
199
+ };
200
+ };
201
+
202
+ export interface Interface {
203
+ func(arg1: string, arg2: number): {
204
+ str: string;
205
+ int: number;
206
+ };
207
+ };
208
+ ```
@@ -3,5 +3,7 @@ end
3
3
 
4
4
  require 'case_transform'
5
5
 
6
+ require 'rbs2ts/converter/helper'
6
7
  require 'rbs2ts/converter/declarations'
7
8
  require 'rbs2ts/converter/types'
9
+ require 'rbs2ts/converter/members'
@@ -9,6 +9,12 @@ module Rbs2ts
9
9
  def to_ts
10
10
  decls_ts = @decls.map do |d|
11
11
  case d
12
+ when ::RBS::AST::Declarations::Class then
13
+ Converter::Declarations::Class.new(d).to_ts
14
+ when ::RBS::AST::Declarations::Module then
15
+ Converter::Declarations::Module.new(d).to_ts
16
+ when ::RBS::AST::Declarations::Interface then
17
+ Converter::Declarations::Interface.new(d).to_ts
12
18
  when ::RBS::AST::Declarations::Alias then
13
19
  Converter::Declarations::Alias.new(d).to_ts
14
20
  end
@@ -29,26 +35,109 @@ module Rbs2ts
29
35
  end
30
36
 
31
37
  def name
32
- declaration.name.to_s.gsub(/:/, '')
38
+ declaration.name.name.to_s.gsub(/:/, '')
33
39
  end
34
40
 
35
41
  private
36
42
 
37
43
  attr_reader :declaration
38
44
  end
39
-
45
+
40
46
  class Class < Base
47
+ def to_ts
48
+ members_ts = declaration.members.map {|member|
49
+ member_to_ts(member)
50
+ }.reject(&:empty?).join("\n")
51
+
52
+ <<~TS
53
+ export declare class #{name} {
54
+ #{Helper.indent(members_ts)}
55
+ };
56
+ TS
57
+ .chomp
58
+ end
59
+
60
+ def member_to_ts(member)
61
+ case member
62
+ when ::RBS::AST::Members::InstanceVariable then
63
+ Converter::Members::InstanceVariable.new(member).to_ts
64
+ when ::RBS::AST::Members::AttrReader then
65
+ Converter::Members::AttrReader.new(member).to_ts
66
+ when ::RBS::AST::Members::AttrWriter then
67
+ Converter::Members::AttrWriter.new(member).to_ts
68
+ when ::RBS::AST::Members::AttrAccessor then
69
+ Converter::Members::AttrAccessor.new(member).to_ts
70
+ when ::RBS::AST::Members::MethodDefinition
71
+ Converter::Members::MethodDefinition.new(member).to_ts
72
+ else
73
+ ''
74
+ end
75
+ end
41
76
  end
42
77
 
43
78
  class Module < Base
79
+ def to_ts
80
+ members_ts = declaration.members.map {|member|
81
+ member_to_ts(member)
82
+ }.reject(&:empty?).join("\n")
83
+
84
+ <<~TS
85
+ export namespace #{name} {
86
+ #{Helper.indent(members_ts)}
87
+ };
88
+ TS
89
+ .chomp
90
+ end
91
+
92
+ def member_to_ts(member)
93
+ case member
94
+ when ::RBS::AST::Declarations::Class then
95
+ Converter::Declarations::Class.new(member).to_ts
96
+ when ::RBS::AST::Declarations::Module then
97
+ Converter::Declarations::Module.new(member).to_ts
98
+ when ::RBS::AST::Declarations::Interface then
99
+ Converter::Declarations::Interface.new(member).to_ts
100
+ when ::RBS::AST::Declarations::Alias then
101
+ Converter::Declarations::Alias.new(member).to_ts
102
+ when ::RBS::AST::Members::InstanceVariable
103
+ ts = Converter::Members::InstanceVariable.new(member).to_ts
104
+ "export declare let #{ts}"
105
+ when ::RBS::AST::Members::MethodDefinition
106
+ ts = Converter::Members::MethodDefinition.new(member).to_ts
107
+ "export declare function #{ts}"
108
+ else
109
+ ''
110
+ end
111
+ end
44
112
  end
45
113
 
46
114
  class Interface < Base
115
+ def to_ts
116
+ members_ts = declaration.members.map {|member|
117
+ member_to_ts(member)
118
+ }.reject(&:empty?).join("\n")
119
+
120
+ <<~TS
121
+ export interface #{name.gsub(/_/, '')} {
122
+ #{Helper.indent(members_ts)}
123
+ };
124
+ TS
125
+ .chomp
126
+ end
127
+
128
+ def member_to_ts(member)
129
+ case member
130
+ when ::RBS::AST::Members::MethodDefinition
131
+ Converter::Members::MethodDefinition.new(member).to_ts
132
+ else
133
+ ''
134
+ end
135
+ end
47
136
  end
48
137
 
49
138
  class Alias < Base
50
139
  def to_ts
51
- "type #{name} = #{Converter::Types::Resolver.to_ts(declaration.type)};"
140
+ "export type #{name} = #{Converter::Types::Resolver.to_ts(declaration.type)};"
52
141
  end
53
142
  end
54
143
 
@@ -0,0 +1,16 @@
1
+ module Rbs2ts
2
+ module Converter
3
+ module Helper
4
+ class << self
5
+ INDENT = ' '
6
+
7
+ def indent(text, level = 1)
8
+ text
9
+ .split("\n")
10
+ .map {|t| "#{INDENT * level}#{t}" }
11
+ .join("\n")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,146 @@
1
+ module Rbs2ts
2
+ module Converter
3
+ module Members
4
+ class Base
5
+ def initialize(member)
6
+ @member = member
7
+ end
8
+
9
+ def to_ts
10
+ ''
11
+ end
12
+
13
+ def name
14
+ CaseTransform.camel_lower(member.name.to_s.gsub(/:/, ''))
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :member
20
+ end
21
+
22
+ class InstanceVariable < Base
23
+ def to_ts
24
+ "#{name.gsub(/@/, '')}: #{Converter::Types::Resolver.to_ts(member.type)};"
25
+ end
26
+ end
27
+
28
+ class AttrReader < Base
29
+ def to_ts
30
+ "readonly #{name}: #{Converter::Types::Resolver.to_ts(member.type)};"
31
+ end
32
+ end
33
+
34
+ class AttrWriter < Base
35
+ def to_ts
36
+ "#{name}: #{Converter::Types::Resolver.to_ts(member.type)};"
37
+ end
38
+ end
39
+
40
+ class AttrAccessor < Base
41
+ def to_ts
42
+ "#{name}: #{Converter::Types::Resolver.to_ts(member.type)};"
43
+ end
44
+ end
45
+
46
+ class MethodDefinition < Base
47
+ def initialize(member)
48
+ super
49
+ @args_count = 0
50
+ end
51
+
52
+ def to_ts
53
+ "#{name}(#{args_to_ts}): #{return_type_to_ts};"
54
+ end
55
+
56
+ def args_to_ts
57
+ [
58
+ *required_positional_to_ts,
59
+ *optional_positional_to_ts,
60
+ *rest_positionals_to_ts,
61
+ *trailing_positionals_to_ts,
62
+ *keyword_args_to_ts
63
+ ].join(", ")
64
+ end
65
+
66
+ def arg_name(arg)
67
+ name = arg.name.nil? ? next_default_arg_name : arg.name.to_s
68
+ CaseTransform.camel_lower(name)
69
+ end
70
+
71
+ def next_default_arg_name
72
+ @args_count = @args_count + 1
73
+ "arg#{@args_count.to_s}"
74
+ end
75
+
76
+ def required_positional_to_ts
77
+ method_type.required_positionals.map {|arg|
78
+ "#{arg_name(arg)}: #{Converter::Types::Resolver.to_ts(arg.type)}"
79
+ }
80
+ end
81
+
82
+ def optional_positional_to_ts
83
+ method_type.optional_positionals.map {|arg|
84
+ "#{arg_name(arg)}?: #{Converter::Types::Resolver.to_ts(arg.type)}"
85
+ }
86
+ end
87
+
88
+ def rest_positionals_to_ts
89
+ arg = method_type.rest_positionals
90
+
91
+ return [] if arg.nil?
92
+
93
+ has_rest_after_arguments ?
94
+ ["#{arg_name(arg)}#{optional_ts_code}: #{Converter::Types::Resolver.to_ts(arg.type)}[]"] :
95
+ ["...#{arg_name(arg)}#{optional_ts_code}: #{Converter::Types::Resolver.to_ts(arg.type)}[]"]
96
+ end
97
+
98
+ def trailing_positionals_to_ts
99
+ method_type.trailing_positionals.map {|arg|
100
+ "#{arg_name(arg)}#{optional_ts_code}: #{Converter::Types::Resolver.to_ts(arg.type)}"
101
+ }
102
+ end
103
+
104
+ def keyword_args_to_ts
105
+ required_keywords_ts = method_type.required_keywords.map {|key, value|
106
+ "#{CaseTransform.camel_lower(key.to_s)}: #{Converter::Types::Resolver.to_ts(value.type)}"
107
+ }
108
+ optional_keywords_ts = method_type.optional_keywords.map {|key, value|
109
+ "#{CaseTransform.camel_lower(key.to_s)}?: #{Converter::Types::Resolver.to_ts(value.type)}"
110
+ }
111
+ rest_keywords_ts = method_type.rest_keywords.nil? ? [] : ["[key: string]: unknown;"]
112
+
113
+ ts_array = [
114
+ *required_keywords_ts,
115
+ *optional_keywords_ts,
116
+ *rest_keywords_ts
117
+ ]
118
+
119
+ return [] if ts_array.empty?
120
+
121
+ ts = ts_array.join(', ')
122
+
123
+ ["#{next_default_arg_name}#{optional_ts_code}: { #{ts} }"]
124
+ end
125
+
126
+ def method_type
127
+ member.types.first.type
128
+ end
129
+
130
+ def return_type_to_ts
131
+ Converter::Types::Resolver.to_ts(method_type.return_type)
132
+ end
133
+
134
+ def optional_ts_code
135
+ method_type.optional_positionals.present? ? '?' : ''
136
+ end
137
+
138
+ def has_rest_after_arguments
139
+ method_type.trailing_positionals.present? ||
140
+ method_type.required_keywords.present? ||
141
+ method_type.optional_keywords.present?
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -46,33 +46,43 @@ module Rbs2ts
46
46
  end
47
47
 
48
48
  class Record < ConverterBase
49
- INDENT = ' '
50
- @@nest = 0
51
-
52
49
  def to_ts
53
- @@nest = @@nest + 1
54
-
55
- indent = INDENT * @@nest
56
50
  field_lines = type.fields.map { |name, type|
57
- "#{indent}#{CaseTransform.camel_lower(name.to_s)}: #{Types::Resolver.to_ts(type)};"
51
+ "#{CaseTransform.camel_lower(name.to_s)}: #{Types::Resolver.to_ts(type)};"
58
52
  }
59
53
 
60
- @@nest = @@nest - 1
61
-
62
54
  return '{}' if field_lines.empty?
63
55
 
64
56
  field_ts = field_lines.join("\n")
65
57
 
66
58
  ts = <<~CODE
67
59
  {
68
- #{field_ts}
69
- #{INDENT * @@nest}}
60
+ #{Helper.indent(field_ts)}
61
+ }
70
62
  CODE
71
63
 
72
64
  ts.chomp
73
65
  end
74
66
  end
75
67
 
68
+ class Tuple < ConverterBase
69
+ def to_ts
70
+ tuple_types_ts = type.types.map {|t|
71
+ ts = Types::Resolver.to_ts(t)
72
+
73
+ if t.is_a?(::RBS::Types::Union) ||
74
+ t.is_a?(::RBS::Types::Intersection) ||
75
+ t.is_a?(::RBS::Types::Optional)
76
+ "(#{ts})"
77
+ else
78
+ ts
79
+ end
80
+ }.join(', ')
81
+
82
+ "[#{tuple_types_ts}]"
83
+ end
84
+ end
85
+
76
86
  class Literal < ConverterBase
77
87
  def to_ts
78
88
  case type.literal
@@ -121,12 +131,12 @@ module Rbs2ts
121
131
  Types::Array.new(type).to_ts
122
132
  when 'String' then
123
133
  Types::String.new(type).to_ts
124
- when 'Integer' then
134
+ when 'Numeric', 'Integer', 'Float' then
125
135
  Types::Integer.new(type).to_ts
126
136
  when 'Bool' then
127
137
  Types::Bool.new(type).to_ts
128
138
  else
129
- type.name.to_s.gsub(/:/, '')
139
+ type.name.name.to_s.gsub(/:/, '')
130
140
  end
131
141
  end
132
142
  end
@@ -192,6 +202,8 @@ module Rbs2ts
192
202
  Types::Intersection
193
203
  when ::RBS::Types::Record then
194
204
  Types::Record
205
+ when ::RBS::Types::Tuple then
206
+ Types::Tuple
195
207
  else
196
208
  Types::Fallback
197
209
  end
@@ -1,3 +1,3 @@
1
1
  module Rbs2ts
2
- VERSION = "1.0.0-alpha.3"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["mugi-uno"]
10
10
  spec.email = ["mugi.uno@gmail.com"]
11
11
 
12
- spec.summary = "Convert rbs to typescript"
12
+ spec.summary = "A RubyGem that converts Ruby RBS to TypeScript definitions."
13
13
  spec.homepage = "https://github.com/mugi-uno/rbs2ts"
14
14
  spec.license = "MIT"
15
15
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbs2ts
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.alpha.3
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mugi-uno
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-14 00:00:00.000000000 Z
11
+ date: 2021-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbs
@@ -104,6 +104,7 @@ extra_rdoc_files: []
104
104
  files:
105
105
  - ".circleci/config.yml"
106
106
  - ".gitignore"
107
+ - ".prettierignore"
107
108
  - ".rspec"
108
109
  - ".travis.yml"
109
110
  - ".vscode/launch.json"
@@ -117,6 +118,8 @@ files:
117
118
  - lib/rbs2ts/cli.rb
118
119
  - lib/rbs2ts/converter.rb
119
120
  - lib/rbs2ts/converter/declarations.rb
121
+ - lib/rbs2ts/converter/helper.rb
122
+ - lib/rbs2ts/converter/members.rb
120
123
  - lib/rbs2ts/converter/types.rb
121
124
  - lib/rbs2ts/version.rb
122
125
  - rbs2ts.gemspec
@@ -135,12 +138,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
135
138
  version: '0'
136
139
  required_rubygems_version: !ruby/object:Gem::Requirement
137
140
  requirements:
138
- - - ">"
141
+ - - ">="
139
142
  - !ruby/object:Gem::Version
140
- version: 1.3.1
143
+ version: '0'
141
144
  requirements: []
142
145
  rubygems_version: 3.1.4
143
146
  signing_key:
144
147
  specification_version: 4
145
- summary: Convert rbs to typescript
148
+ summary: A RubyGem that converts Ruby RBS to TypeScript definitions.
146
149
  test_files: []