syntax_tree-rbs 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.
@@ -0,0 +1,431 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module RBS
5
+ # An annotation can be attached to many kinds of nodes, and should be
6
+ # printed using %a{}. This class wraps a set of annotations and provides the
7
+ # ability to print them if they are found.
8
+ class Annotations
9
+ attr_reader :annotations
10
+
11
+ def initialize(annotations)
12
+ @annotations = annotations
13
+ end
14
+
15
+ def format(q)
16
+ q.seplist(annotations, -> { q.breakable(force: true) }) do |annotation|
17
+ if annotation.string.match?(/[{}]/)
18
+ # Bail out and just print the source string if there are any braces
19
+ # because we don't want to mess with escaping them.
20
+ q.text(q.source[annotation.location.range])
21
+ else
22
+ q.text("%a{")
23
+ q.text(annotation.string)
24
+ q.text("}")
25
+ end
26
+ end
27
+ q.breakable(force: true)
28
+ end
29
+
30
+ def pretty_print(q)
31
+ q.seplist(annotations) do |annotation|
32
+ q.group(2, "(annotation", ")") do
33
+ q.breakable
34
+ q.pp(annotation.string)
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.maybe_format(q, annotations)
40
+ new(annotations).format(q) if annotations.any?
41
+ end
42
+
43
+ def self.maybe_pretty_print(q, annotations)
44
+ if annotations.any?
45
+ q.breakable
46
+ q.text("annotations=")
47
+ q.pp(new(annotations))
48
+ end
49
+ end
50
+ end
51
+
52
+ # A comment can be attached to many kinds of nodes, and should be printed
53
+ # before them. This class wraps a comment and provides the ability to print
54
+ # it if it is found.
55
+ class Comment
56
+ attr_reader :comment
57
+
58
+ def initialize(comment)
59
+ @comment = comment
60
+ end
61
+
62
+ # Comments come in as one whole string, so here we split it up into
63
+ # multiple lines and then prefix it with the pound sign.
64
+ def format(q)
65
+ q.seplist(comment.string.split(/\r?\n/), -> { q.breakable(force: true) }) do |line|
66
+ q.text("# #{line}")
67
+ end
68
+ q.breakable(force: true)
69
+ end
70
+
71
+ def pretty_print(q)
72
+ q.group(2, "(comment", ")") do
73
+ q.breakable
74
+ q.pp(comment.string)
75
+ end
76
+ end
77
+
78
+ def self.maybe_format(q, comment)
79
+ new(comment).format(q) if comment
80
+ end
81
+
82
+ def self.maybe_pretty_print(q, comment)
83
+ if comment
84
+ q.breakable
85
+ q.text("comment=")
86
+ q.pp(new(comment))
87
+ end
88
+ end
89
+ end
90
+
91
+ # Certain nodes are names with optional arguments attached, as in Array[A].
92
+ # We handle all of that printing centralized here.
93
+ class NameAndArgs
94
+ attr_reader :node
95
+
96
+ def initialize(node)
97
+ @node = node
98
+ end
99
+
100
+ def format(q)
101
+ q.group do
102
+ node.name.format(q)
103
+
104
+ if node.args.any?
105
+ q.text("[")
106
+ q.seplist(node.args, -> { q.text(", ") }) { |arg| arg.format(q) }
107
+ q.text("]")
108
+ end
109
+ end
110
+ end
111
+
112
+ def pretty_print(q)
113
+ q.breakable
114
+ q.pp(node.name)
115
+
116
+ if node.args.any?
117
+ q.breakable
118
+ q.pp(node.args)
119
+ end
120
+ end
121
+ end
122
+
123
+ # Prints out the name of a class, interface, or module declaration.
124
+ # Additionally loops through each type parameter if there are any and print
125
+ # them out joined by commas. Checks for validation and variance.
126
+ class NameAndTypeParams
127
+ attr_reader :node
128
+
129
+ def initialize(node)
130
+ @node = node
131
+ end
132
+
133
+ def format(q)
134
+ node.name.format(q)
135
+ return if node.type_params.length == 0
136
+
137
+ q.text("[")
138
+ q.seplist(node.type_params, -> { q.text(", ") }) do |param|
139
+ parts = []
140
+
141
+ if param.unchecked?
142
+ parts << "unchecked"
143
+ end
144
+
145
+ if param.variance == :covariant
146
+ parts << "out"
147
+ elsif param.variance == :contravariant
148
+ parts << "in"
149
+ end
150
+
151
+ parts << param.name
152
+ q.text(parts.join(" "))
153
+
154
+ if param.upper_bound
155
+ q.text(" < ")
156
+ param.upper_bound.format(q)
157
+ end
158
+ end
159
+
160
+ q.text("]")
161
+ end
162
+
163
+ def pretty_print(q)
164
+ q.breakable
165
+ q.pp(node.name)
166
+
167
+ if node.type_params.any?
168
+ q.breakable
169
+ q.group(2, "type_params=[", "]") do
170
+ q.seplist(node.type_params) do |param|
171
+ q.group(2, "(type-param", ")") do
172
+ if param.unchecked?
173
+ q.breakable
174
+ q.text("unchecked")
175
+ end
176
+
177
+ if param.variance == :covariant
178
+ q.breakable
179
+ q.text("covariant")
180
+ elsif param.variance == :contravariant
181
+ q.breakable
182
+ q.text("contravariant")
183
+ end
184
+
185
+ q.breakable
186
+ q.text("name=")
187
+ q.pp(param.name)
188
+
189
+ if param.upper_bound
190
+ q.breakable
191
+ q.text("upper_bound=")
192
+ q.pp(param.upper_bound)
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ # Nodes which have members will all flow their printing through this class,
202
+ # which keeps track of
203
+ class Members
204
+ attr_reader :node
205
+
206
+ def initialize(node)
207
+ @node = node
208
+ end
209
+
210
+ def format(q)
211
+ last_line = nil
212
+
213
+ node.members.each do |member|
214
+ q.breakable(force: true)
215
+
216
+ if last_line && (member.location.start_line - last_line >= 2)
217
+ q.breakable(force: true)
218
+ end
219
+
220
+ member.format(q)
221
+ last_line = member.location.end_line
222
+ end
223
+ end
224
+ end
225
+
226
+ # Prints out a specific method signature, which looks like:
227
+ # (T t) -> void
228
+ class MethodSignature
229
+ class OptionalPositional
230
+ attr_reader :param
231
+
232
+ def initialize(param)
233
+ @param = param
234
+ end
235
+
236
+ def format(q)
237
+ q.text("?")
238
+ param.format(q)
239
+ end
240
+ end
241
+
242
+ class RestPositional
243
+ attr_reader :param
244
+
245
+ def initialize(param)
246
+ @param = param
247
+ end
248
+
249
+ def format(q)
250
+ q.text("*")
251
+ param.format(q)
252
+ end
253
+ end
254
+
255
+ class RequiredKeyword
256
+ attr_reader :name, :param
257
+
258
+ def initialize(name, param)
259
+ @name = name
260
+ @param = param
261
+ end
262
+
263
+ def format(q)
264
+ q.text(name)
265
+ q.text(": ")
266
+ param.format(q)
267
+ end
268
+ end
269
+
270
+ class OptionalKeyword
271
+ attr_reader :name, :param
272
+
273
+ def initialize(name, param)
274
+ @name = name
275
+ @param = param
276
+ end
277
+
278
+ def format(q)
279
+ q.text("?")
280
+ q.text(name)
281
+ q.text(": ")
282
+ param.format(q)
283
+ end
284
+ end
285
+
286
+ class RestKeyword
287
+ attr_reader :param
288
+
289
+ def initialize(param)
290
+ @param = param
291
+ end
292
+
293
+ def format(q)
294
+ q.text("**")
295
+ param.format(q)
296
+ end
297
+ end
298
+
299
+ attr_reader :node
300
+
301
+ def initialize(node)
302
+ @node = node
303
+ end
304
+
305
+ def format(q)
306
+ q.group do
307
+ # We won't have a type_params key if we're printing a block
308
+ if node.respond_to?(:type_params) && node.type_params.any?
309
+ q.text("[")
310
+ q.seplist(node.type_params, -> { q.text(", ") }) do |param|
311
+ q.text(param.name)
312
+ end
313
+ q.text("] ")
314
+ end
315
+
316
+ params = [
317
+ *node.type.required_positionals,
318
+ *node.type.optional_positionals.map { |param| OptionalPositional.new(param) },
319
+ *(RestPositional.new(node.type.rest_positionals) if node.type.rest_positionals),
320
+ *node.type.trailing_positionals,
321
+ *node.type.required_keywords.map { |name, param| RequiredKeyword.new(name, param) },
322
+ *node.type.optional_keywords.map { |name, param| OptionalKeyword.new(name, param) },
323
+ *(RestKeyword.new(node.type.rest_keywords) if node.type.rest_keywords)
324
+ ]
325
+
326
+ if params.any?
327
+ q.text("(")
328
+ q.indent do
329
+ q.breakable("")
330
+ q.seplist(params) { |param| param.format(q) }
331
+ end
332
+ q.breakable("")
333
+ q.text(") ")
334
+ end
335
+
336
+ if node.respond_to?(:block) && node.block
337
+ q.text("?") unless node.block.required
338
+ q.text("{")
339
+ q.indent do
340
+ q.breakable
341
+ MethodSignature.new(node.block).format(q)
342
+ end
343
+ q.breakable
344
+ q.text("} ")
345
+ end
346
+
347
+ q.text("-> ")
348
+ q.force_parens { node.type.return_type.format(q) }
349
+ end
350
+ end
351
+
352
+ def pretty_print(q)
353
+ if node.respond_to?(:type_params) && node.type_params.any?
354
+ q.breakable
355
+ q.text("type_params=")
356
+ q.group(2, "[", "]") do
357
+ q.breakable("")
358
+ q.seplist(node.type_params) do |param|
359
+ q.group(2, "(type-param", ")") do
360
+ q.breakable
361
+ q.text("name=")
362
+ q.pp(param.name)
363
+ end
364
+ end
365
+ q.breakable("")
366
+ end
367
+ end
368
+
369
+ if node.type.required_positionals.any?
370
+ q.breakable
371
+ q.text("required_positionals=")
372
+ q.pp(node.type.required_positionals)
373
+ end
374
+
375
+ if node.type.optional_positionals.any?
376
+ q.breakable
377
+ q.text("optional_positionals=")
378
+ q.pp(node.type.optional_positionals)
379
+ end
380
+
381
+ if node.type.rest_positionals
382
+ q.breakable
383
+ q.text("rest_positionals=")
384
+ q.pp(node.type.rest_positionals)
385
+ end
386
+
387
+ if node.type.trailing_positionals.any?
388
+ q.breakable
389
+ q.text("trailing_positionals=")
390
+ q.pp(node.type.trailing_positionals)
391
+ end
392
+
393
+ if node.type.required_keywords.any?
394
+ q.breakable
395
+ q.text("required_keywords=")
396
+ q.pp(node.type.required_keywords)
397
+ end
398
+
399
+ if node.type.optional_keywords.any?
400
+ q.breakable
401
+ q.text("optional_keywords=")
402
+ q.pp(node.type.optional_keywords)
403
+ end
404
+
405
+ if node.type.rest_keywords
406
+ q.breakable
407
+ q.text("rest_keywords=")
408
+ q.pp(node.type.rest_keywords)
409
+ end
410
+
411
+ if node.respond_to?(:block) && node.block
412
+ q.breakable
413
+ q.text("block=")
414
+ q.group(2, "(block", ")") do
415
+ if node.block.required
416
+ q.breakable
417
+ q.text("required")
418
+ end
419
+
420
+ q.breakable
421
+ q.pp(MethodSignature.new(node.block))
422
+ end
423
+ end
424
+
425
+ q.breakable
426
+ q.text("return_type=")
427
+ q.pp(node.type.return_type)
428
+ end
429
+ end
430
+ end
431
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module RBS
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+ require "syntax_tree"
5
+
6
+ require_relative "rbs/declarations"
7
+ require_relative "rbs/members"
8
+ require_relative "rbs/types"
9
+ require_relative "rbs/utils"
10
+ require_relative "rbs/version"
11
+
12
+ module SyntaxTree
13
+ module RBS
14
+ # A slight extension to the default PP formatter that keeps track of the
15
+ # source (so that it can be referenced by annotations if they need it) and
16
+ # keeps track of the level of intersections and unions so that parentheses
17
+ # can be forced if necessary.
18
+ class Formatter < PP
19
+ attr_reader :source
20
+
21
+ def initialize(source, ...)
22
+ super(...)
23
+ @source = source
24
+ @force_parens = false
25
+ end
26
+
27
+ def force_parens
28
+ old_force_parens = @force_parens
29
+ @force_parens = true
30
+ yield
31
+ ensure
32
+ @force_parens = old_force_parens
33
+ end
34
+
35
+ def force_parens?
36
+ @force_parens
37
+ end
38
+ end
39
+
40
+ # This is the root node of the entire tree. It contains all of the top-level
41
+ # declarations within the file.
42
+ class Root
43
+ attr_reader :declarations
44
+
45
+ def initialize(declarations)
46
+ @declarations = declarations
47
+ end
48
+
49
+ def format(q)
50
+ separator =
51
+ lambda do
52
+ q.breakable(force: true)
53
+ q.breakable(force: true)
54
+ end
55
+
56
+ q.seplist(declarations, separator) { |declaration| declaration.format(q) }
57
+ q.breakable(force: true)
58
+ end
59
+
60
+ def pretty_print(q)
61
+ q.group(2, "(root", ")") do
62
+ q.breakable
63
+ q.text("declarations=")
64
+ q.pp(declarations)
65
+ end
66
+ end
67
+ end
68
+
69
+ class << self
70
+ def format(source)
71
+ formatter = Formatter.new(source, [])
72
+ parse(source).format(formatter)
73
+
74
+ formatter.flush
75
+ formatter.output.join
76
+ end
77
+
78
+ def parse(source)
79
+ Root.new(::RBS::Parser.parse_signature(source))
80
+ end
81
+
82
+ def read(filepath)
83
+ File.read(filepath)
84
+ end
85
+ end
86
+ end
87
+
88
+ register_handler(".rbs", RBS)
89
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/syntax_tree/rbs/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "syntax_tree-rbs"
7
+ spec.version = SyntaxTree::RBS::VERSION
8
+ spec.authors = ["Kevin Newton"]
9
+ spec.email = ["kddnewton@gmail.com"]
10
+
11
+ spec.summary = "Syntax Tree support for RBS"
12
+ spec.homepage = "https://github.com/ruby-syntax-tree/syntax_tree-rbs"
13
+ spec.license = "MIT"
14
+ spec.metadata = { "rubygems_mfa_required" => "true" }
15
+
16
+ spec.files = Dir.chdir(__dir__) do
17
+ `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ end
21
+
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = %w[lib]
25
+
26
+ spec.add_dependency "rbs"
27
+ spec.add_dependency "syntax_tree", ">= 2.0.1"
28
+
29
+ spec.add_development_dependency "bundler"
30
+ spec.add_development_dependency "minitest"
31
+ spec.add_development_dependency "rake"
32
+ spec.add_development_dependency "simplecov"
33
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: syntax_tree-rbs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kevin Newton
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rbs
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: syntax_tree
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - kddnewton@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".github/dependabot.yml"
105
+ - ".github/workflows/main.yml"
106
+ - ".gitignore"
107
+ - CHANGELOG.md
108
+ - Gemfile
109
+ - Gemfile.lock
110
+ - LICENSE
111
+ - README.md
112
+ - Rakefile
113
+ - bin/console
114
+ - bin/format
115
+ - bin/parse
116
+ - bin/setup
117
+ - lib/syntax_tree/rbs.rb
118
+ - lib/syntax_tree/rbs/declarations.rb
119
+ - lib/syntax_tree/rbs/members.rb
120
+ - lib/syntax_tree/rbs/types.rb
121
+ - lib/syntax_tree/rbs/utils.rb
122
+ - lib/syntax_tree/rbs/version.rb
123
+ - syntax_tree-rbs.gemspec
124
+ homepage: https://github.com/ruby-syntax-tree/syntax_tree-rbs
125
+ licenses:
126
+ - MIT
127
+ metadata:
128
+ rubygems_mfa_required: 'true'
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubygems_version: 3.3.3
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: Syntax Tree support for RBS
148
+ test_files: []