diff --git a/crates/compiler/src/builtin/functions/string.rs b/crates/compiler/src/builtin/functions/string.rs index 36064154..63641dc3 100644 --- a/crates/compiler/src/builtin/functions/string.rs +++ b/crates/compiler/src/builtin/functions/string.rs @@ -115,6 +115,53 @@ pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass } } +/// https://sass-lang.com/documentation/modules/string/#split +/// +/// Returns a bracketed, comma-separated list of substrings of $string +/// that are separated by $separator. The $separators aren’t included +/// in these substrings. +/// +/// If $limit is a number 1 or higher, this splits on at most that many +/// $separators (and so returns at most $limit + 1 strings). The last +/// substring contains the rest of the string, including any remaining +/// $separators. +pub(crate) fn str_split(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { + args.max_args(3)?; + + let s1 = args + .get_err(0, "string")? + .assert_string_with_name("string", args.span())? + .0; + + let separator = args + .get_err(1, "separator")? + .assert_string_with_name("separator", args.span())? + .0; + + let limit = args.default_arg(2, "limit", Value::Null); + + let vec = if matches!(limit, Value::Null) { + s1.split(&separator) + .map(|s| Value::String(s.to_string(), QuoteKind::Quoted)) + .collect() + } else { + let limit = limit.assert_number_with_name("limit", args.span())?; + let limit_int = limit.assert_int_with_name("limit", args.span())?; + if limit_int < 1 { + return Err(( + format!("$limit: Must be greater than 1, was {}.", limit_int), + args.span(), + ) + .into()); + } + // note: `1 + limit_int` is required to match dart-sass + s1.splitn(1 + limit_int as usize, &separator) + .map(|s| Value::String(s.to_string(), QuoteKind::Quoted)) + .collect() + }; + Ok(Value::List(vec, ListSeparator::Comma, Brackets::Bracketed)) +} + pub(crate) fn str_index(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(2)?; let s1 = args diff --git a/crates/compiler/src/builtin/modules/string.rs b/crates/compiler/src/builtin/modules/string.rs index bd1b2cf2..8d83566e 100644 --- a/crates/compiler/src/builtin/modules/string.rs +++ b/crates/compiler/src/builtin/modules/string.rs @@ -1,7 +1,8 @@ use crate::builtin::{ modules::Module, string::{ - quote, str_index, str_insert, str_length, str_slice, to_lower_case, to_upper_case, unquote, + quote, str_index, str_insert, str_length, str_slice, str_split, to_lower_case, + to_upper_case, unquote, }, }; @@ -14,6 +15,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("insert", str_insert); f.insert_builtin("length", str_length); f.insert_builtin("slice", str_slice); + f.insert_builtin("split", str_split); f.insert_builtin("to-lower-case", to_lower_case); f.insert_builtin("to-upper-case", to_upper_case); #[cfg(feature = "random")] diff --git a/crates/lib/tests/strings.rs b/crates/lib/tests/strings.rs index b4f1862e..ff4252a2 100644 --- a/crates/lib/tests/strings.rs +++ b/crates/lib/tests/strings.rs @@ -308,3 +308,30 @@ test!( ", "a {\n color: \"aaa\";\n}\n" ); +test!( + str_split_abc_space, + "@use 'sass:string'; + foo { + bar: string.split('a b c', ' '); + } + ", + "foo {\n bar: [\"a\", \"b\", \"c\"];\n}\n" +); +test!( + str_split_abc_space_1, + "@use 'sass:string'; + foo { + bar: string.split('a b c', ' ', 1); + } + ", + "foo {\n bar: [\"a\", \"b c\"];\n}\n" +); +test!( + str_split_rgb_comma, + "@use 'sass:string'; + foo { + bar: string.split('red,green,blue', ','); + } + ", + "foo {\n bar: [\"red\", \"green\", \"blue\"];\n}\n" +);