pub(in crate::container::vue::compat) fn skip_regex_literal(
bytes: &[u8],
mut pos: usize,
len: usize,
line: &mut usize,
last_newline: &mut usize,
) -> Option<usize> {
debug_assert_eq!(bytes.get(pos), Some(&b'/'));
pos += 1;
let mut in_character_class = false;
let mut scanned_line = *line;
let mut scanned_last_newline = *last_newline;
while pos < len
&& let Some(&c) = bytes.get(pos)
{
if matches!(c, b'\n' | b'\r') {
return None;
}
if c == b'\\' {
match bytes.get(pos + 1) {
Some(b'\n') => {
scanned_line += 1;
scanned_last_newline = pos + 1;
pos += 2;
}
Some(b'\r') if bytes.get(pos + 2) == Some(&b'\n') => {
scanned_line += 1;
scanned_last_newline = pos + 2;
pos += 3;
}
_ => pos = (pos + 2).min(len),
}
continue;
}
if in_character_class {
if c == b']' {
in_character_class = false;
}
pos += 1;
continue;
}
match c {
b'[' => {
in_character_class = true;
pos += 1;
}
b'/' => {
pos += 1;
while pos < len
&& bytes
.get(pos)
.is_some_and(|&b| b.is_ascii_alphanumeric() || b == b'_')
{
pos += 1;
}
*line = scanned_line;
*last_newline = scanned_last_newline;
return Some(pos);
}
_ => pos += 1,
}
}
None
}
#[cfg(test)]
mod tests {
use super::skip_regex_literal;
#[test]
fn failed_lookahead_does_not_commit_line_state() {
for bytes in [b"/[a\\\n".as_slice(), b"/[a\\\n\n", b"/[a\\\nb\\\nc"] {
let (mut line, mut last_newline) = (4, 17);
assert_eq!(
skip_regex_literal(bytes, 0, bytes.len(), &mut line, &mut last_newline),
None
);
assert_eq!((line, last_newline), (4, 17));
}
}
#[test]
fn successful_lookahead_commits_line_state_once() {
let bytes = b"/a\\\nb/g";
let (mut line, mut last_newline) = (4, 0);
assert_eq!(
skip_regex_literal(bytes, 0, bytes.len(), &mut line, &mut last_newline),
Some(bytes.len())
);
assert_eq!((line, last_newline), (5, 3));
}
#[test]
fn cr_terminates_regex_and_escaped_crlf_stays_transactional() {
let bytes = b"/a\r</script>";
let (mut line, mut last_newline) = (4, 0);
assert_eq!(
skip_regex_literal(bytes, 0, bytes.len(), &mut line, &mut last_newline),
None
);
assert_eq!((line, last_newline), (4, 0));
let bytes = b"/a\\\r\nb/g";
let (mut line, mut last_newline) = (4, 0);
assert_eq!(
skip_regex_literal(bytes, 0, bytes.len(), &mut line, &mut last_newline),
Some(bytes.len())
);
assert_eq!((line, last_newline), (5, 4));
}
#[test]
fn regex_escapes_classes_and_flags_still_hide_closing_tags() {
let bytes = br"/[</script>\/]/giu";
let (mut line, mut last_newline) = (4, 0);
assert_eq!(
skip_regex_literal(bytes, 0, bytes.len(), &mut line, &mut last_newline),
Some(bytes.len())
);
assert_eq!((line, last_newline), (4, 0));
}
}