rbi 0.1.14 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3705c571d4b2685e60fec1c0dc54435caed79ba988239006be0bf14757790c51
4
- data.tar.gz: 4560dc122385781dd604cdc4ca57835caa22e3f29b2cafa5b7e2e468032c5923
3
+ metadata.gz: 68328047c11f18c7617e8101a773a3b3b9438be4e36a0b01072274eba2b1c583
4
+ data.tar.gz: 275b2ed35e5486461d2ebf4afae88d7b75b9f26ac1d7d8d9693ff630b17dff3d
5
5
  SHA512:
6
- metadata.gz: 3f70d57182bed826c64ca53ed2d62a1da0aeb0dee1badbe774ed00c8bd505b8e40f00363c4ca069efef457649cff19ce6b2be595e6dca453e56456eb4580ca38
7
- data.tar.gz: 5c60fedeeb8995f9e6ed4ef926f68870ed88021de20e60294e1aec97029c728f24d098a8d297ec967dbfc23c31788dbb30d82b8f925f1c95962df7772580c294
6
+ metadata.gz: 53e510f0df7f4e44c45686774f3573e06031ba7c81a29f7ff429b2dacb601c42a83be37acca12809161ef7acd50722682ad88483b12d4af8d536c6f709e540b8
7
+ data.tar.gz: 172bbba7e9c3cd29032d4d34a4fda47ce81728b971a8956d742b0d012ca393b3f555109493ec2ebfe1aa3087a142da109061fc07074f8a4b721096cf1884091d
data/Gemfile CHANGED
@@ -10,7 +10,7 @@ group(:development, :test) do
10
10
  gem("minitest")
11
11
  gem("minitest-reporters")
12
12
  gem("rake", "~> 13.2")
13
- gem("rubocop", "~> 1.65", require: false)
13
+ gem("rubocop", "~> 1.66", require: false)
14
14
  gem("rubocop-shopify", require: false)
15
15
  gem("rubocop-sorbet", require: false)
16
16
  gem("sorbet", ">= 0.5.9204", require: false)
data/lib/rbi/formatter.rb CHANGED
@@ -17,7 +17,7 @@ module RBI
17
17
  group_nodes: T::Boolean,
18
18
  max_line_length: T.nilable(Integer),
19
19
  nest_singleton_methods: T::Boolean,
20
- nest_non_public_methods: T::Boolean,
20
+ nest_non_public_members: T::Boolean,
21
21
  sort_nodes: T::Boolean,
22
22
  ).void
23
23
  end
@@ -26,14 +26,14 @@ module RBI
26
26
  group_nodes: false,
27
27
  max_line_length: nil,
28
28
  nest_singleton_methods: false,
29
- nest_non_public_methods: false,
29
+ nest_non_public_members: false,
30
30
  sort_nodes: false
31
31
  )
32
32
  @add_sig_templates = add_sig_templates
33
33
  @group_nodes = group_nodes
34
34
  @max_line_length = max_line_length
35
35
  @nest_singleton_methods = nest_singleton_methods
36
- @nest_non_public_methods = nest_non_public_methods
36
+ @nest_non_public_members = nest_non_public_members
37
37
  @sort_nodes = sort_nodes
38
38
  end
39
39
 
@@ -52,7 +52,7 @@ module RBI
52
52
  def format_tree(tree)
53
53
  tree.add_sig_templates! if @add_sig_templates
54
54
  tree.nest_singleton_methods! if @nest_singleton_methods
55
- tree.nest_non_public_methods! if @nest_non_public_methods
55
+ tree.nest_non_public_members! if @nest_non_public_members
56
56
  tree.group_nodes! if @group_nodes
57
57
  tree.sort_nodes! if @sort_nodes
58
58
  end
data/lib/rbi/model.rb CHANGED
@@ -574,7 +574,7 @@ module RBI
574
574
  sig do
575
575
  params(
576
576
  params: T::Array[SigParam],
577
- return_type: T.nilable(String),
577
+ return_type: T.any(String, Type),
578
578
  is_abstract: T::Boolean,
579
579
  is_override: T::Boolean,
580
580
  is_overridable: T::Boolean,
@@ -586,7 +586,7 @@ module RBI
586
586
  end
587
587
  def add_sig(
588
588
  params: [],
589
- return_type: nil,
589
+ return_type: "void",
590
590
  is_abstract: false,
591
591
  is_override: false,
592
592
  is_overridable: false,
@@ -928,8 +928,10 @@ module RBI
928
928
  @visibility = visibility
929
929
  end
930
930
 
931
- sig { params(other: Visibility).returns(T::Boolean) }
931
+ sig { params(other: T.nilable(Object)).returns(T::Boolean) }
932
932
  def ==(other)
933
+ return false unless other.is_a?(Visibility)
934
+
933
935
  visibility == other.visibility
934
936
  end
935
937
 
@@ -1099,13 +1101,13 @@ module RBI
1099
1101
 
1100
1102
  # Sorbet's sigs
1101
1103
 
1102
- class Sig < Node
1104
+ class Sig < NodeWithComments
1103
1105
  extend T::Sig
1104
1106
 
1105
1107
  sig { returns(T::Array[SigParam]) }
1106
1108
  attr_reader :params
1107
1109
 
1108
- sig { returns(T.nilable(String)) }
1110
+ sig { returns(T.any(Type, String)) }
1109
1111
  attr_accessor :return_type
1110
1112
 
1111
1113
  sig { returns(T::Boolean) }
@@ -1120,7 +1122,7 @@ module RBI
1120
1122
  sig do
1121
1123
  params(
1122
1124
  params: T::Array[SigParam],
1123
- return_type: T.nilable(String),
1125
+ return_type: T.any(Type, String),
1124
1126
  is_abstract: T::Boolean,
1125
1127
  is_override: T::Boolean,
1126
1128
  is_overridable: T::Boolean,
@@ -1128,12 +1130,13 @@ module RBI
1128
1130
  type_params: T::Array[String],
1129
1131
  checked: T.nilable(Symbol),
1130
1132
  loc: T.nilable(Loc),
1133
+ comments: T::Array[Comment],
1131
1134
  block: T.nilable(T.proc.params(node: Sig).void),
1132
1135
  ).void
1133
1136
  end
1134
1137
  def initialize(
1135
1138
  params: [],
1136
- return_type: nil,
1139
+ return_type: "void",
1137
1140
  is_abstract: false,
1138
1141
  is_override: false,
1139
1142
  is_overridable: false,
@@ -1141,9 +1144,10 @@ module RBI
1141
1144
  type_params: [],
1142
1145
  checked: nil,
1143
1146
  loc: nil,
1147
+ comments: [],
1144
1148
  &block
1145
1149
  )
1146
- super(loc: loc)
1150
+ super(loc: loc, comments: comments)
1147
1151
  @params = params
1148
1152
  @return_type = return_type
1149
1153
  @is_abstract = is_abstract
@@ -1160,7 +1164,7 @@ module RBI
1160
1164
  @params << param
1161
1165
  end
1162
1166
 
1163
- sig { params(name: String, type: String).void }
1167
+ sig { params(name: String, type: T.any(Type, String)).void }
1164
1168
  def add_param(name, type)
1165
1169
  @params << SigParam.new(name, type)
1166
1170
  end
@@ -1169,7 +1173,7 @@ module RBI
1169
1173
  def ==(other)
1170
1174
  return false unless other.is_a?(Sig)
1171
1175
 
1172
- params == other.params && return_type == other.return_type && is_abstract == other.is_abstract &&
1176
+ params == other.params && return_type.to_s == other.return_type.to_s && is_abstract == other.is_abstract &&
1173
1177
  is_override == other.is_override && is_overridable == other.is_overridable && is_final == other.is_final &&
1174
1178
  type_params == other.type_params && checked == other.checked
1175
1179
  end
@@ -1179,12 +1183,15 @@ module RBI
1179
1183
  extend T::Sig
1180
1184
 
1181
1185
  sig { returns(String) }
1182
- attr_reader :name, :type
1186
+ attr_reader :name
1187
+
1188
+ sig { returns(T.any(Type, String)) }
1189
+ attr_reader :type
1183
1190
 
1184
1191
  sig do
1185
1192
  params(
1186
1193
  name: String,
1187
- type: String,
1194
+ type: T.any(Type, String),
1188
1195
  loc: T.nilable(Loc),
1189
1196
  comments: T::Array[Comment],
1190
1197
  block: T.nilable(T.proc.params(node: SigParam).void),
@@ -1199,7 +1206,7 @@ module RBI
1199
1206
 
1200
1207
  sig { params(other: Object).returns(T::Boolean) }
1201
1208
  def ==(other)
1202
- other.is_a?(SigParam) && name == other.name && type == other.type
1209
+ other.is_a?(SigParam) && name == other.name && type.to_s == other.type.to_s
1203
1210
  end
1204
1211
  end
1205
1212
 
@@ -1229,7 +1236,10 @@ module RBI
1229
1236
  abstract!
1230
1237
 
1231
1238
  sig { returns(String) }
1232
- attr_accessor :name, :type
1239
+ attr_accessor :name
1240
+
1241
+ sig { returns(T.any(Type, String)) }
1242
+ attr_accessor :type
1233
1243
 
1234
1244
  sig { returns(T.nilable(String)) }
1235
1245
  attr_accessor :default
@@ -1237,7 +1247,7 @@ module RBI
1237
1247
  sig do
1238
1248
  params(
1239
1249
  name: String,
1240
- type: String,
1250
+ type: T.any(Type, String),
1241
1251
  default: T.nilable(String),
1242
1252
  loc: T.nilable(Loc),
1243
1253
  comments: T::Array[Comment],
@@ -1260,7 +1270,7 @@ module RBI
1260
1270
  sig do
1261
1271
  params(
1262
1272
  name: String,
1263
- type: String,
1273
+ type: T.any(Type, String),
1264
1274
  default: T.nilable(String),
1265
1275
  loc: T.nilable(Loc),
1266
1276
  comments: T::Array[Comment],
@@ -1290,7 +1300,7 @@ module RBI
1290
1300
  sig do
1291
1301
  params(
1292
1302
  name: String,
1293
- type: String,
1303
+ type: T.any(Type, String),
1294
1304
  default: T.nilable(String),
1295
1305
  loc: T.nilable(Loc),
1296
1306
  comments: T::Array[Comment],
@@ -1333,39 +1343,29 @@ module RBI
1333
1343
  end
1334
1344
  end
1335
1345
 
1336
- class TEnumBlock < NodeWithComments
1346
+ class TEnumBlock < Scope
1337
1347
  extend T::Sig
1338
1348
 
1339
- sig { returns(T::Array[String]) }
1340
- attr_reader :names
1341
-
1342
1349
  sig do
1343
1350
  params(
1344
- names: T::Array[String],
1345
1351
  loc: T.nilable(Loc),
1346
1352
  comments: T::Array[Comment],
1347
1353
  block: T.nilable(T.proc.params(node: TEnumBlock).void),
1348
1354
  ).void
1349
1355
  end
1350
- def initialize(names = [], loc: nil, comments: [], &block)
1351
- super(loc: loc, comments: comments)
1352
- @names = names
1356
+ def initialize(loc: nil, comments: [], &block)
1357
+ super(loc: loc, comments: comments) {}
1353
1358
  block&.call(self)
1354
1359
  end
1355
1360
 
1356
- sig { returns(T::Boolean) }
1357
- def empty?
1358
- names.empty?
1359
- end
1360
-
1361
- sig { params(name: String).void }
1362
- def <<(name)
1363
- @names << name
1361
+ sig { override.returns(String) }
1362
+ def fully_qualified_name
1363
+ "#{parent_scope&.fully_qualified_name}.enums"
1364
1364
  end
1365
1365
 
1366
1366
  sig { override.returns(String) }
1367
1367
  def to_s
1368
- "#{parent_scope&.fully_qualified_name}.enums"
1368
+ fully_qualified_name
1369
1369
  end
1370
1370
  end
1371
1371
 
data/lib/rbi/parser.rb CHANGED
@@ -169,22 +169,38 @@ module RBI
169
169
  @scopes_stack = T.let([@tree], T::Array[Tree])
170
170
  @last_node = T.let(nil, T.nilable(Prism::Node))
171
171
  @last_sigs = T.let([], T::Array[RBI::Sig])
172
- @last_sigs_comments = T.let([], T::Array[Comment])
173
172
  end
174
173
 
175
174
  sig { override.params(node: Prism::ClassNode).void }
176
175
  def visit_class_node(node)
177
176
  @last_node = node
178
- scope = Class.new(
179
- node_string!(node.constant_path),
180
- superclass_name: node_string(node.superclass),
181
- loc: node_loc(node),
182
- comments: node_comments(node),
183
- )
177
+ superclass_name = node_string(node.superclass)
178
+ scope = case superclass_name
179
+ when /^(::)?T::Struct$/
180
+ TStruct.new(
181
+ node_string!(node.constant_path),
182
+ loc: node_loc(node),
183
+ comments: node_comments(node),
184
+ )
185
+ when /^(::)?T::Enum$/
186
+ TEnum.new(
187
+ node_string!(node.constant_path),
188
+ loc: node_loc(node),
189
+ comments: node_comments(node),
190
+ )
191
+ else
192
+ Class.new(
193
+ node_string!(node.constant_path),
194
+ superclass_name: superclass_name,
195
+ loc: node_loc(node),
196
+ comments: node_comments(node),
197
+ )
198
+ end
184
199
 
185
200
  current_scope << scope
186
201
  @scopes_stack << scope
187
202
  visit(node.body)
203
+ scope.nodes.concat(current_sigs)
188
204
  collect_dangling_comments(node)
189
205
  @scopes_stack.pop
190
206
  @last_node = nil
@@ -240,12 +256,19 @@ module RBI
240
256
  sig { override.params(node: Prism::DefNode).void }
241
257
  def visit_def_node(node)
242
258
  @last_node = node
259
+
260
+ # We need to collect the comments with `current_sigs_comments` _before_ visiting the parameters to make sure
261
+ # the method comments are properly associated with the sigs and not the parameters.
262
+ sigs = current_sigs
263
+ comments = detach_comments_from_sigs(sigs) + node_comments(node)
264
+ params = parse_params(node.parameters)
265
+
243
266
  current_scope << Method.new(
244
267
  node.name.to_s,
245
- params: parse_params(node.parameters),
246
- sigs: current_sigs,
268
+ params: params,
269
+ sigs: sigs,
247
270
  loc: node_loc(node),
248
- comments: current_sigs_comments + node_comments(node),
271
+ comments: comments,
249
272
  is_singleton: !!node.receiver,
250
273
  )
251
274
  @last_node = nil
@@ -263,6 +286,7 @@ module RBI
263
286
  current_scope << scope
264
287
  @scopes_stack << scope
265
288
  visit(node.body)
289
+ scope.nodes.concat(current_sigs)
266
290
  collect_dangling_comments(node)
267
291
  @scopes_stack.pop
268
292
  @last_node = nil
@@ -272,7 +296,7 @@ module RBI
272
296
  def visit_program_node(node)
273
297
  @last_node = node
274
298
  super
275
-
299
+ @tree.nodes.concat(current_sigs)
276
300
  collect_orphan_comments
277
301
  separate_header_comments
278
302
  set_root_tree_loc
@@ -290,6 +314,7 @@ module RBI
290
314
  current_scope << scope
291
315
  @scopes_stack << scope
292
316
  visit(node.body)
317
+ scope.nodes.concat(current_sigs)
293
318
  collect_dangling_comments(node)
294
319
  @scopes_stack.pop
295
320
  @last_node = nil
@@ -314,11 +339,14 @@ module RBI
314
339
  return
315
340
  end
316
341
 
342
+ sigs = current_sigs
343
+ comments = detach_comments_from_sigs(sigs) + node_comments(node)
344
+
317
345
  current_scope << AttrReader.new(
318
346
  *T.unsafe(args.arguments.map { |arg| node_string!(arg).delete_prefix(":").to_sym }),
319
- sigs: current_sigs,
347
+ sigs: sigs,
320
348
  loc: node_loc(node),
321
- comments: current_sigs_comments + node_comments(node),
349
+ comments: comments,
322
350
  )
323
351
  when "attr_writer"
324
352
  args = node.arguments
@@ -328,11 +356,14 @@ module RBI
328
356
  return
329
357
  end
330
358
 
359
+ sigs = current_sigs
360
+ comments = detach_comments_from_sigs(sigs) + node_comments(node)
361
+
331
362
  current_scope << AttrWriter.new(
332
363
  *T.unsafe(args.arguments.map { |arg| node_string!(arg).delete_prefix(":").to_sym }),
333
- sigs: current_sigs,
364
+ sigs: sigs,
334
365
  loc: node_loc(node),
335
- comments: current_sigs_comments + node_comments(node),
366
+ comments: comments,
336
367
  )
337
368
  when "attr_accessor"
338
369
  args = node.arguments
@@ -342,32 +373,30 @@ module RBI
342
373
  return
343
374
  end
344
375
 
376
+ sigs = current_sigs
377
+ comments = detach_comments_from_sigs(sigs) + node_comments(node)
378
+
345
379
  current_scope << AttrAccessor.new(
346
380
  *T.unsafe(args.arguments.map { |arg| node_string!(arg).delete_prefix(":").to_sym }),
347
- sigs: current_sigs,
381
+ sigs: sigs,
348
382
  loc: node_loc(node),
349
- comments: current_sigs_comments + node_comments(node),
383
+ comments: comments,
350
384
  )
351
385
  when "enums"
352
- block = node.block
353
-
354
- unless block.is_a?(Prism::BlockNode)
355
- @last_node = nil
356
- return
357
- end
358
-
359
- body = block.body
360
-
361
- unless body.is_a?(Prism::StatementsNode)
362
- @last_node = nil
363
- return
386
+ if node.block && node.arguments.nil?
387
+ scope = TEnumBlock.new(loc: node_loc(node), comments: node_comments(node))
388
+ current_scope << scope
389
+ @scopes_stack << scope
390
+ visit(node.block)
391
+ @scopes_stack.pop
392
+ else
393
+ current_scope << Send.new(
394
+ message,
395
+ parse_send_args(node.arguments),
396
+ loc: node_loc(node),
397
+ comments: node_comments(node),
398
+ )
364
399
  end
365
-
366
- current_scope << TEnumBlock.new(
367
- body.body.map { |stmt| T.cast(stmt, Prism::ConstantWriteNode).name.to_s },
368
- loc: node_loc(node),
369
- comments: node_comments(node),
370
- )
371
400
  when "extend"
372
401
  args = node.arguments
373
402
 
@@ -415,6 +444,13 @@ module RBI
415
444
  case last_node
416
445
  when Method, Attr
417
446
  last_node.visibility = parse_visibility(node.name.to_s, node)
447
+ when Send
448
+ current_scope << Send.new(
449
+ message,
450
+ parse_send_args(node.arguments),
451
+ loc: node_loc(node),
452
+ comments: node_comments(node),
453
+ )
418
454
  else
419
455
  raise ParseError.new(
420
456
  "Unexpected token `#{node.message}` before `#{last_node&.string&.strip}`",
@@ -470,7 +506,7 @@ module RBI
470
506
 
471
507
  last_node_last_line = node.child_nodes.last&.location&.end_line
472
508
 
473
- last_line.downto(first_line) do |line|
509
+ first_line.upto(last_line) do |line|
474
510
  comment = @comments_by_line[line]
475
511
  next unless comment
476
512
  break if last_node_last_line && line <= last_node_last_line
@@ -516,10 +552,15 @@ module RBI
516
552
  sigs
517
553
  end
518
554
 
519
- sig { returns(T::Array[Comment]) }
520
- def current_sigs_comments
521
- comments = @last_sigs_comments.dup
522
- @last_sigs_comments.clear
555
+ sig { params(sigs: T::Array[Sig]).returns(T::Array[Comment]) }
556
+ def detach_comments_from_sigs(sigs)
557
+ comments = T.let([], T::Array[Comment])
558
+
559
+ sigs.each do |sig|
560
+ comments += sig.comments.dup
561
+ sig.comments.clear
562
+ end
563
+
523
564
  comments
524
565
  end
525
566
 
@@ -646,11 +687,10 @@ module RBI
646
687
 
647
688
  sig { params(node: Prism::CallNode).returns(Sig) }
648
689
  def parse_sig(node)
649
- @last_sigs_comments = node_comments(node)
650
-
651
690
  builder = SigBuilder.new(@source, file: @file)
652
691
  builder.current.loc = node_loc(node)
653
692
  builder.visit_call_node(node)
693
+ builder.current.comments = node_comments(node)
654
694
  builder.current
655
695
  end
656
696
 
@@ -745,11 +785,11 @@ module RBI
745
785
  def parse_visibility(name, node)
746
786
  case name
747
787
  when "public"
748
- Public.new(loc: node_loc(node))
788
+ Public.new(loc: node_loc(node), comments: node_comments(node))
749
789
  when "protected"
750
- Protected.new(loc: node_loc(node))
790
+ Protected.new(loc: node_loc(node), comments: node_comments(node))
751
791
  when "private"
752
- Private.new(loc: node_loc(node))
792
+ Private.new(loc: node_loc(node), comments: node_comments(node))
753
793
  else
754
794
  raise ParseError.new("Unexpected visibility `#{name}`", node_loc(node))
755
795
  end
@@ -781,10 +821,7 @@ module RBI
781
821
 
782
822
  sig { params(node: T.nilable(Prism::Node)).returns(T::Boolean) }
783
823
  def type_variable_definition?(node)
784
- return false unless node.is_a?(Prism::CallNode)
785
- return false unless node.block
786
-
787
- node.message == "type_member" || node.message == "type_template"
824
+ node.is_a?(Prism::CallNode) && (node.message == "type_member" || node.message == "type_template")
788
825
  end
789
826
  end
790
827
 
@@ -839,7 +876,7 @@ module RBI
839
876
  end
840
877
  end
841
878
  when "void"
842
- @current.return_type = nil
879
+ @current.return_type = "void"
843
880
  end
844
881
 
845
882
  visit(node.receiver)
data/lib/rbi/printer.rb CHANGED
@@ -169,6 +169,10 @@ module RBI
169
169
  case node
170
170
  when Module
171
171
  printt("module #{node.name}")
172
+ when TEnum
173
+ printt("class #{node.name} < T::Enum")
174
+ when TStruct
175
+ printt("class #{node.name} < T::Struct")
172
176
  when Class
173
177
  printt("class #{node.name}")
174
178
  superclass = node.superclass_name
@@ -184,10 +188,6 @@ module RBI
184
188
  end
185
189
  when SingletonClass
186
190
  printt("class << self")
187
- when TStruct
188
- printt("class #{node.name} < T::Struct")
189
- when TEnum
190
- printt("class #{node.name} < T::Enum")
191
191
  else
192
192
  raise PrinterError, "Unhandled node: #{node.class}"
193
193
  end
@@ -426,6 +426,7 @@ module RBI
426
426
  sig { override.params(node: Sig).void }
427
427
  def visit_sig(node)
428
428
  print_loc(node)
429
+ visit_all(node.comments)
429
430
 
430
431
  max_line_length = self.max_line_length
431
432
  if oneline?(node) && max_line_length.nil?
@@ -492,9 +493,7 @@ module RBI
492
493
 
493
494
  printl("enums do")
494
495
  indent
495
- node.names.each do |name|
496
- printl("#{name} = new")
497
- end
496
+ visit_all(node.nodes)
498
497
  dedent
499
498
  printl("end")
500
499
  end
@@ -611,7 +610,7 @@ module RBI
611
610
  def print_sig_param_comment_leading_space(node, last:)
612
611
  printn
613
612
  printt
614
- print(" " * (node.name.size + node.type.size + 3))
613
+ print(" " * (node.name.size + node.type.to_s.size + 3))
615
614
  print(" ") unless last
616
615
  end
617
616
 
@@ -654,10 +653,10 @@ module RBI
654
653
  print(").")
655
654
  end
656
655
  return_type = node.return_type
657
- if node.return_type && node.return_type != "void"
658
- print("returns(#{return_type})")
659
- else
656
+ if node.return_type.to_s == "void"
660
657
  print("void")
658
+ else
659
+ print("returns(#{return_type})")
661
660
  end
662
661
  printn(" }")
663
662
  end
@@ -707,10 +706,10 @@ module RBI
707
706
  print(".") if modifiers.any? || params.any?
708
707
 
709
708
  return_type = node.return_type
710
- if return_type && return_type != "void"
711
- print("returns(#{return_type})")
712
- else
709
+ if return_type.to_s == "void"
713
710
  print("void")
711
+ else
712
+ print("returns(#{return_type})")
714
713
  end
715
714
  printn
716
715
  dedent