rbs-inline 0.4.0 → 0.5.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: 713b2bd4c5ddd833fa1bd1a71505924081013101a8a41200a88fed55751146ba
4
- data.tar.gz: 44ffe7a91c0c0afeb7e163a02de508c50f96a609ae7c64593a6b739109ac11b0
3
+ metadata.gz: 3c6ea86390a9ce51331956c34fb2eedc77ae45c4ac0fc7b39d67f2cacb2a5af0
4
+ data.tar.gz: 9e1446ab0920edec03ed54a1a07c8868e3dac9529813c389ed35f85f0380e7c9
5
5
  SHA512:
6
- metadata.gz: 6507f727be79bcae3890c8b8c3c3865544022058e44d5bfd060efb5df0c91e50f2b5e43a11f284c7ea3de1066bdd0ed7e269a14c20b45cc05cafa7ad60ecffb7
7
- data.tar.gz: aad87cafa19657b4d4b055527924146cb232cd96c56081bcbb24c1e1b0ff2fc24a449bdfe2132a43c2e5f5ac2affca8d615d0946b135676979556eaca4887aec
6
+ metadata.gz: 793f9e99431ac027e1ccf6d3b2b31ead740b9227584aa5be5f751cd1eafea85ce8db37e070b5dfcde28139f629d2c2a499aa5e2d507fcbe53c7fd8a3e9cf2377
7
+ data.tar.gz: 3f74d9a9a96189fcd21c470aa7b97949fa625dbb46b218c6ae993b049993947d7a27840755af43626702aa6b55cebc1a25d70830343c6f954bcb2694f30fc34b
data/README.md CHANGED
@@ -8,29 +8,32 @@ RBS::Inline allows embedding RBS type declarations into Ruby code as comments. Y
8
8
  > [!IMPORTANT]
9
9
  > This gem is a prototype for testing. We plan to merge this feature to rbs-gem and deprecate rbs-inline gem after that.
10
10
 
11
+ > [!NOTE]
12
+ > Use Steep >= `1.8.0.dev` to avoid the conflicts on `#:` syntax.
13
+
11
14
  Here is a quick example of embedded declarations.
12
15
 
13
16
  ```rb
14
17
  # rbs_inline: enabled
15
18
 
16
19
  class Person
17
- attr_reader :name #:: String
20
+ attr_reader :name #: String
18
21
 
19
- attr_reader :addresses #:: Array[String]
22
+ attr_reader :addresses #: Array[String]
20
23
 
21
24
  # @rbs name: String
22
25
  # @rbs addresses: Array[String]
23
- # @rbs returns void
26
+ # @rbs return: void
24
27
  def initialize(name:, addresses:)
25
28
  @name = name
26
29
  @addresses = addresses
27
30
  end
28
31
 
29
- def to_s #:: String
32
+ def to_s #: String
30
33
  "Person(name = #{name}, addresses = #{addresses.join(", ")})"
31
34
  end
32
35
 
33
- # @rbs yields (String) -> void
36
+ # @rbs &block: (String) -> void
34
37
  def each_address(&block) #:: void
35
38
  addresses.each(&block)
36
39
  end
data/Rakefile CHANGED
@@ -10,3 +10,15 @@ Rake::TestTask.new(:test) do |t|
10
10
  end
11
11
 
12
12
  task default: :test
13
+
14
+ namespace :rbs do
15
+ task :generate do
16
+ sh "rbs-inline --opt-out --output lib"
17
+ end
18
+
19
+ task :watch do
20
+ sh "fswatch -0 lib | xargs -n1 -0 rbs-inline --opt-out --output lib"
21
+ rescue Interrupt
22
+ # nop
23
+ end
24
+ end
@@ -0,0 +1,361 @@
1
+ module RBS
2
+ module Inline
3
+ class AnnotationParser
4
+ module Tokens
5
+ K_RETURN = :kRETURN
6
+ K_INHERITS = :kINHERITS
7
+ K_AS = :kAS
8
+ K_OVERRIDE = :kOVERRIDE
9
+ K_USE = :kUSE
10
+ K_MODULE_SELF = :kMODULESELF
11
+ K_GENERIC = :kGENERIC
12
+ K_IN = :kIN
13
+ K_OUT = :kOUT
14
+ K_UNCHECKED = :kUNCHECKED
15
+ K_SELF = :kSELF
16
+ K_SKIP = :kSKIP
17
+ K_YIELDS = :kYIELDS
18
+ K_MODULE = :kMODULE
19
+ K_CLASS = :kCLASS
20
+ K_COLON2 = :kCOLON2
21
+ K_COLON = :kCOLON
22
+ K_LBRACKET = :kLBRACKET
23
+ K_RBRACKET = :kRBRACKET
24
+ K_COMMA = :kCOMMA
25
+ K_STAR2 = :kSTAR2
26
+ K_STAR = :kSTAR
27
+ K_MINUS2 = :kMINUS2
28
+ K_LT = :kLT
29
+ K_DOT3 = :kDOT3
30
+ K_DOT = :kDOT
31
+ K_ARROW = :kARROW
32
+ K_LBRACE = :kLBRACE
33
+ K_LPAREN = :kLPAREN
34
+ K_AMP = :kAMP
35
+ K_QUESTION = :kQUESTION
36
+ K_VBAR = :kVBAR
37
+
38
+ K_EOF = :kEOF
39
+
40
+ # `@rbs!`
41
+ K_RBSE = :kRBSE
42
+
43
+ # `@rbs`
44
+ K_RBS = :kRBS
45
+
46
+ T_UIDENT = :tUIDENT
47
+ T_IFIDENT = :tIFIDENT
48
+ T_LVAR = :tLVAR
49
+
50
+ # The body of comment string following `--`
51
+ T_COMMENT = :tCOMMENT
52
+
53
+ # Type/method type source
54
+ T_SOURCE = :tSOURCE
55
+
56
+ # Block type source
57
+ T_BLOCKSTR = :tBLOCKSTR
58
+
59
+ # `!` local variable
60
+ T_ELVAR = :tELVAR
61
+
62
+ T_ATIDENT = :tATIDENT
63
+ T_ANNOTATION = :tANNOTATION
64
+ T_WHITESPACE = :tWHITESPACE
65
+ end
66
+
67
+ class Tokenizer
68
+ include Tokens
69
+
70
+ KEYWORDS = {
71
+ "return" => K_RETURN,
72
+ "inherits" => K_INHERITS,
73
+ "as" => K_AS,
74
+ "override" => K_OVERRIDE,
75
+ "use" => K_USE,
76
+ "module-self" => K_MODULE_SELF,
77
+ "generic" => K_GENERIC,
78
+ "in" => K_IN,
79
+ "out" => K_OUT,
80
+ "unchecked" => K_UNCHECKED,
81
+ "self" => K_SELF,
82
+ "skip" => K_SKIP,
83
+ "yields" => K_YIELDS,
84
+ "module" => K_MODULE,
85
+ "class" => K_CLASS,
86
+ } #: Hash[String, Symbol]
87
+ KW_RE = /#{Regexp.union(KEYWORDS.keys)}\b/
88
+
89
+ PUNCTS = {
90
+ "::" => K_COLON2,
91
+ ":" => K_COLON,
92
+ "[" => K_LBRACKET,
93
+ "]" => K_RBRACKET,
94
+ "," => K_COMMA,
95
+ "**" => K_STAR2,
96
+ "*" => K_STAR,
97
+ "--" => K_MINUS2,
98
+ "<" => K_LT,
99
+ "..." => K_DOT3,
100
+ "." => K_DOT,
101
+ "->" => K_ARROW,
102
+ "{" => K_LBRACE,
103
+ "(" => K_LPAREN,
104
+ "&" => K_AMP,
105
+ "?" => K_QUESTION,
106
+ "|" => K_VBAR,
107
+ } #: Hash[String, Symbol]
108
+ PUNCTS_RE = Regexp.union(PUNCTS.keys) #: Regexp
109
+
110
+ attr_reader :scanner #: StringScanner
111
+
112
+ # Tokens that comes after the current position
113
+ #
114
+ # This is a four tuple of tokens.
115
+ #
116
+ # 1. The first array is a trivia tokens before current position
117
+ # 2. The second token is the first lookahead token after the current position
118
+ # 3. The third array is a trivia tokens between the first lookahead and the second lookahead
119
+ # 4. The fourth token is the second lookahead token
120
+ #
121
+ attr_reader :lookahead_tokens #: [Array[token], token?, Array[token], token?]
122
+
123
+ # Token that comes after the current position
124
+ # @rbs %a{pure}
125
+ def lookahead1 #: token?
126
+ lookahead_tokens[1]
127
+ end
128
+
129
+ # Token that comes after `lookahead1`
130
+ # @rbs %a{pure}
131
+ def lookahead2 #: token?
132
+ lookahead_tokens[3]
133
+ end
134
+
135
+ # Returns the current char position of the first lookahead token
136
+ #
137
+ # ```
138
+ # __ foo ___ bar baz
139
+ # ^^ Trivia tokens before lookahead1
140
+ # ^ #current_position
141
+ # ^^^ lookahead1
142
+ # ^^^ Trivia tokens between lookahead1 and lookahead2
143
+ # ^^^ lookahead2
144
+ # ^ <= scanner.charpos
145
+ # ```
146
+ #
147
+ def current_position #: Integer
148
+ start = scanner.charpos
149
+ start -= lookahead1[1].size if lookahead1
150
+ lookahead_tokens[2].each {|_, s| start -= s.size }
151
+ start -= lookahead2[1].size if lookahead2
152
+ start
153
+ end
154
+
155
+ def lookaheads #: Array[Symbol?]
156
+ [lookahead1&.[](0), lookahead2&.[](0)]
157
+ end
158
+
159
+ # @rbs scanner: StringScanner
160
+ # @rbs return: void
161
+ def initialize(scanner)
162
+ @scanner = scanner
163
+
164
+ @lookahead_tokens = [[], nil, [], nil]
165
+ end
166
+
167
+ # Advances the scanner
168
+ #
169
+ # @rbs tree: AST::Tree -- Tree to insert trivia tokens
170
+ # @rbs eat: bool -- true to add the current lookahead token into the tree
171
+ # @rbs return: void
172
+ def advance(tree, eat: false)
173
+ consume_trivias(tree)
174
+ last = lookahead_tokens[1]
175
+ tree << last if eat
176
+
177
+ lookahead_tokens[0].replace(lookahead_tokens[2])
178
+ lookahead_tokens[1] = lookahead_tokens[3]
179
+ lookahead_tokens[2].clear
180
+
181
+ while s = scanner.scan(/\s+/)
182
+ lookahead_tokens[2] << [T_WHITESPACE, s]
183
+ end
184
+
185
+ lookahead =
186
+ case
187
+ when scanner.eos?
188
+ [K_EOF, ""]
189
+ when s = scanner.scan(/@rbs!/)
190
+ [K_RBSE, s]
191
+ when s = scanner.scan(/@rbs\b/)
192
+ [K_RBS, s]
193
+ when s = scanner.scan(PUNCTS_RE)
194
+ [PUNCTS.fetch(s), s]
195
+ when s = scanner.scan(KW_RE)
196
+ [KEYWORDS.fetch(s), s]
197
+ when s = scanner.scan(/[A-Z]\w*/)
198
+ [T_UIDENT, s]
199
+ when s = scanner.scan(/_[A-Z]\w*/)
200
+ [T_IFIDENT, s]
201
+ when s = scanner.scan(/[a-z]\w*/)
202
+ [T_LVAR, s]
203
+ when s = scanner.scan(/![a-z]\w*/)
204
+ [T_ELVAR, s]
205
+ when s = scanner.scan(/@\w+/)
206
+ [T_ATIDENT, s]
207
+ when s = scanner.scan(/%a\{[^}]+\}/)
208
+ [T_ANNOTATION, s]
209
+ when s = scanner.scan(/%a\[[^\]]+\]/)
210
+ [T_ANNOTATION, s]
211
+ when s = scanner.scan(/%a\([^)]+\)/)
212
+ [T_ANNOTATION, s]
213
+ end #: token?
214
+
215
+ lookahead_tokens[3] = lookahead
216
+
217
+ last
218
+ end
219
+
220
+ # @rbs (AST::Tree?) -> String
221
+ def consume_trivias(tree)
222
+ buf = +""
223
+
224
+ lookahead_tokens[0].each do |tok|
225
+ tree << tok if tree
226
+ buf << tok[1]
227
+ end
228
+ lookahead_tokens[0].clear
229
+
230
+ buf
231
+ end
232
+
233
+ # Returns true if the scanner cannot consume next token
234
+ def stuck? #: bool
235
+ lookahead1.nil? && lookahead2.nil?
236
+ end
237
+
238
+ # Skips characters
239
+ #
240
+ # This method ensures the `current_position` will be the given `position`.
241
+ #
242
+ # @rbs position: Integer -- The new position
243
+ # @rbs tree: AST::Tree -- Tree to insert trivia tokens
244
+ # @rbs return: void
245
+ def reset(position, tree)
246
+ if scanner.charpos > position
247
+ scanner.reset()
248
+ end
249
+
250
+ skips = position - scanner.charpos
251
+
252
+ if scanner.rest_size < skips
253
+ raise "The position is bigger than the size of the rest of the input: input size=#{scanner.string.size}, position=#{position}"
254
+ end
255
+
256
+ scanner.skip(/.{#{skips}}/)
257
+
258
+ @lookahead_tokens = [[], nil, [], nil]
259
+
260
+ advance(tree)
261
+ advance(tree)
262
+ end
263
+
264
+ def rest #: String
265
+ buf = +""
266
+ lookahead_tokens[0].each {|_, s| buf << s }
267
+ buf << lookahead1[1] if lookahead1
268
+ lookahead_tokens[2].each {|_, s| buf << s }
269
+ buf << lookahead2[1] if lookahead2
270
+ buf << scanner.rest
271
+ buf
272
+ end
273
+
274
+ # Consume given token type and inserts the token to the tree or `nil`
275
+ #
276
+ # @rbs *types: Symbol
277
+ # @rbs tree: AST::Tree
278
+ # @rbs return: void
279
+ def consume_token(*types, tree:)
280
+ if type?(*types)
281
+ advance(tree, eat: true)
282
+ else
283
+ tree << nil
284
+ end
285
+ end
286
+
287
+ # Consume given token type and inserts the token to the tree or raise
288
+ #
289
+ # @rbs *types: Symbol
290
+ # @rbs tree: AST::Tree
291
+ # @rbs return: void
292
+ def consume_token!(*types, tree:)
293
+ type!(*types)
294
+ advance(tree, eat: true)
295
+ end
296
+
297
+ # Test if current token has specified `type`
298
+ #
299
+ # @rbs *types: Symbol
300
+ # @rbs return: bool
301
+ def type?(*types)
302
+ types.any? { lookahead1 && lookahead1[0] == _1 }
303
+ end
304
+
305
+ # Test if lookahead2 token have specified `type`
306
+ #
307
+ # @rbs *types: Symbol -- The type of the lookahead2 token
308
+ # @rbs return: bool
309
+ def type2?(*types)
310
+ types.any? { lookahead2 && lookahead2[0] == _1 }
311
+ end
312
+
313
+ # Ensure current token is one of the specified in types
314
+ #
315
+ # @rbs *types: Symbol
316
+ # @rbs return: void
317
+ def type!(*types)
318
+ raise "Unexpected token: #{lookahead1&.[](0)}, where expected token: #{types.join(",")}" unless type?(*types)
319
+ end
320
+
321
+ # Reset the current_token to incoming comment `--`
322
+ #
323
+ # Reset to the end of the input if `--` token cannot be found.
324
+ #
325
+ # @rbs return: String -- String that is skipped
326
+ def skip_to_comment
327
+ prefix = +""
328
+
329
+ lookahead_tokens[0].each { prefix << _1[1] }
330
+ lookahead_tokens[0].clear
331
+
332
+ if type?(K_MINUS2)
333
+ return prefix
334
+ end
335
+
336
+ prefix << lookahead1[1] if lookahead1
337
+ lookahead_tokens[2].each { prefix << _1[1] }
338
+ lookahead_tokens[2].clear
339
+
340
+ if type2?(K_MINUS2)
341
+ advance(_ = nil) # The tree is unused because no trivia tokens are left
342
+ return prefix
343
+ end
344
+
345
+ prefix << lookahead2[1] if lookahead2
346
+
347
+ if string = scanner.scan_until(/--/)
348
+ @lookahead_tokens = [[], nil, [], [K_MINUS2, "--"]]
349
+ advance(_ = nil) # The tree is unused because no trivia tokens are left
350
+ prefix + string.delete_suffix("--")
351
+ else
352
+ s = scanner.rest
353
+ @lookahead_tokens = [[], [K_EOF, ""], [], nil]
354
+ scanner.terminate
355
+ prefix + s
356
+ end
357
+ end
358
+ end
359
+ end
360
+ end
361
+ end