@@ -1272,7 +1272,9 @@ ast::ExpressionPtr Translator::desugarSendOpAssign(pm_node_t *untypedNode) {
12721272template <typename PrismAssignmentNode, ast::UnresolvedIdent::Kind IdentKind>
12731273ast::ExpressionPtr Translator::desugarAssignment (pm_node_t *untypedNode) {
12741274 auto node = down_cast<PrismAssignmentNode>(untypedNode);
1275- auto location = translateLoc (untypedNode->location );
1275+ // For heredocs, Prism's location only includes the opening tag (e.g., "x = <<~EOF").
1276+ // Extend to include the full heredoc body by using endLoc() on the value.
1277+ auto location = translateLoc (startLoc (untypedNode), endLoc (node->value ));
12761278 auto rhs = desugar (node->value );
12771279
12781280 ast::ExpressionPtr lhs;
@@ -1374,6 +1376,7 @@ ast::ExpressionPtr Translator::desugarMethodCall(ast::ExpressionPtr receiver, co
13741376
13751377 // For heredoc xstrings in arguments, Prism's call node location only includes the opening.
13761378 // Extend the location to include the full heredoc using endLoc(), which handles heredocs.
1379+ // NOTE: Only xstrings extend the call location; regular string heredocs do not.
13771380 if (!prismArgs.empty ()) {
13781381 auto lastArg = prismArgs.back ();
13791382 if (PM_NODE_TYPE_P (lastArg, PM_X_STRING_NODE) || PM_NODE_TYPE_P (lastArg, PM_INTERPOLATED_X_STRING_NODE)) {
@@ -2970,8 +2973,11 @@ ast::ExpressionPtr Translator::desugar(pm_node_t *node) {
29702973 case PM_INTERPOLATED_STRING_NODE: { // An interpolated string like `"foo #{bar} baz"`
29712974 auto interpolatedStringNode = down_cast<pm_interpolated_string_node>(node);
29722975
2976+ // For heredocs, endLoc() extends to the closing delimiter.
2977+ auto strLoc = translateLoc (startLoc (node), endLoc (node));
2978+
29732979 // Desugar `"a #{b} c"` to `::Magic.<string-interpolate>("a ", b, " c")`
2974- return desugarDString (location , interpolatedStringNode->parts );
2980+ return desugarDString (strLoc , interpolatedStringNode->parts );
29752981 }
29762982 case PM_INTERPOLATED_SYMBOL_NODE: { // A symbol like `:"a #{b} c"`
29772983 auto interpolatedSymbolNode = down_cast<pm_interpolated_symbol_node>(node);
@@ -3402,10 +3408,13 @@ ast::ExpressionPtr Translator::desugar(pm_node_t *node) {
34023408 case PM_STRING_NODE: { // A string literal, e.g. `"foo"`
34033409 auto strNode = down_cast<pm_string_node>(node);
34043410
3411+ // For heredocs, endLoc() extends to the closing delimiter.
3412+ auto strLoc = translateLoc (startLoc (node), endLoc (node));
3413+
34053414 auto unescaped = &strNode->unescaped ;
34063415 auto content = ctx.state .enterNameUTF8 (parser.extractString (unescaped));
34073416
3408- return MK::String (location , content);
3417+ return MK::String (strLoc , content);
34093418 }
34103419 case PM_SUPER_NODE: { // A `super` call with explicit args, like `super()`, `super(a, b)`
34113420 // If there's no arguments (except a literal block argument), then it's a `PM_FORWARDING_SUPER_NODE`.
@@ -3617,6 +3626,7 @@ const uint8_t *endLoc(pm_node_t *anyNode) {
36173626 case PM_CALL_NODE: {
36183627 // For call nodes with heredoc xstring arguments, Prism's location only includes the opening.
36193628 // Extend to include the full heredoc by checking the last argument.
3629+ // NOTE: Only xstrings extend the call location; regular string heredocs do not.
36203630 auto *node = down_cast<pm_call_node>(anyNode);
36213631 if (node->arguments && node->arguments ->arguments .size > 0 ) {
36223632 auto *lastArg = node->arguments ->arguments .nodes [node->arguments ->arguments .size - 1 ];
@@ -3627,6 +3637,23 @@ const uint8_t *endLoc(pm_node_t *anyNode) {
36273637 }
36283638 return anyNode->location .end ;
36293639 }
3640+ case PM_STRING_NODE: {
3641+ // For heredoc strings, Prism's base.location only includes the opening delimiter.
3642+ // Use closing_loc to get the full heredoc span, excluding the trailing newline.
3643+ auto *node = down_cast<pm_string_node>(anyNode);
3644+ if (auto end = closingLocEnd (node->closing_loc )) {
3645+ return end;
3646+ }
3647+ return anyNode->location .end ;
3648+ }
3649+ case PM_INTERPOLATED_STRING_NODE: {
3650+ // Same as PM_STRING_NODE - extend to closing delimiter for heredocs.
3651+ auto *node = down_cast<pm_interpolated_string_node>(anyNode);
3652+ if (auto end = closingLocEnd (node->closing_loc )) {
3653+ return end;
3654+ }
3655+ return anyNode->location .end ;
3656+ }
36303657 case PM_X_STRING_NODE: {
36313658 // For heredoc xstrings, Prism's base.location only includes the opening delimiter.
36323659 // Use closing_loc to get the full heredoc span, excluding the trailing newline.
@@ -3644,6 +3671,31 @@ const uint8_t *endLoc(pm_node_t *anyNode) {
36443671 }
36453672 return anyNode->location .end ;
36463673 }
3674+ case PM_LOCAL_VARIABLE_WRITE_NODE: {
3675+ // For assignments with heredoc values, extend to the heredoc closing delimiter.
3676+ auto *node = down_cast<pm_local_variable_write_node>(anyNode);
3677+ return endLoc (node->value );
3678+ }
3679+ case PM_INSTANCE_VARIABLE_WRITE_NODE: {
3680+ auto *node = down_cast<pm_instance_variable_write_node>(anyNode);
3681+ return endLoc (node->value );
3682+ }
3683+ case PM_CLASS_VARIABLE_WRITE_NODE: {
3684+ auto *node = down_cast<pm_class_variable_write_node>(anyNode);
3685+ return endLoc (node->value );
3686+ }
3687+ case PM_GLOBAL_VARIABLE_WRITE_NODE: {
3688+ auto *node = down_cast<pm_global_variable_write_node>(anyNode);
3689+ return endLoc (node->value );
3690+ }
3691+ case PM_CONSTANT_WRITE_NODE: {
3692+ auto *node = down_cast<pm_constant_write_node>(anyNode);
3693+ return endLoc (node->value );
3694+ }
3695+ case PM_CONSTANT_PATH_WRITE_NODE: {
3696+ auto *node = down_cast<pm_constant_path_write_node>(anyNode);
3697+ return endLoc (node->value );
3698+ }
36473699 default : {
36483700 return anyNode->location .end ;
36493701 }
0 commit comments