const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
fn b64_val(c: u8) -> i64 {
B64.iter().position(|&b| b == c).expect("valid base64 digit") as i64
}
fn vlq_decode_all(seg: &str) -> Vec<i64> {
let bytes = seg.as_bytes();
let mut out = Vec::new();
let mut i = 0;
while i < bytes.len() {
let mut result: i64 = 0;
let mut shift = 0;
loop {
let d = b64_val(bytes[i]);
i += 1;
result |= (d & 0x1f) << shift;
shift += 5;
if d & 0x20 == 0 {
break;
}
}
let mag = result >> 1;
out.push(if result & 1 == 1 { -mag } else { mag });
}
out
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct Mapping {
gen_line: u32,
gen_col: u32,
src_id: u32,
src_line: u32,
src_col: u32,
}
fn decode_mappings(mappings: &str) -> Vec<Mapping> {
let mut out = Vec::new();
let (mut s_id, mut s_line, mut s_col) = (0i64, 0i64, 0i64);
for (gen_line, line) in mappings.split(';').enumerate() {
let mut gen_col = 0i64;
for seg in line.split(',') {
if seg.is_empty() {
continue;
}
let f = vlq_decode_all(seg);
gen_col += f[0];
if f.len() >= 4 {
s_id += f[1];
s_line += f[2];
s_col += f[3];
out.push(Mapping {
gen_line: gen_line as u32,
gen_col: gen_col as u32,
src_id: s_id as u32,
src_line: s_line as u32,
src_col: s_col as u32,
});
}
}
}
out
}
fn at(text: &str, line: u32, col: u32) -> Option<&str> {
text.lines()
.nth(line as usize)
.and_then(|l| l.get(col as usize..))
}
fn leading_ident(s: &str) -> String {
s.chars()
.take_while(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_')
.collect()
}
fn check_declaration_invariants(sources: &[(&str, &str)], output_css: &str, mappings: &str) -> usize {
let maps = decode_mappings(mappings);
let mut strict = 0;
for m in &maps {
let src_text = sources
.get(m.src_id as usize)
.unwrap_or_else(|| panic!("mapping references missing source id {}", m.src_id))
.1;
let gen = at(output_css, m.gen_line, m.gen_col)
.unwrap_or_else(|| panic!("generated pos {:?} out of bounds", m));
let src =
at(src_text, m.src_line, m.src_col).unwrap_or_else(|| panic!("source pos {:?} out of bounds", m));
let gen_id = leading_ident(gen);
if gen_id.is_empty() {
continue; }
assert_eq!(
gen_id,
leading_ident(src),
"mapping {m:?}: generated {gen_id:?} != source {:?}",
leading_ident(src)
);
strict += 1;
}
strict
}
const DART_INPUT: &str = ".a {\n color: red;\n .b { width: 10px; }\n}\n";
const DART_OUTPUT_CSS: &str = ".a {\n color: red;\n}\n.a .b {\n width: 10px;\n}\n";
const DART_MAPPINGS: &str = "AAAA;EACE;;AACA;EAAK";
#[test]
fn decoder_matches_known_dart_map() {
let got = decode_mappings(DART_MAPPINGS);
let expect = vec![
Mapping {
gen_line: 0,
gen_col: 0,
src_id: 0,
src_line: 0,
src_col: 0,
}, Mapping {
gen_line: 1,
gen_col: 2,
src_id: 0,
src_line: 1,
src_col: 2,
}, Mapping {
gen_line: 3,
gen_col: 0,
src_id: 0,
src_line: 2,
src_col: 2,
}, Mapping {
gen_line: 4,
gen_col: 2,
src_id: 0,
src_line: 2,
src_col: 7,
}, ];
assert_eq!(got, expect);
}
#[test]
fn invariant_checker_accepts_known_good_dart_map() {
let strict = check_declaration_invariants(&[("sm_in.scss", DART_INPUT)], DART_OUTPUT_CSS, DART_MAPPINGS);
assert_eq!(
strict, 2,
"expected to strictly verify the 2 declaration-name mappings"
);
}
#[test]
fn invariant_checker_rejects_a_corrupted_map() {
let bad = "AAAA;EACA;;AACA;EAAK"; let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {})); let result = std::panic::catch_unwind(|| {
check_declaration_invariants(&[("sm_in.scss", DART_INPUT)], DART_OUTPUT_CSS, bad)
});
std::panic::set_hook(prev);
assert!(
result.is_err(),
"checker must reject a map whose source column is wrong"
);
}
use sasso::{
compile, compile_with_source_map, CanonicalUrl, CanonicalizeContext, Importer, ImporterError,
ImporterResult, Options, OutputStyle, Syntax,
};
fn sasso_map(src: &str, options: &Options<'_>) -> (String, Vec<Mapping>, String) {
let r = compile_with_source_map(src, options).expect("compile_with_source_map failed");
let json = r.source_map.to_json();
let mappings = r.source_map.mappings.clone();
(r.css, decode_mappings(&mappings), json)
}
#[test]
fn sasso_css_matches_plain_compile() {
for src in [
DART_INPUT,
"a { color: red; b { x: 1px; } }\n",
"@media screen { a { color: red; } }\n",
"/* hi */\na { color: red; }\n",
] {
let plain = compile(src, &Options::default()).unwrap();
let r = compile_with_source_map(src, &Options::default()).unwrap();
assert_eq!(plain, r.css, "css mismatch for {src:?}");
}
}
#[test]
fn sasso_nested_rule_map_is_valid() {
let (css, _maps, json) = sasso_map(DART_INPUT, &Options::default().with_url("sm_in.scss"));
assert_eq!(css, DART_OUTPUT_CSS, "sasso css must match the dart fixture css");
let strict = check_declaration_invariants(&[("sm_in.scss", DART_INPUT)], &css, &sasso_mappings(&json));
assert!(
strict >= 2,
"expected to strictly verify >=2 decl names, got {strict}"
);
assert!(json.contains("\"version\":3"));
assert!(json.contains("\"sources\":[\"sm_in.scss\"]"));
assert!(json.contains("\"file\":\"sm_in.scss\""));
}
#[test]
fn sasso_multiline_decl_block_map_is_valid() {
let src = "\
.card {
color: red;
background: blue;
border: 1px solid green;
}
";
let (css, _m, json) = sasso_map(src, &Options::default().with_url("in.scss"));
let strict = check_declaration_invariants(&[("in.scss", src)], &css, &sasso_mappings(&json));
assert!(strict >= 3, "expected >=3 strict decl mappings, got {strict}");
}
#[test]
fn sasso_media_block_map_is_valid() {
let src = "\
@media screen {
.a {
color: red;
}
}
";
let (css, maps, json) = sasso_map(src, &Options::default().with_url("in.scss"));
let strict = check_declaration_invariants(&[("in.scss", src)], &css, &sasso_mappings(&json));
assert!(strict >= 1, "expected the `color` decl mapped, got {strict}");
assert!(
maps.iter().any(|m| m.src_line == 0 && m.src_col == 0),
"expected a mapping at source 0:0 for the @media keyword"
);
}
#[test]
fn sasso_comment_map_is_valid() {
let src = "/* a loud comment */\n.a {\n color: red;\n}\n";
let (css, maps, json) = sasso_map(src, &Options::default().with_url("in.scss"));
assert!(
maps.iter().any(|m| m.src_line == 0 && m.src_col == 0),
"expected the comment `/*` mapped to source 0:0"
);
let strict = check_declaration_invariants(&[("in.scss", src)], &css, &sasso_mappings(&json));
assert!(strict >= 1);
}
#[test]
fn sasso_compressed_map_is_valid() {
let src = ".a {\n color: red;\n width: 10px;\n}\n";
let opts = Options::default()
.with_style(OutputStyle::Compressed)
.with_url("in.scss");
let (css, maps, _json) = sasso_map(src, &opts);
assert_eq!(css, ".a{color:red;width:10px}", "compressed css");
assert_eq!(
maps.iter().filter(|m| m.src_col == 2).count(),
2,
"both compressed decl names should map to source col 2: {maps:?}"
);
assert!(maps
.iter()
.any(|m| m.gen_line == 0 && m.src_line == 0 && m.src_col == 0));
}
#[test]
fn sasso_compressed_skips_consecutive_same_source_line() {
let opts = Options::default()
.with_style(OutputStyle::Compressed)
.with_url("in.scss");
let (_c, _m, json) = sasso_map(".a { color: red; width: 1px; }\n", &opts);
assert_eq!(sasso_mappings(&json), "AAAA");
let (_c, _m, json) = sasso_map(
".a {\n color: red;\n .b { width: 1px; height: 2px; }\n}\n",
&opts,
);
assert_eq!(sasso_mappings(&json), "AAAA,GACE,UACA");
}
#[test]
fn sasso_compressed_bubbled_media_matches_dart() {
let src = ".a {\n color: red;\n @media screen { width: 1px; }\n height: 2px;\n}\n";
let (css, _m, json) = sasso_map(
src,
&Options::default()
.with_style(OutputStyle::Compressed)
.with_url("in.scss"),
);
assert_eq!(css, ".a{color:red}@media screen{.a{width:1px}}.a{height:2px}");
assert_eq!(sasso_mappings(&json), "AAAA,GACE,UACA,cAFF,GAEkB,WAFlB,GAGE");
let (_c, _m, json) = sasso_map(src, &Options::default().with_url("in.scss"));
assert_eq!(sasso_mappings(&json), "AAAA;EACE;;AACA;EAFF;IAEkB;;;AAFlB;EAGE");
}
#[test]
fn sasso_supports_header_maps_to_keyword() {
let src = ".a { @supports (display: grid) { d: grid; } }\n";
let (_c, _m, json) = sasso_map(src, &Options::default().with_url("in.scss"));
assert_eq!(sasso_mappings(&json), "AAAK;EAAL;IAAiC");
let (_c, _m, json) = sasso_map(
src,
&Options::default()
.with_style(OutputStyle::Compressed)
.with_url("in.scss"),
);
assert_eq!(sasso_mappings(&json), "AAAK");
}
#[test]
fn sasso_sources_content_round_trips() {
let src = ".a { color: red; }\n";
let opts = Options::default()
.with_url("in.scss")
.with_source_map_include_sources(true);
let r = compile_with_source_map(src, &opts).unwrap();
assert_eq!(r.source_map.sources, vec!["in.scss".to_string()]);
assert_eq!(
r.source_map.sources_content.as_deref(),
Some(&[src.to_string()][..]),
"sourcesContent must hold the entry source"
);
let r2 = compile_with_source_map(src, &Options::default().with_url("in.scss")).unwrap();
assert!(r2.source_map.sources_content.is_none());
assert!(!r2.source_map.to_json().contains("sourcesContent"));
}
#[test]
fn sasso_stdin_file_name() {
let r = compile_with_source_map(".a { x: 1px; }\n", &Options::default()).unwrap();
assert_eq!(r.source_map.file.as_deref(), Some("stdin"));
assert_eq!(r.source_map.sources, vec!["stdin".to_string()]);
}
fn sasso_mappings(json: &str) -> String {
let key = "\"mappings\":\"";
let start = json.find(key).expect("mappings field") + key.len();
let rest = &json[start..];
let end = rest.find('"').expect("mappings close quote");
rest[..end].to_string()
}
fn dart_enabled() -> bool {
std::env::var("SASSO_PARITY").map(|v| v != "0").unwrap_or(false)
}
fn dart_map(src: &str) -> Option<(Vec<Mapping>, Vec<String>)> {
use std::io::Write as _;
let bin = std::env::var("SASS_BIN").ok()?;
let dir = std::env::temp_dir();
let stem = format!("sasso_dartdiff_{}", std::process::id());
let in_path = dir.join(format!("{stem}.scss"));
let out_path = dir.join(format!("{stem}.css"));
let map_path = dir.join(format!("{stem}.css.map"));
std::fs::File::create(&in_path)
.ok()?
.write_all(src.as_bytes())
.ok()?;
let status = std::process::Command::new(&bin)
.arg("--source-map")
.arg(&in_path)
.arg(&out_path)
.status()
.ok()?;
if !status.success() {
return None;
}
let map_json = std::fs::read_to_string(&map_path).ok()?;
let mappings = sasso_mappings(&map_json);
let sources = {
let key = "\"sources\":[";
let s = map_json.find(key)? + key.len();
let rest = &map_json[s..];
let e = rest.find(']')?;
rest[..e]
.split(',')
.filter(|t| !t.is_empty())
.map(|t| t.trim().trim_matches('"').to_string())
.collect()
};
let _ = std::fs::remove_file(&in_path);
let _ = std::fs::remove_file(&out_path);
let _ = std::fs::remove_file(&map_path);
Some((decode_mappings(&mappings), sources))
}
fn source_positions(maps: &[Mapping]) -> std::collections::BTreeSet<(u32, u32, u32)> {
maps.iter().map(|m| (m.src_id, m.src_line, m.src_col)).collect()
}
#[test]
fn dart_differential_source_positions_agree() {
if !dart_enabled() {
return;
}
let cases = [
".a {\n color: red;\n .b { width: 10px; }\n}\n",
"a {\n color: red;\n}\nb {\n width: 10px;\n}\n",
"@media screen {\n .a {\n color: red;\n }\n}\n",
".card {\n color: red;\n background: blue;\n}\n",
];
for src in cases {
let Some((dart, _dsrc)) = dart_map(src) else {
eprintln!("skipping dart-diff: dart-sass unavailable");
return;
};
let opts = Options::default().with_url("in.scss");
let r = compile_with_source_map(src, &opts).unwrap();
let ours = decode_mappings(&r.source_map.mappings);
let dart_pos = source_positions(&dart);
let our_pos = source_positions(&ours);
for p in &dart_pos {
assert!(
our_pos.contains(p),
"dart maps source {p:?} that sasso does not.\nsrc:\n{src}\nours: {our_pos:?}\ndart: {dart_pos:?}"
);
}
}
}
#[test]
fn sasso_charset_prefix_and_utf16_columns() {
let src = ".a {\n content: \"héllo 𝕏\";\n color: red;\n}\n";
let r = compile_with_source_map(src, &Options::default().with_url("u_in.scss")).unwrap();
assert!(
r.css.starts_with("@charset \"UTF-8\";\n"),
"expected a charset prefix"
);
assert_eq!(
r.source_map.mappings, ";AAAA;EACE;EACA",
"charset-shifted + UTF-16 map"
);
let maps = decode_mappings(&r.source_map.mappings);
assert!(maps
.iter()
.any(|m| m.gen_line == 1 && m.src_line == 0 && m.src_col == 0));
}
struct SourceMapUrlImporter;
impl Importer for SourceMapUrlImporter {
fn canonicalize(
&self,
url: &str,
_ctx: &CanonicalizeContext<'_>,
) -> Result<Option<CanonicalUrl>, ImporterError> {
Ok((url == "mod").then(|| CanonicalUrl::new("mod")))
}
fn load(&self, canonical: &CanonicalUrl) -> Result<Option<ImporterResult>, ImporterError> {
if canonical.as_str() == "mod" {
Ok(Some(ImporterResult {
contents: ".m { x: 1px; }".to_string(),
syntax: Syntax::Scss,
source_map_url: Some("custom://virtual/mod.scss".to_string()),
}))
} else {
Ok(None)
}
}
}
#[test]
fn importer_source_map_url_override_appears_in_sources() {
let imp = SourceMapUrlImporter;
let opts = Options::default().with_importer(&imp).with_url("entry.scss");
let r = compile_with_source_map("@use \"mod\";\n.a { y: 2px; }\n", &opts).unwrap();
assert!(
r.source_map
.sources
.contains(&"custom://virtual/mod.scss".to_string()),
"sources should carry the importer's source_map_url override, got {:?}",
r.source_map.sources
);
assert!(
r.source_map.sources.contains(&"entry.scss".to_string()),
"the entry file keeps its own URL, got {:?}",
r.source_map.sources
);
}