@@ -1647,14 +1647,21 @@ def visit_interpolated_regular_expression_node(node)
1647
1647
# "foo #{bar}"
1648
1648
# ^^^^^^^^^^^^
1649
1649
def visit_interpolated_string_node ( node )
1650
- bounds ( node . parts . first . location )
1651
- parts =
1652
- node . parts . inject ( on_string_content ) do |content , part |
1653
- on_string_add ( content , visit_string_content ( part ) )
1654
- end
1650
+ if node . opening . start_with? ( "<<~" )
1651
+ heredoc = visit_string_heredoc_node ( node . parts )
1655
1652
1656
- bounds ( node . location )
1657
- on_string_literal ( parts )
1653
+ bounds ( node . location )
1654
+ on_string_literal ( heredoc )
1655
+ else
1656
+ bounds ( node . parts . first . location )
1657
+ parts =
1658
+ node . parts . inject ( on_string_content ) do |content , part |
1659
+ on_string_add ( content , visit_string_content ( part ) )
1660
+ end
1661
+
1662
+ bounds ( node . location )
1663
+ on_string_literal ( parts )
1664
+ end
1658
1665
end
1659
1666
1660
1667
# :"foo #{bar}"
@@ -1673,14 +1680,21 @@ def visit_interpolated_symbol_node(node)
1673
1680
# `foo #{bar}`
1674
1681
# ^^^^^^^^^^^^
1675
1682
def visit_interpolated_x_string_node ( node )
1676
- bounds ( node . parts . first . location )
1677
- parts =
1678
- node . parts . inject ( on_xstring_new ) do |content , part |
1679
- on_xstring_add ( content , visit_string_content ( part ) )
1680
- end
1683
+ if node . opening . start_with? ( "<<~" )
1684
+ heredoc = visit_x_string_heredoc_node ( node . parts )
1681
1685
1682
- bounds ( node . location )
1683
- on_xstring_literal ( parts )
1686
+ bounds ( node . location )
1687
+ on_xstring_literal ( heredoc )
1688
+ else
1689
+ bounds ( node . parts . first . location )
1690
+ parts =
1691
+ node . parts . inject ( on_xstring_new ) do |content , part |
1692
+ on_xstring_add ( content , visit_string_content ( part ) )
1693
+ end
1694
+
1695
+ bounds ( node . location )
1696
+ on_xstring_literal ( parts )
1697
+ end
1684
1698
end
1685
1699
1686
1700
# Visit an individual part of a string-like node.
@@ -1846,12 +1860,12 @@ def visit_local_variable_target_node(node)
1846
1860
# ^^^^^
1847
1861
def visit_match_last_line_node ( node )
1848
1862
bounds ( node . content_loc )
1849
- content = on_tstring_content ( node . unescaped )
1863
+ tstring_content = on_tstring_content ( node . content )
1850
1864
1851
1865
bounds ( node . closing_loc )
1852
1866
closing = on_regexp_end ( node . closing )
1853
1867
1854
- on_regexp_literal ( on_regexp_add ( on_regexp_new , content ) , closing )
1868
+ on_regexp_literal ( on_regexp_add ( on_regexp_new , tstring_content ) , closing )
1855
1869
end
1856
1870
1857
1871
# foo in bar
@@ -2140,12 +2154,12 @@ def visit_redo_node(node)
2140
2154
# ^^^^^
2141
2155
def visit_regular_expression_node ( node )
2142
2156
bounds ( node . content_loc )
2143
- content = on_tstring_content ( node . unescaped )
2157
+ tstring_content = on_tstring_content ( node . content )
2144
2158
2145
2159
bounds ( node . closing_loc )
2146
2160
closing = on_regexp_end ( node . closing )
2147
2161
2148
- on_regexp_literal ( on_regexp_add ( on_regexp_new , content ) , closing )
2162
+ on_regexp_literal ( on_regexp_add ( on_regexp_new , tstring_content ) , closing )
2149
2163
end
2150
2164
2151
2165
# def foo(bar:); end
@@ -2320,18 +2334,113 @@ def visit_statements_node(node)
2320
2334
# "foo"
2321
2335
# ^^^^^
2322
2336
def visit_string_node ( node )
2323
- if node . opening == "?"
2337
+ if ( content = node . content ) . empty?
2338
+ bounds ( node . location )
2339
+ on_string_literal ( on_string_content )
2340
+ elsif ( opening = node . opening ) == "?"
2324
2341
bounds ( node . location )
2325
2342
on_CHAR ( "?#{ node . content } " )
2326
- elsif node . content . empty?
2343
+ elsif opening . start_with? ( "<<~" )
2344
+ heredoc = visit_string_heredoc_node ( [ node ] )
2345
+
2327
2346
bounds ( node . location )
2328
- on_string_literal ( on_string_content )
2347
+ on_string_literal ( heredoc )
2329
2348
else
2330
2349
bounds ( node . content_loc )
2331
- content = on_tstring_content ( node . content )
2350
+ tstring_content = on_tstring_content ( content )
2332
2351
2333
2352
bounds ( node . location )
2334
- on_string_literal ( on_string_add ( on_string_content , content ) )
2353
+ on_string_literal ( on_string_add ( on_string_content , tstring_content ) )
2354
+ end
2355
+ end
2356
+
2357
+ # Ripper gives back the escaped string content but strips out the common
2358
+ # leading whitespace. Prism gives back the unescaped string content and a
2359
+ # location for the escaped string content. Unfortunately these don't work
2360
+ # well together, so we need to re-derive the common leading whitespace.
2361
+ private def heredoc_common_whitespace ( parts )
2362
+ common_whitespace = nil
2363
+ dedent_next = true
2364
+
2365
+ parts . each do |part |
2366
+ if part . is_a? ( StringNode )
2367
+ if dedent_next
2368
+ common_whitespace = [
2369
+ common_whitespace || Float ::INFINITY ,
2370
+ part . content [ /\A \s */ ] . each_char . inject ( 0 ) do |part_whitespace , char |
2371
+ char == "\t " ? ( ( part_whitespace / 8 + 1 ) * 8 ) : ( part_whitespace + 1 )
2372
+ end
2373
+ ] . min
2374
+ end
2375
+
2376
+ dedent_next = true
2377
+ else
2378
+ dedent_next = false
2379
+ end
2380
+ end
2381
+
2382
+ common_whitespace
2383
+ end
2384
+
2385
+ # Take the content of a string and return the index of the first character
2386
+ # that is not trimmed out by eliminating common whitespace.
2387
+ private def heredoc_trimmed_whitespace ( content , common_whitespace )
2388
+ trimmed_whitespace = 0
2389
+
2390
+ index = 0
2391
+ while index < content . length && content [ index ] . match? ( /\s / ) && trimmed_whitespace < common_whitespace
2392
+ if content [ index ] == "\t "
2393
+ trimmed_whitespace = ( ( trimmed_whitespace / 8 + 1 ) * 8 )
2394
+ break if trimmed_whitespace > common_whitespace
2395
+ else
2396
+ trimmed_whitespace += 1
2397
+ end
2398
+
2399
+ index += 1
2400
+ end
2401
+
2402
+ index
2403
+ end
2404
+
2405
+ # Visit a string that is expressed using a <<~ heredoc.
2406
+ private def visit_string_heredoc_node ( parts )
2407
+ common_whitespace = heredoc_common_whitespace ( parts )
2408
+
2409
+ bounds ( parts . first . location )
2410
+ parts . inject ( on_string_content ) do |string_content , part |
2411
+ on_string_add (
2412
+ string_content ,
2413
+ if part . is_a? ( StringNode )
2414
+ content = part . content
2415
+ trimmed_whitespace = heredoc_trimmed_whitespace ( content , common_whitespace )
2416
+
2417
+ bounds ( part . content_loc . copy ( start_offset : part . content_loc . start_offset + trimmed_whitespace ) )
2418
+ on_tstring_content ( part . content [ trimmed_whitespace ..] )
2419
+ else
2420
+ visit ( part )
2421
+ end
2422
+ )
2423
+ end
2424
+ end
2425
+
2426
+ # Visit an xstring that is expressed using a <<~ heredoc.
2427
+ private def visit_x_string_heredoc_node ( parts )
2428
+ common_whitespace = heredoc_common_whitespace ( parts )
2429
+
2430
+ bounds ( parts . first . location )
2431
+ parts . inject ( on_xstring_new ) do |xstring , part |
2432
+ on_xstring_add (
2433
+ xstring ,
2434
+ if part . is_a? ( StringNode )
2435
+ content = part . content
2436
+ trimmed_whitespace = heredoc_trimmed_whitespace ( content , common_whitespace )
2437
+
2438
+ bounds ( part . content_loc . copy ( start_offset : part . content_loc . start_offset + trimmed_whitespace ) )
2439
+ on_tstring_content ( part . content [ trimmed_whitespace ..] )
2440
+ else
2441
+ visit ( part )
2442
+ end
2443
+ )
2335
2444
end
2336
2445
end
2337
2446
@@ -2514,11 +2623,21 @@ def visit_while_node(node)
2514
2623
# `foo`
2515
2624
# ^^^^^
2516
2625
def visit_x_string_node ( node )
2517
- bounds ( node . content_loc )
2518
- unescaped = on_tstring_content ( node . unescaped )
2626
+ if node . unescaped . empty?
2627
+ bounds ( node . location )
2628
+ on_xstring_literal ( on_xstring_new )
2629
+ elsif node . opening . start_with? ( "<<~" )
2630
+ heredoc = visit_x_string_heredoc_node ( [ node ] )
2519
2631
2520
- bounds ( node . location )
2521
- on_xstring_literal ( on_xstring_add ( on_xstring_new , unescaped ) )
2632
+ bounds ( node . location )
2633
+ on_xstring_literal ( heredoc )
2634
+ else
2635
+ bounds ( node . content_loc )
2636
+ content = on_tstring_content ( node . content )
2637
+
2638
+ bounds ( node . location )
2639
+ on_xstring_literal ( on_xstring_add ( on_xstring_new , content ) )
2640
+ end
2522
2641
end
2523
2642
2524
2643
# yield
0 commit comments