use std::path::Path;
use std::sync::LazyLock;
static SCSS_NAMESPACED_CALL_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
crate::static_regex(r"[a-zA-Z_][a-zA-Z0-9_-]*(\.)[a-zA-Z_][a-zA-Z0-9_-]*\s*\(")
});
static SCSS_NAMESPACED_INCLUDE_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
crate::static_regex(r"@include\s+[a-zA-Z_][a-zA-Z0-9_-]*(\.)[a-zA-Z_][a-zA-Z0-9_-]*")
});
static SASS_NAMESPACED_INCLUDE_SHORTHAND_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
crate::static_regex(r"(?m)^[ \t]*\+[ \t]*[a-zA-Z_][a-zA-Z0-9_-]*(\.)[a-zA-Z_][a-zA-Z0-9_-]*")
});
static VIEW_TRANSITION_ARGUMENT_RE: LazyLock<regex::Regex> =
LazyLock::new(|| crate::static_regex(r"::view-transition-[a-z-]+\(([^)]*)\)"));
pub fn mask_with_whitespace(src: &str, re: ®ex::Regex) -> String {
let mut out = String::with_capacity(src.len());
let mut cursor = 0;
for m in re.find_iter(src) {
out.push_str(&src[cursor..m.start()]);
out.extend(std::iter::repeat_n(' ', m.len()));
cursor = m.end();
}
out.push_str(&src[cursor..]);
out
}
fn mask_capture_with_whitespace(src: &str, re: ®ex::Regex) -> String {
let mut out = String::with_capacity(src.len());
let mut cursor = 0;
for cap in re.captures_iter(src) {
let Some(group) = cap.get(1) else {
continue;
};
out.push_str(&src[cursor..group.start()]);
out.extend(std::iter::repeat_n(' ', group.len()));
cursor = group.end();
}
out.push_str(&src[cursor..]);
out
}
pub fn mask_sass_module_members(src: &str) -> String {
let masked = mask_capture_with_whitespace(src, &SCSS_NAMESPACED_CALL_RE);
mask_capture_with_whitespace(&masked, &SCSS_NAMESPACED_INCLUDE_RE)
}
pub fn mask_sass_shorthand(src: String, path: &Path) -> String {
if path.extension().is_some_and(|ext| ext == "sass") {
mask_capture_with_whitespace(&src, &SASS_NAMESPACED_INCLUDE_SHORTHAND_RE)
} else {
src
}
}
pub fn mask_view_transition_arguments(src: &str) -> String {
mask_capture_with_whitespace(src, &VIEW_TRANSITION_ARGUMENT_RE)
}
#[cfg(test)]
mod tests {
use super::*;
static COMMENT_RE: LazyLock<regex::Regex> =
LazyLock::new(|| crate::static_regex(r"(?s)/\*.*?\*/"));
#[test]
fn mask_with_whitespace_preserves_byte_length() {
let src = "/* hello */ .foo { }";
let masked = mask_with_whitespace(src, &COMMENT_RE);
assert_eq!(masked.len(), src.len());
assert!(masked.is_char_boundary(src.len()));
}
#[test]
fn mask_with_whitespace_preserves_offsets_around_multibyte() {
let src = "/* \u{2713} */ .foo { }";
let foo_offset = src.find(".foo").expect("`.foo` present");
let masked = mask_with_whitespace(src, &COMMENT_RE);
assert_eq!(masked.len(), src.len());
assert_eq!(masked.find(".foo"), Some(foo_offset));
}
}