syntax_tree-rbs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []