syntax_tree 2.0.1 → 2.2.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.
@@ -163,6 +163,13 @@ module SyntaxTree
163
163
  line_counts[lineno - 1][column]
164
164
  end
165
165
 
166
+ # This represents the current column we're in relative to the beginning of
167
+ # the current line.
168
+ def current_column
169
+ line = line_counts[lineno - 1]
170
+ line[column].to_i - line.start
171
+ end
172
+
166
173
  # As we build up a list of tokens, we'll periodically need to go backwards
167
174
  # and find the ones that we've already hit in order to determine the
168
175
  # location information for nodes that use them. For example, if you have a
@@ -251,10 +258,13 @@ module SyntaxTree
251
258
  def on_BEGIN(statements)
252
259
  lbrace = find_token(LBrace)
253
260
  rbrace = find_token(RBrace)
261
+ start_char = find_next_statement_start(lbrace.location.end_char)
254
262
 
255
263
  statements.bind(
256
- find_next_statement_start(lbrace.location.end_char),
257
- rbrace.location.start_char
264
+ start_char,
265
+ start_char - line_counts[lbrace.location.start_line - 1].start,
266
+ rbrace.location.start_char,
267
+ rbrace.location.start_column,
258
268
  )
259
269
 
260
270
  keyword = find_token(Kw, "BEGIN")
@@ -271,7 +281,7 @@ module SyntaxTree
271
281
  def on_CHAR(value)
272
282
  CHAR.new(
273
283
  value: value,
274
- location: Location.token(line: lineno, char: char_pos, size: value.size)
284
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
275
285
  )
276
286
  end
277
287
 
@@ -280,10 +290,13 @@ module SyntaxTree
280
290
  def on_END(statements)
281
291
  lbrace = find_token(LBrace)
282
292
  rbrace = find_token(RBrace)
293
+ start_char = find_next_statement_start(lbrace.location.end_char)
283
294
 
284
295
  statements.bind(
285
- find_next_statement_start(lbrace.location.end_char),
286
- rbrace.location.start_char
296
+ start_char,
297
+ start_char - line_counts[lbrace.location.start_line - 1].start,
298
+ rbrace.location.start_char,
299
+ rbrace.location.start_column
287
300
  )
288
301
 
289
302
  keyword = find_token(Kw, "END")
@@ -301,7 +314,7 @@ module SyntaxTree
301
314
  @__end__ =
302
315
  EndContent.new(
303
316
  value: source[(char_pos + value.length)..-1],
304
- location: Location.token(line: lineno, char: char_pos, size: value.size)
317
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
305
318
  )
306
319
  end
307
320
 
@@ -401,12 +414,19 @@ module SyntaxTree
401
414
  # (false | untyped) block
402
415
  # ) -> Args
403
416
  def on_args_add_block(arguments, block)
417
+ # First, see if there is an & operator that could potentially be
418
+ # associated with the block part of this args_add_block. If there is not,
419
+ # then just return the arguments.
404
420
  operator = find_token(Op, "&", consume: false)
405
-
406
- # If we can't find the & operator, then there's no block to add to the
407
- # list, so we're just going to return the arguments as-is.
408
421
  return arguments unless operator
409
422
 
423
+ # If there are any arguments and the operator we found from the list is
424
+ # not after them, then we're going to return the arguments as-is because
425
+ # we're looking at an & that occurs before the arguments are done.
426
+ if arguments.parts.any? && operator.location.start_char < arguments.location.end_char
427
+ return arguments
428
+ end
429
+
410
430
  # Now we know we have an & operator, so we're going to delete it from the
411
431
  # list of tokens to make sure it doesn't get confused with anything else.
412
432
  tokens.delete(operator)
@@ -415,13 +435,6 @@ module SyntaxTree
415
435
  location = operator.location
416
436
  location = operator.location.to(block.location) if block
417
437
 
418
- # If there are any arguments and the operator we found from the list is
419
- # not after them, then we're going to return the arguments as-is because
420
- # we're looking at an & that occurs before the arguments are done.
421
- if arguments.parts.any? && location.start_char < arguments.location.end_char
422
- return arguments
423
- end
424
-
425
438
  # Otherwise, we're looking at an actual block argument (with or without a
426
439
  # block, which could be missing because it could be a bare & since 3.1.0).
427
440
  arg_block = ArgBlock.new(value: block, location: location)
@@ -465,7 +478,7 @@ module SyntaxTree
465
478
  # :call-seq:
466
479
  # on_args_new: () -> Args
467
480
  def on_args_new
468
- Args.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
481
+ Args.new(parts: [], location: Location.fixed(line: lineno, column: current_column, char: char_pos))
469
482
  end
470
483
 
471
484
  # :call-seq:
@@ -503,12 +516,46 @@ module SyntaxTree
503
516
  def on_aryptn(constant, requireds, rest, posts)
504
517
  parts = [constant, *requireds, rest, *posts].compact
505
518
 
519
+ # If there aren't any parts (no constant, no positional arguments), then
520
+ # we're matching an empty array. In this case, we're going to look for the
521
+ # left and right brackets explicitly. Otherwise, we'll just use the bounds
522
+ # of the various parts.
523
+ location =
524
+ if parts.empty?
525
+ find_token(LBracket).location.to(find_token(RBracket).location)
526
+ else
527
+ parts[0].location.to(parts[-1].location)
528
+ end
529
+
530
+ # If there's the optional then keyword, then we'll delete that and use it
531
+ # as the end bounds of the location.
532
+ if token = find_token(Kw, "then", consume: false)
533
+ tokens.delete(token)
534
+ location = location.to(token.location)
535
+ end
536
+
537
+ # If there is a plain *, then we're going to fix up the location of it
538
+ # here because it currently doesn't have anything to use for its precise
539
+ # location. If we hit a comma, then we've gone too far.
540
+ if rest.is_a?(VarField) && rest.value.nil?
541
+ tokens.rindex do |token|
542
+ case token
543
+ in Op[value: "*"]
544
+ rest = VarField.new(value: nil, location: token.location)
545
+ break
546
+ in Comma
547
+ break
548
+ else
549
+ end
550
+ end
551
+ end
552
+
506
553
  AryPtn.new(
507
554
  constant: constant,
508
555
  requireds: requireds || [],
509
556
  rest: rest,
510
557
  posts: posts || [],
511
- location: parts[0].location.to(parts[-1].location)
558
+ location: location
512
559
  )
513
560
  end
514
561
 
@@ -551,7 +598,7 @@ module SyntaxTree
551
598
  def on_backref(value)
552
599
  Backref.new(
553
600
  value: value,
554
- location: Location.token(line: lineno, char: char_pos, size: value.size)
601
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
555
602
  )
556
603
  end
557
604
 
@@ -561,7 +608,7 @@ module SyntaxTree
561
608
  node =
562
609
  Backtick.new(
563
610
  value: value,
564
- location: Location.token(line: lineno, char: char_pos, size: value.size)
611
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
565
612
  )
566
613
 
567
614
  tokens << node
@@ -592,15 +639,20 @@ module SyntaxTree
592
639
  PinnedBegin.new(statement: bodystmt, location: location)
593
640
  else
594
641
  keyword = find_token(Kw, "begin")
595
- end_char =
642
+ end_location =
596
643
  if bodystmt.rescue_clause || bodystmt.ensure_clause ||
597
644
  bodystmt.else_clause
598
- bodystmt.location.end_char
645
+ bodystmt.location
599
646
  else
600
- find_token(Kw, "end").location.end_char
647
+ find_token(Kw, "end").location
601
648
  end
602
649
 
603
- bodystmt.bind(keyword.location.end_char, end_char)
650
+ bodystmt.bind(
651
+ keyword.location.end_char,
652
+ keyword.location.end_column,
653
+ end_location.end_char,
654
+ end_location.end_column
655
+ )
604
656
  location = keyword.location.to(bodystmt.location)
605
657
 
606
658
  Begin.new(bodystmt: bodystmt, location: location)
@@ -682,9 +734,10 @@ module SyntaxTree
682
734
  BodyStmt.new(
683
735
  statements: statements,
684
736
  rescue_clause: rescue_clause,
737
+ else_keyword: else_clause && find_token(Kw, "else"),
685
738
  else_clause: else_clause,
686
739
  ensure_clause: ensure_clause,
687
- location: Location.fixed(line: lineno, char: char_pos)
740
+ location: Location.fixed(line: lineno, char: char_pos, column: current_column)
688
741
  )
689
742
  end
690
743
 
@@ -696,18 +749,24 @@ module SyntaxTree
696
749
  def on_brace_block(block_var, statements)
697
750
  lbrace = find_token(LBrace)
698
751
  rbrace = find_token(RBrace)
752
+ location = (block_var || lbrace).location
753
+ start_char = find_next_statement_start(location.end_char)
699
754
 
700
755
  statements.bind(
701
- find_next_statement_start((block_var || lbrace).location.end_char),
702
- rbrace.location.start_char
756
+ start_char,
757
+ start_char - line_counts[location.start_line - 1].start,
758
+ rbrace.location.start_char,
759
+ rbrace.location.start_column
703
760
  )
704
761
 
705
762
  location =
706
763
  Location.new(
707
764
  start_line: lbrace.location.start_line,
708
765
  start_char: lbrace.location.start_char,
766
+ start_column: lbrace.location.start_column,
709
767
  end_line: [rbrace.location.end_line, statements.location.end_line].max,
710
- end_char: rbrace.location.end_char
768
+ end_char: rbrace.location.end_char,
769
+ end_column: rbrace.location.end_column
711
770
  )
712
771
 
713
772
  BraceBlock.new(
@@ -736,8 +795,14 @@ module SyntaxTree
736
795
  # (:call | Backtick | Const | Ident | Op) message
737
796
  # ) -> Call
738
797
  def on_call(receiver, operator, message)
739
- ending = message
740
- ending = operator if message == :call
798
+ ending =
799
+ if message != :call
800
+ message
801
+ elsif operator != :"::"
802
+ operator
803
+ else
804
+ receiver
805
+ end
741
806
 
742
807
  Call.new(
743
808
  receiver: receiver,
@@ -781,10 +846,14 @@ module SyntaxTree
781
846
  def on_class(constant, superclass, bodystmt)
782
847
  beginning = find_token(Kw, "class")
783
848
  ending = find_token(Kw, "end")
849
+ location = (superclass || constant).location
850
+ start_char = find_next_statement_start(location.end_char)
784
851
 
785
852
  bodystmt.bind(
786
- find_next_statement_start((superclass || constant).location.end_char),
787
- ending.location.start_char
853
+ start_char,
854
+ start_char - line_counts[location.start_line - 1].start,
855
+ ending.location.start_char,
856
+ ending.location.start_column
788
857
  )
789
858
 
790
859
  ClassDeclaration.new(
@@ -801,7 +870,7 @@ module SyntaxTree
801
870
  node =
802
871
  Comma.new(
803
872
  value: value,
804
- location: Location.token(line: lineno, char: char_pos, size: value.size)
873
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
805
874
  )
806
875
 
807
876
  tokens << node
@@ -846,7 +915,7 @@ module SyntaxTree
846
915
  value: value.chomp,
847
916
  inline: value.strip != lines[line - 1].strip,
848
917
  location:
849
- Location.token(line: line, char: char_pos, size: value.size - 1)
918
+ Location.token(line: line, char: char_pos, column: current_column, size: value.size - 1)
850
919
  )
851
920
 
852
921
  @comments << comment
@@ -858,7 +927,7 @@ module SyntaxTree
858
927
  def on_const(value)
859
928
  Const.new(
860
929
  value: value,
861
- location: Location.token(line: lineno, char: char_pos, size: value.size)
930
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
862
931
  )
863
932
  end
864
933
 
@@ -893,7 +962,7 @@ module SyntaxTree
893
962
  def on_cvar(value)
894
963
  CVar.new(
895
964
  value: value,
896
- location: Location.token(line: lineno, char: char_pos, size: value.size)
965
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
897
966
  )
898
967
  end
899
968
 
@@ -917,12 +986,15 @@ module SyntaxTree
917
986
  # location information
918
987
  if params.is_a?(Params) && params.empty?
919
988
  end_char = name.location.end_char
989
+ end_column = name.location.end_column
920
990
  location =
921
991
  Location.new(
922
992
  start_line: params.location.start_line,
923
993
  start_char: end_char,
994
+ start_column: end_column,
924
995
  end_line: params.location.end_line,
925
- end_char: end_char
996
+ end_char: end_char,
997
+ end_column: end_column
926
998
  )
927
999
 
928
1000
  params = Params.new(location: location)
@@ -932,9 +1004,13 @@ module SyntaxTree
932
1004
 
933
1005
  if ending
934
1006
  tokens.delete(ending)
1007
+ start_char = find_next_statement_start(params.location.end_char)
1008
+
935
1009
  bodystmt.bind(
936
- find_next_statement_start(params.location.end_char),
937
- ending.location.start_char
1010
+ start_char,
1011
+ start_char - line_counts[params.location.start_line - 1].start,
1012
+ ending.location.start_char,
1013
+ ending.location.start_column
938
1014
  )
939
1015
 
940
1016
  Def.new(
@@ -992,12 +1068,15 @@ module SyntaxTree
992
1068
  # location information
993
1069
  if params.is_a?(Params) && params.empty?
994
1070
  end_char = name.location.end_char
1071
+ end_column = name.location.end_column
995
1072
  location =
996
1073
  Location.new(
997
1074
  start_line: params.location.start_line,
998
1075
  start_char: end_char,
1076
+ start_column: end_column,
999
1077
  end_line: params.location.end_line,
1000
- end_char: end_char
1078
+ end_char: end_char,
1079
+ end_column: end_column
1001
1080
  )
1002
1081
 
1003
1082
  params = Params.new(location: location)
@@ -1008,9 +1087,13 @@ module SyntaxTree
1008
1087
 
1009
1088
  if ending
1010
1089
  tokens.delete(ending)
1090
+ start_char = find_next_statement_start(params.location.end_char)
1091
+
1011
1092
  bodystmt.bind(
1012
- find_next_statement_start(params.location.end_char),
1013
- ending.location.start_char
1093
+ start_char,
1094
+ start_char - line_counts[params.location.start_line - 1].start,
1095
+ ending.location.start_char,
1096
+ ending.location.start_column
1014
1097
  )
1015
1098
 
1016
1099
  Defs.new(
@@ -1042,10 +1125,14 @@ module SyntaxTree
1042
1125
  def on_do_block(block_var, bodystmt)
1043
1126
  beginning = find_token(Kw, "do")
1044
1127
  ending = find_token(Kw, "end")
1128
+ location = (block_var || beginning).location
1129
+ start_char = find_next_statement_start(location.end_char)
1045
1130
 
1046
1131
  bodystmt.bind(
1047
- find_next_statement_start((block_var || beginning).location.end_char),
1048
- ending.location.start_char
1132
+ start_char,
1133
+ start_char - line_counts[location.start_line - 1].start,
1134
+ ending.location.start_char,
1135
+ ending.location.start_column
1049
1136
  )
1050
1137
 
1051
1138
  DoBlock.new(
@@ -1115,7 +1202,7 @@ module SyntaxTree
1115
1202
  # :call-seq:
1116
1203
  # on_else: (Statements statements) -> Else
1117
1204
  def on_else(statements)
1118
- beginning = find_token(Kw, "else")
1205
+ keyword = find_token(Kw, "else")
1119
1206
 
1120
1207
  # else can either end with an end keyword (in which case we'll want to
1121
1208
  # consume that event) or it can end with an ensure keyword (in which case
@@ -1127,13 +1214,19 @@ module SyntaxTree
1127
1214
 
1128
1215
  node = tokens[index]
1129
1216
  ending = node.value == "end" ? tokens.delete_at(index) : node
1130
- # ending = node
1217
+ start_char = find_next_statement_start(keyword.location.end_char)
1131
1218
 
1132
- statements.bind(beginning.location.end_char, ending.location.start_char)
1219
+ statements.bind(
1220
+ start_char,
1221
+ start_char - line_counts[keyword.location.start_line - 1].start,
1222
+ ending.location.start_char,
1223
+ ending.location.start_column
1224
+ )
1133
1225
 
1134
1226
  Else.new(
1227
+ keyword: keyword,
1135
1228
  statements: statements,
1136
- location: beginning.location.to(ending.location)
1229
+ location: keyword.location.to(ending.location)
1137
1230
  )
1138
1231
  end
1139
1232
 
@@ -1147,7 +1240,12 @@ module SyntaxTree
1147
1240
  beginning = find_token(Kw, "elsif")
1148
1241
  ending = consequent || find_token(Kw, "end")
1149
1242
 
1150
- statements.bind(predicate.location.end_char, ending.location.start_char)
1243
+ statements.bind(
1244
+ predicate.location.end_char,
1245
+ predicate.location.end_column,
1246
+ ending.location.start_char,
1247
+ ending.location.start_column
1248
+ )
1151
1249
 
1152
1250
  Elsif.new(
1153
1251
  predicate: predicate,
@@ -1170,7 +1268,7 @@ module SyntaxTree
1170
1268
  @embdoc =
1171
1269
  EmbDoc.new(
1172
1270
  value: value,
1173
- location: Location.fixed(line: lineno, char: char_pos)
1271
+ location: Location.fixed(line: lineno, column: current_column, char: char_pos)
1174
1272
  )
1175
1273
  end
1176
1274
 
@@ -1185,8 +1283,10 @@ module SyntaxTree
1185
1283
  Location.new(
1186
1284
  start_line: location.start_line,
1187
1285
  start_char: location.start_char,
1286
+ start_column: location.start_column,
1188
1287
  end_line: lineno,
1189
- end_char: char_pos + value.length - 1
1288
+ end_char: char_pos + value.length - 1,
1289
+ end_column: current_column + value.length - 1
1190
1290
  )
1191
1291
  )
1192
1292
 
@@ -1202,7 +1302,7 @@ module SyntaxTree
1202
1302
  node =
1203
1303
  EmbExprBeg.new(
1204
1304
  value: value,
1205
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1305
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1206
1306
  )
1207
1307
 
1208
1308
  tokens << node
@@ -1215,7 +1315,7 @@ module SyntaxTree
1215
1315
  node =
1216
1316
  EmbExprEnd.new(
1217
1317
  value: value,
1218
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1318
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1219
1319
  )
1220
1320
 
1221
1321
  tokens << node
@@ -1228,7 +1328,7 @@ module SyntaxTree
1228
1328
  node =
1229
1329
  EmbVar.new(
1230
1330
  value: value,
1231
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1331
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1232
1332
  )
1233
1333
 
1234
1334
  tokens << node
@@ -1243,9 +1343,12 @@ module SyntaxTree
1243
1343
  # We don't want to consume the :@kw event, because that would break
1244
1344
  # def..ensure..end chains.
1245
1345
  ending = find_token(Kw, "end", consume: false)
1346
+ start_char = find_next_statement_start(keyword.location.end_char)
1246
1347
  statements.bind(
1247
- find_next_statement_start(keyword.location.end_char),
1248
- ending.location.start_char
1348
+ start_char,
1349
+ start_char - line_counts[keyword.location.start_line - 1].start,
1350
+ ending.location.start_char,
1351
+ ending.location.start_column
1249
1352
  )
1250
1353
 
1251
1354
  Ensure.new(
@@ -1292,7 +1395,7 @@ module SyntaxTree
1292
1395
  def on_float(value)
1293
1396
  FloatLiteral.new(
1294
1397
  value: value,
1295
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1398
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1296
1399
  )
1297
1400
  end
1298
1401
 
@@ -1304,15 +1407,35 @@ module SyntaxTree
1304
1407
  # VarField right
1305
1408
  # ) -> FndPtn
1306
1409
  def on_fndptn(constant, left, values, right)
1307
- beginning = constant || find_token(LBracket)
1308
- ending = find_token(RBracket)
1410
+ # The opening of this find pattern is either going to be a left bracket, a
1411
+ # right left parenthesis, or the left splat. We're going to use this to
1412
+ # determine how to find the closing of the pattern, as well as determining
1413
+ # the location of the node.
1414
+ opening =
1415
+ find_token(LBracket, consume: false) ||
1416
+ find_token(LParen, consume: false) ||
1417
+ left
1418
+
1419
+ # The closing is based on the opening, which is either the matched
1420
+ # punctuation or the right splat.
1421
+ closing =
1422
+ case opening
1423
+ in LBracket
1424
+ tokens.delete(opening)
1425
+ find_token(RBracket)
1426
+ in LParen
1427
+ tokens.delete(opening)
1428
+ find_token(RParen)
1429
+ else
1430
+ right
1431
+ end
1309
1432
 
1310
1433
  FndPtn.new(
1311
1434
  constant: constant,
1312
1435
  left: left,
1313
1436
  values: values,
1314
1437
  right: right,
1315
- location: beginning.location.to(ending.location)
1438
+ location: (constant || opening).location.to(closing.location)
1316
1439
  )
1317
1440
  end
1318
1441
 
@@ -1337,7 +1460,9 @@ module SyntaxTree
1337
1460
 
1338
1461
  statements.bind(
1339
1462
  (keyword || collection).location.end_char,
1340
- ending.location.start_char
1463
+ (keyword || collection).location.end_column,
1464
+ ending.location.start_char,
1465
+ ending.location.start_column
1341
1466
  )
1342
1467
 
1343
1468
  if index.is_a?(MLHS)
@@ -1358,7 +1483,7 @@ module SyntaxTree
1358
1483
  def on_gvar(value)
1359
1484
  GVar.new(
1360
1485
  value: value,
1361
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1486
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1362
1487
  )
1363
1488
  end
1364
1489
 
@@ -1379,7 +1504,7 @@ module SyntaxTree
1379
1504
  # on_heredoc_beg: (String value) -> HeredocBeg
1380
1505
  def on_heredoc_beg(value)
1381
1506
  location =
1382
- Location.token(line: lineno, char: char_pos, size: value.size + 1)
1507
+ Location.token(line: lineno, char: char_pos, column: current_column, size: value.size + 1)
1383
1508
 
1384
1509
  # Here we're going to artificially create an extra node type so that if
1385
1510
  # there are comments after the declaration of a heredoc, they get printed.
@@ -1397,6 +1522,7 @@ module SyntaxTree
1397
1522
  @heredocs[-1] = Heredoc.new(
1398
1523
  beginning: heredoc.beginning,
1399
1524
  ending: heredoc.ending,
1525
+ dedent: width,
1400
1526
  parts: string.parts,
1401
1527
  location: heredoc.location
1402
1528
  )
@@ -1410,13 +1536,16 @@ module SyntaxTree
1410
1536
  @heredocs[-1] = Heredoc.new(
1411
1537
  beginning: heredoc.beginning,
1412
1538
  ending: value.chomp,
1539
+ dedent: heredoc.dedent,
1413
1540
  parts: heredoc.parts,
1414
1541
  location:
1415
1542
  Location.new(
1416
1543
  start_line: heredoc.location.start_line,
1417
1544
  start_char: heredoc.location.start_char,
1545
+ start_column: heredoc.location.start_column,
1418
1546
  end_line: lineno,
1419
- end_char: char_pos
1547
+ end_char: char_pos,
1548
+ end_column: current_column,
1420
1549
  )
1421
1550
  )
1422
1551
  end
@@ -1428,13 +1557,30 @@ module SyntaxTree
1428
1557
  # (nil | VarField) keyword_rest
1429
1558
  # ) -> HshPtn
1430
1559
  def on_hshptn(constant, keywords, keyword_rest)
1431
- parts = [constant, keywords, keyword_rest].flatten(2).compact
1560
+ # Create an artificial VarField if we find an extra ** on the end
1561
+ if !keyword_rest && (token = find_token(Op, "**", consume: false))
1562
+ tokens.delete(token)
1563
+ keyword_rest = VarField.new(value: nil, location: token.location)
1564
+ end
1565
+
1566
+ # Delete the optional then keyword
1567
+ if token = find_token(Kw, "then", consume: false)
1568
+ tokens.delete(token)
1569
+ end
1570
+
1571
+ parts = [constant, *keywords&.flatten(1), keyword_rest].compact
1572
+ location =
1573
+ if parts.any?
1574
+ parts[0].location.to(parts[-1].location)
1575
+ else
1576
+ find_token(LBrace).location.to(find_token(RBrace).location)
1577
+ end
1432
1578
 
1433
1579
  HshPtn.new(
1434
1580
  constant: constant,
1435
- keywords: keywords,
1581
+ keywords: keywords || [],
1436
1582
  keyword_rest: keyword_rest,
1437
- location: parts[0].location.to(parts[-1].location)
1583
+ location: location
1438
1584
  )
1439
1585
  end
1440
1586
 
@@ -1443,7 +1589,7 @@ module SyntaxTree
1443
1589
  def on_ident(value)
1444
1590
  Ident.new(
1445
1591
  value: value,
1446
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1592
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1447
1593
  )
1448
1594
  end
1449
1595
 
@@ -1457,7 +1603,12 @@ module SyntaxTree
1457
1603
  beginning = find_token(Kw, "if")
1458
1604
  ending = consequent || find_token(Kw, "end")
1459
1605
 
1460
- statements.bind(predicate.location.end_char, ending.location.start_char)
1606
+ statements.bind(
1607
+ predicate.location.end_char,
1608
+ predicate.location.end_column,
1609
+ ending.location.start_char,
1610
+ ending.location.start_column
1611
+ )
1461
1612
 
1462
1613
  If.new(
1463
1614
  predicate: predicate,
@@ -1503,7 +1654,7 @@ module SyntaxTree
1503
1654
  def on_imaginary(value)
1504
1655
  Imaginary.new(
1505
1656
  value: value,
1506
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1657
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1507
1658
  )
1508
1659
  end
1509
1660
 
@@ -1527,9 +1678,12 @@ module SyntaxTree
1527
1678
  statements_start = token
1528
1679
  end
1529
1680
 
1681
+ start_char = find_next_statement_start(statements_start.location.end_char)
1530
1682
  statements.bind(
1531
- find_next_statement_start(statements_start.location.end_char),
1532
- ending.location.start_char
1683
+ start_char,
1684
+ start_char - line_counts[statements_start.location.start_line - 1].start,
1685
+ ending.location.start_char,
1686
+ ending.location.start_column
1533
1687
  )
1534
1688
 
1535
1689
  In.new(
@@ -1545,7 +1699,7 @@ module SyntaxTree
1545
1699
  def on_int(value)
1546
1700
  Int.new(
1547
1701
  value: value,
1548
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1702
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1549
1703
  )
1550
1704
  end
1551
1705
 
@@ -1554,7 +1708,7 @@ module SyntaxTree
1554
1708
  def on_ivar(value)
1555
1709
  IVar.new(
1556
1710
  value: value,
1557
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1711
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1558
1712
  )
1559
1713
  end
1560
1714
 
@@ -1564,7 +1718,7 @@ module SyntaxTree
1564
1718
  node =
1565
1719
  Kw.new(
1566
1720
  value: value,
1567
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1721
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1568
1722
  )
1569
1723
 
1570
1724
  tokens << node
@@ -1585,7 +1739,7 @@ module SyntaxTree
1585
1739
  def on_label(value)
1586
1740
  Label.new(
1587
1741
  value: value,
1588
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1742
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1589
1743
  )
1590
1744
  end
1591
1745
 
@@ -1595,7 +1749,7 @@ module SyntaxTree
1595
1749
  node =
1596
1750
  LabelEnd.new(
1597
1751
  value: value,
1598
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1752
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1599
1753
  )
1600
1754
 
1601
1755
  tokens << node
@@ -1621,7 +1775,12 @@ module SyntaxTree
1621
1775
  closing = find_token(Kw, "end")
1622
1776
  end
1623
1777
 
1624
- statements.bind(opening.location.end_char, closing.location.start_char)
1778
+ statements.bind(
1779
+ opening.location.end_char,
1780
+ opening.location.end_column,
1781
+ closing.location.start_char,
1782
+ closing.location.start_column
1783
+ )
1625
1784
 
1626
1785
  Lambda.new(
1627
1786
  params: params,
@@ -1636,7 +1795,7 @@ module SyntaxTree
1636
1795
  node =
1637
1796
  LBrace.new(
1638
1797
  value: value,
1639
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1798
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1640
1799
  )
1641
1800
 
1642
1801
  tokens << node
@@ -1649,7 +1808,7 @@ module SyntaxTree
1649
1808
  node =
1650
1809
  LBracket.new(
1651
1810
  value: value,
1652
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1811
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1653
1812
  )
1654
1813
 
1655
1814
  tokens << node
@@ -1662,7 +1821,7 @@ module SyntaxTree
1662
1821
  node =
1663
1822
  LParen.new(
1664
1823
  value: value,
1665
- location: Location.token(line: lineno, char: char_pos, size: value.size)
1824
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1666
1825
  )
1667
1826
 
1668
1827
  tokens << node
@@ -1761,7 +1920,7 @@ module SyntaxTree
1761
1920
  # :call-seq:
1762
1921
  # on_mlhs_new: () -> MLHS
1763
1922
  def on_mlhs_new
1764
- MLHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
1923
+ MLHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos, column: current_column))
1765
1924
  end
1766
1925
 
1767
1926
  # :call-seq:
@@ -1787,10 +1946,13 @@ module SyntaxTree
1787
1946
  def on_module(constant, bodystmt)
1788
1947
  beginning = find_token(Kw, "module")
1789
1948
  ending = find_token(Kw, "end")
1949
+ start_char = find_next_statement_start(constant.location.end_char)
1790
1950
 
1791
1951
  bodystmt.bind(
1792
- find_next_statement_start(constant.location.end_char),
1793
- ending.location.start_char
1952
+ start_char,
1953
+ start_char - line_counts[constant.location.start_line - 1].start,
1954
+ ending.location.start_char,
1955
+ ending.location.start_column
1794
1956
  )
1795
1957
 
1796
1958
  ModuleDeclaration.new(
@@ -1803,7 +1965,7 @@ module SyntaxTree
1803
1965
  # :call-seq:
1804
1966
  # on_mrhs_new: () -> MRHS
1805
1967
  def on_mrhs_new
1806
- MRHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
1968
+ MRHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos, column: current_column))
1807
1969
  end
1808
1970
 
1809
1971
  # :call-seq:
@@ -1872,7 +2034,7 @@ module SyntaxTree
1872
2034
  node =
1873
2035
  Op.new(
1874
2036
  value: value,
1875
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2037
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
1876
2038
  )
1877
2039
 
1878
2040
  tokens << node
@@ -1931,7 +2093,7 @@ module SyntaxTree
1931
2093
  if parts.any?
1932
2094
  parts[0].location.to(parts[-1].location)
1933
2095
  else
1934
- Location.fixed(line: lineno, char: char_pos)
2096
+ Location.fixed(line: lineno, char: char_pos, column: current_column)
1935
2097
  end
1936
2098
 
1937
2099
  Params.new(
@@ -1954,12 +2116,15 @@ module SyntaxTree
1954
2116
 
1955
2117
  if contents && contents.is_a?(Params)
1956
2118
  location = contents.location
2119
+ start_char = find_next_statement_start(lparen.location.end_char)
1957
2120
  location =
1958
2121
  Location.new(
1959
2122
  start_line: location.start_line,
1960
- start_char: find_next_statement_start(lparen.location.end_char),
2123
+ start_char: start_char,
2124
+ start_column: start_char - line_counts[lparen.location.start_line - 1].start,
1961
2125
  end_line: location.end_line,
1962
- end_char: rparen.location.start_char
2126
+ end_char: rparen.location.start_char,
2127
+ end_column: rparen.location.start_column
1963
2128
  )
1964
2129
 
1965
2130
  contents =
@@ -1997,23 +2162,26 @@ module SyntaxTree
1997
2162
  def on_period(value)
1998
2163
  Period.new(
1999
2164
  value: value,
2000
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2165
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2001
2166
  )
2002
2167
  end
2003
2168
 
2004
2169
  # :call-seq:
2005
2170
  # on_program: (Statements statements) -> Program
2006
2171
  def on_program(statements)
2172
+ last_column = source.length - line_counts[lines.length - 1].start
2007
2173
  location =
2008
2174
  Location.new(
2009
2175
  start_line: 1,
2010
2176
  start_char: 0,
2177
+ start_column: 0,
2011
2178
  end_line: lines.length,
2012
- end_char: source.length
2179
+ end_char: source.length,
2180
+ end_column: last_column
2013
2181
  )
2014
2182
 
2015
2183
  statements.body << @__end__ if @__end__
2016
- statements.bind(0, source.length)
2184
+ statements.bind(0, 0, source.length, last_column)
2017
2185
 
2018
2186
  program = Program.new(statements: statements, location: location)
2019
2187
  attach_comments(program, @comments)
@@ -2126,7 +2294,7 @@ module SyntaxTree
2126
2294
  node =
2127
2295
  QSymbolsBeg.new(
2128
2296
  value: value,
2129
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2297
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2130
2298
  )
2131
2299
 
2132
2300
  tokens << node
@@ -2161,7 +2329,7 @@ module SyntaxTree
2161
2329
  node =
2162
2330
  QWordsBeg.new(
2163
2331
  value: value,
2164
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2332
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2165
2333
  )
2166
2334
 
2167
2335
  tokens << node
@@ -2181,7 +2349,7 @@ module SyntaxTree
2181
2349
  def on_rational(value)
2182
2350
  RationalLiteral.new(
2183
2351
  value: value,
2184
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2352
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2185
2353
  )
2186
2354
  end
2187
2355
 
@@ -2191,7 +2359,7 @@ module SyntaxTree
2191
2359
  node =
2192
2360
  RBrace.new(
2193
2361
  value: value,
2194
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2362
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2195
2363
  )
2196
2364
 
2197
2365
  tokens << node
@@ -2204,7 +2372,7 @@ module SyntaxTree
2204
2372
  node =
2205
2373
  RBracket.new(
2206
2374
  value: value,
2207
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2375
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2208
2376
  )
2209
2377
 
2210
2378
  tokens << node
@@ -2238,7 +2406,7 @@ module SyntaxTree
2238
2406
  node =
2239
2407
  RegexpBeg.new(
2240
2408
  value: value,
2241
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2409
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2242
2410
  )
2243
2411
 
2244
2412
  tokens << node
@@ -2250,7 +2418,7 @@ module SyntaxTree
2250
2418
  def on_regexp_end(value)
2251
2419
  RegexpEnd.new(
2252
2420
  value: value,
2253
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2421
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2254
2422
  )
2255
2423
  end
2256
2424
 
@@ -2292,9 +2460,12 @@ module SyntaxTree
2292
2460
  exceptions = exceptions[0] if exceptions.is_a?(Array)
2293
2461
 
2294
2462
  last_node = variable || exceptions || keyword
2463
+ start_char = find_next_statement_start(last_node.location.end_char)
2295
2464
  statements.bind(
2296
- find_next_statement_start(last_node.location.end_char),
2297
- char_pos
2465
+ start_char,
2466
+ start_char - line_counts[last_node.location.start_line - 1].start,
2467
+ char_pos,
2468
+ current_column
2298
2469
  )
2299
2470
 
2300
2471
  # We add an additional inner node here that ripper doesn't provide so that
@@ -2309,13 +2480,16 @@ module SyntaxTree
2309
2480
  Location.new(
2310
2481
  start_line: keyword.location.start_line,
2311
2482
  start_char: keyword.location.end_char + 1,
2483
+ start_column: keyword.location.end_column + 1,
2312
2484
  end_line: last_node.location.end_line,
2313
- end_char: last_node.location.end_char
2485
+ end_char: last_node.location.end_char,
2486
+ end_column: last_node.location.end_column
2314
2487
  )
2315
2488
  )
2316
2489
  end
2317
2490
 
2318
2491
  Rescue.new(
2492
+ keyword: keyword,
2319
2493
  exception: rescue_ex,
2320
2494
  statements: statements,
2321
2495
  consequent: consequent,
@@ -2323,8 +2497,10 @@ module SyntaxTree
2323
2497
  Location.new(
2324
2498
  start_line: keyword.location.start_line,
2325
2499
  start_char: keyword.location.start_char,
2500
+ start_column: keyword.location.start_column,
2326
2501
  end_line: lineno,
2327
- end_char: char_pos
2502
+ end_char: char_pos,
2503
+ end_column: current_column
2328
2504
  )
2329
2505
  )
2330
2506
  end
@@ -2383,7 +2559,7 @@ module SyntaxTree
2383
2559
  node =
2384
2560
  RParen.new(
2385
2561
  value: value,
2386
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2562
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2387
2563
  )
2388
2564
 
2389
2565
  tokens << node
@@ -2395,10 +2571,13 @@ module SyntaxTree
2395
2571
  def on_sclass(target, bodystmt)
2396
2572
  beginning = find_token(Kw, "class")
2397
2573
  ending = find_token(Kw, "end")
2574
+ start_char = find_next_statement_start(target.location.end_char)
2398
2575
 
2399
2576
  bodystmt.bind(
2400
- find_next_statement_start(target.location.end_char),
2401
- ending.location.start_char
2577
+ start_char,
2578
+ start_char - line_counts[target.location.start_line - 1].start,
2579
+ ending.location.start_char,
2580
+ ending.location.start_column
2402
2581
  )
2403
2582
 
2404
2583
  SClass.new(
@@ -2437,7 +2616,7 @@ module SyntaxTree
2437
2616
  Statements.new(
2438
2617
  self,
2439
2618
  body: [],
2440
- location: Location.fixed(line: lineno, char: char_pos)
2619
+ location: Location.fixed(line: lineno, char: char_pos, column: current_column)
2441
2620
  )
2442
2621
  end
2443
2622
 
@@ -2471,7 +2650,7 @@ module SyntaxTree
2471
2650
  def on_string_content
2472
2651
  StringContent.new(
2473
2652
  parts: [],
2474
- location: Location.fixed(line: lineno, char: char_pos)
2653
+ location: Location.fixed(line: lineno, char: char_pos, column: current_column)
2475
2654
  )
2476
2655
  end
2477
2656
 
@@ -2494,18 +2673,22 @@ module SyntaxTree
2494
2673
 
2495
2674
  statements.bind(
2496
2675
  embexpr_beg.location.end_char,
2497
- embexpr_end.location.start_char
2676
+ embexpr_beg.location.end_column,
2677
+ embexpr_end.location.start_char,
2678
+ embexpr_end.location.start_column
2498
2679
  )
2499
2680
 
2500
2681
  location =
2501
2682
  Location.new(
2502
2683
  start_line: embexpr_beg.location.start_line,
2503
2684
  start_char: embexpr_beg.location.start_char,
2685
+ start_column: embexpr_beg.location.start_column,
2504
2686
  end_line: [
2505
2687
  embexpr_end.location.end_line,
2506
2688
  statements.location.end_line
2507
2689
  ].max,
2508
- end_char: embexpr_end.location.end_char
2690
+ end_char: embexpr_end.location.end_char,
2691
+ end_column: embexpr_end.location.end_column
2509
2692
  )
2510
2693
 
2511
2694
  StringEmbExpr.new(statements: statements, location: location)
@@ -2522,6 +2705,7 @@ module SyntaxTree
2522
2705
  Heredoc.new(
2523
2706
  beginning: heredoc.beginning,
2524
2707
  ending: heredoc.ending,
2708
+ dedent: heredoc.dedent,
2525
2709
  parts: string.parts,
2526
2710
  location: heredoc.location
2527
2711
  )
@@ -2533,11 +2717,13 @@ module SyntaxTree
2533
2717
  Location.new(
2534
2718
  start_line: tstring_beg.location.start_line,
2535
2719
  start_char: tstring_beg.location.start_char,
2720
+ start_column: tstring_beg.location.start_column,
2536
2721
  end_line: [
2537
2722
  tstring_end.location.end_line,
2538
2723
  string.location.end_line
2539
2724
  ].max,
2540
- end_char: tstring_end.location.end_char
2725
+ end_char: tstring_end.location.end_char,
2726
+ end_column: tstring_end.location.end_column
2541
2727
  )
2542
2728
 
2543
2729
  StringLiteral.new(
@@ -2566,7 +2752,7 @@ module SyntaxTree
2566
2752
  node =
2567
2753
  SymBeg.new(
2568
2754
  value: value,
2569
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2755
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2570
2756
  )
2571
2757
 
2572
2758
  tokens << node
@@ -2620,7 +2806,7 @@ module SyntaxTree
2620
2806
  node =
2621
2807
  SymbolsBeg.new(
2622
2808
  value: value,
2623
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2809
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2624
2810
  )
2625
2811
 
2626
2812
  tokens << node
@@ -2645,7 +2831,7 @@ module SyntaxTree
2645
2831
  node =
2646
2832
  TLambda.new(
2647
2833
  value: value,
2648
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2834
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2649
2835
  )
2650
2836
 
2651
2837
  tokens << node
@@ -2658,7 +2844,7 @@ module SyntaxTree
2658
2844
  node =
2659
2845
  TLamBeg.new(
2660
2846
  value: value,
2661
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2847
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2662
2848
  )
2663
2849
 
2664
2850
  tokens << node
@@ -2693,7 +2879,7 @@ module SyntaxTree
2693
2879
  node =
2694
2880
  TStringBeg.new(
2695
2881
  value: value,
2696
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2882
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2697
2883
  )
2698
2884
 
2699
2885
  tokens << node
@@ -2705,7 +2891,7 @@ module SyntaxTree
2705
2891
  def on_tstring_content(value)
2706
2892
  TStringContent.new(
2707
2893
  value: value,
2708
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2894
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2709
2895
  )
2710
2896
  end
2711
2897
 
@@ -2715,7 +2901,7 @@ module SyntaxTree
2715
2901
  node =
2716
2902
  TStringEnd.new(
2717
2903
  value: value,
2718
- location: Location.token(line: lineno, char: char_pos, size: value.size)
2904
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
2719
2905
  )
2720
2906
 
2721
2907
  tokens << node
@@ -2731,19 +2917,17 @@ module SyntaxTree
2731
2917
  # parentheses they don't get reported as a paren node for some reason.
2732
2918
 
2733
2919
  beginning = find_token(Kw, "not")
2734
- ending = statement
2735
-
2736
- range = beginning.location.end_char...statement.location.start_char
2737
- paren = source[range].include?("(")
2920
+ ending = statement || beginning
2921
+ parentheses = source[beginning.location.end_char] == "("
2738
2922
 
2739
- if paren
2923
+ if parentheses
2740
2924
  find_token(LParen)
2741
2925
  ending = find_token(RParen)
2742
2926
  end
2743
2927
 
2744
2928
  Not.new(
2745
2929
  statement: statement,
2746
- parentheses: paren,
2930
+ parentheses: parentheses,
2747
2931
  location: beginning.location.to(ending.location)
2748
2932
  )
2749
2933
  else
@@ -2789,7 +2973,12 @@ module SyntaxTree
2789
2973
  beginning = find_token(Kw, "unless")
2790
2974
  ending = consequent || find_token(Kw, "end")
2791
2975
 
2792
- statements.bind(predicate.location.end_char, ending.location.start_char)
2976
+ statements.bind(
2977
+ predicate.location.end_char,
2978
+ predicate.location.end_column,
2979
+ ending.location.start_char,
2980
+ ending.location.start_column
2981
+ )
2793
2982
 
2794
2983
  Unless.new(
2795
2984
  predicate: predicate,
@@ -2826,7 +3015,12 @@ module SyntaxTree
2826
3015
  end
2827
3016
 
2828
3017
  # Update the Statements location information
2829
- statements.bind(predicate.location.end_char, ending.location.start_char)
3018
+ statements.bind(
3019
+ predicate.location.end_char,
3020
+ predicate.location.end_column,
3021
+ ending.location.start_char,
3022
+ ending.location.start_column
3023
+ )
2830
3024
 
2831
3025
  Until.new(
2832
3026
  predicate: predicate,
@@ -2865,12 +3059,12 @@ module SyntaxTree
2865
3059
  # ) -> VarField
2866
3060
  def on_var_field(value)
2867
3061
  location =
2868
- if value
3062
+ if value && value != :nil
2869
3063
  value.location
2870
3064
  else
2871
3065
  # You can hit this pattern if you're assigning to a splat using
2872
3066
  # pattern matching syntax in Ruby 2.7+
2873
- Location.fixed(line: lineno, char: char_pos)
3067
+ Location.fixed(line: lineno, char: char_pos, column: current_column)
2874
3068
  end
2875
3069
 
2876
3070
  VarField.new(value: value, location: location)
@@ -2898,7 +3092,7 @@ module SyntaxTree
2898
3092
  # :call-seq:
2899
3093
  # on_void_stmt: () -> VoidStmt
2900
3094
  def on_void_stmt
2901
- VoidStmt.new(location: Location.fixed(line: lineno, char: char_pos))
3095
+ VoidStmt.new(location: Location.fixed(line: lineno, char: char_pos, column: current_column))
2902
3096
  end
2903
3097
 
2904
3098
  # :call-seq:
@@ -2917,9 +3111,13 @@ module SyntaxTree
2917
3111
  statements_start = token
2918
3112
  end
2919
3113
 
3114
+ start_char = find_next_statement_start(statements_start.location.end_char)
3115
+
2920
3116
  statements.bind(
2921
- find_next_statement_start(statements_start.location.end_char),
2922
- ending.location.start_char
3117
+ start_char,
3118
+ start_char - line_counts[statements_start.location.start_line - 1].start,
3119
+ ending.location.start_char,
3120
+ ending.location.start_column
2923
3121
  )
2924
3122
 
2925
3123
  When.new(
@@ -2945,7 +3143,12 @@ module SyntaxTree
2945
3143
  end
2946
3144
 
2947
3145
  # Update the Statements location information
2948
- statements.bind(predicate.location.end_char, ending.location.start_char)
3146
+ statements.bind(
3147
+ predicate.location.end_char,
3148
+ predicate.location.end_column,
3149
+ ending.location.start_char,
3150
+ ending.location.start_column
3151
+ )
2949
3152
 
2950
3153
  While.new(
2951
3154
  predicate: predicate,
@@ -2981,7 +3184,7 @@ module SyntaxTree
2981
3184
  # :call-seq:
2982
3185
  # on_word_new: () -> Word
2983
3186
  def on_word_new
2984
- Word.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
3187
+ Word.new(parts: [], location: Location.fixed(line: lineno, char: char_pos, column: current_column))
2985
3188
  end
2986
3189
 
2987
3190
  # :call-seq:
@@ -3000,7 +3203,7 @@ module SyntaxTree
3000
3203
  node =
3001
3204
  WordsBeg.new(
3002
3205
  value: value,
3003
- location: Location.token(line: lineno, char: char_pos, size: value.size)
3206
+ location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size)
3004
3207
  )
3005
3208
 
3006
3209
  tokens << node
@@ -3055,6 +3258,7 @@ module SyntaxTree
3055
3258
  Heredoc.new(
3056
3259
  beginning: heredoc.beginning,
3057
3260
  ending: heredoc.ending,
3261
+ dedent: heredoc.dedent,
3058
3262
  parts: xstring.parts,
3059
3263
  location: heredoc.location
3060
3264
  )