Skip to content

Commit 3c5292a

Browse files
authored
Merge pull request #3988 from ruby/track-newlines-in-escapes
Track newlines in character escape sequences
2 parents e2f28a6 + 2e58c52 commit 3c5292a

2 files changed

Lines changed: 30 additions & 2 deletions

File tree

src/prism.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8594,6 +8594,7 @@ escape_write_escape_encoded(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_
85948594
}
85958595

85968596
if (width == 1) {
8597+
if (*parser->current.end == '\n') pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
85978598
escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(*parser->current.end++, flags));
85988599
} else if (width > 1) {
85998600
// Valid multibyte character. Just ignore escape.
@@ -8910,6 +8911,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
89108911
return;
89118912
}
89128913

8914+
if (peeked == '\n') pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
89138915
parser->current.end++;
89148916
escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL));
89158917
return;
@@ -8968,6 +8970,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
89688970
return;
89698971
}
89708972

8973+
if (peeked == '\n') pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
89718974
parser->current.end++;
89728975
escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL));
89738976
return;
@@ -9021,13 +9024,15 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
90219024
return;
90229025
}
90239026

9027+
if (peeked == '\n') pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1);
90249028
parser->current.end++;
90259029
escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_META));
90269030
return;
90279031
}
90289032
}
90299033
case '\r': {
90309034
if (peek_offset(parser, 1) == '\n') {
9035+
pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 2);
90319036
parser->current.end += 2;
90329037
escape_write_byte_encoded(parser, buffer, flags, escape_byte('\n', flags));
90339038
return;

test/prism/newline_offsets_test.rb

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,38 @@ class NewlineOffsetsTest < TestCase
88
define_method(fixture.test_name) { assert_newline_offsets(fixture) }
99
end
1010

11+
def test_escape_control_newline
12+
# Newlines consumed inside escape sequences like \C-, \c, and \M-
13+
# must be tracked in line offsets across all literal types.
14+
%w[\\C- \\c \\M-].each do |escape|
15+
assert_newline_offsets_for("\"#{escape}\n\"", "#{escape} in string")
16+
assert_newline_offsets_for("`#{escape}\n`", "#{escape} in xstring")
17+
assert_newline_offsets_for("/#{escape}\n/", "#{escape} in regexp")
18+
assert_newline_offsets_for("%Q{#{escape}\n}", "#{escape} in %Q")
19+
assert_newline_offsets_for("%W[#{escape}\n]", "#{escape} in %W")
20+
assert_newline_offsets_for("<<~H\n#{escape}\n\nH\n", "#{escape} in heredoc")
21+
assert_newline_offsets_for("?#{escape}\n", "#{escape} in char literal")
22+
end
23+
24+
# Combined meta + control escapes
25+
assert_newline_offsets_for("\"\\M-\\C-\n\"", "\\M-\\C- in string")
26+
assert_newline_offsets_for("\"\\M-\\c\n\"", "\\M-\\c in string")
27+
28+
# \r\n consumed inside escape context
29+
assert_newline_offsets_for("\"\\C-\r\n\"", "\\C- with \\r\\n")
30+
end
31+
1132
private
1233

1334
def assert_newline_offsets(fixture)
14-
source = fixture.read
35+
assert_newline_offsets_for(fixture.read)
36+
end
1537

38+
def assert_newline_offsets_for(source, message = nil)
1639
expected = [0]
1740
source.b.scan("\n") { expected << $~.offset(0)[0] + 1 }
1841

19-
assert_equal expected, Prism.parse(source).source.offsets
42+
assert_equal expected, Prism.parse(source).source.offsets, message
2043
end
2144
end
2245
end

0 commit comments

Comments
 (0)