improve code coverage, handle more builtin fn edge cases
This commit is contained in:
parent
753c4960ca
commit
65c1a9e833
@ -5,6 +5,14 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
# 0.12.1 (unreleased)
|
||||||
|
|
||||||
|
- improve error message for complex units in calculations
|
||||||
|
- more accurate formatting of named arguments in arglists when passed to `inspect(..)`
|
||||||
|
- support `$whiteness` and `$blackness` as arguments to `scale-color(..)`
|
||||||
|
- more accurate list separator from `join(..)`
|
||||||
|
- resolve unicode edge cases in `str-index(..)`
|
||||||
|
|
||||||
# 0.12.0
|
# 0.12.0
|
||||||
|
|
||||||
- complete rewrite of parsing, evaluation, and serialization steps
|
- complete rewrite of parsing, evaluation, and serialization steps
|
||||||
|
@ -19,7 +19,7 @@ a bug except for in the case of error messages and error spans.
|
|||||||
|
|
||||||
Every commit of `grass` is tested against bootstrap v5.0.2, and every release is tested against the last 2,500 commits of bootstrap's `main` branch.
|
Every commit of `grass` is tested against bootstrap v5.0.2, and every release is tested against the last 2,500 commits of bootstrap's `main` branch.
|
||||||
|
|
||||||
That said, there are a number of known missing features and bugs. The rough edges of `grass` largely include `@forward` and more complex uses of `@uses`. We support basic usage of these rules, but more advanced features such as `@import`ing modules containing `@forward` with prefixes may not behave as expected.
|
That said, there are a number of known missing features and bugs. The rough edges of `grass` largely include `@forward` and more complex uses of `@use`. We support basic usage of these rules, but more advanced features such as `@import`ing modules containing `@forward` with prefixes may not behave as expected.
|
||||||
|
|
||||||
All known missing features and bugs are tracked in [#19](https://github.com/connorskees/grass/issues/19).
|
All known missing features and bugs are tracked in [#19](https://github.com/connorskees/grass/issues/19).
|
||||||
|
|
||||||
@ -71,8 +71,8 @@ Using a modified version of the spec runner that ignores warnings and error span
|
|||||||
|
|
||||||
```
|
```
|
||||||
2022-12-26
|
2022-12-26
|
||||||
PASSING: 6024
|
PASSING: 6077
|
||||||
FAILING: 881
|
FAILING: 828
|
||||||
TOTAL: 6905
|
TOTAL: 6905
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -9,11 +9,8 @@ pub(crate) fn blackness(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass
|
|||||||
.get_err(0, "color")?
|
.get_err(0, "color")?
|
||||||
.assert_color_with_name("color", args.span())?;
|
.assert_color_with_name("color", args.span())?;
|
||||||
|
|
||||||
let blackness =
|
|
||||||
Number(1.0) - (color.red().max(color.green()).max(color.blue()) / Number(255.0));
|
|
||||||
|
|
||||||
Ok(Value::Dimension(SassNumber {
|
Ok(Value::Dimension(SassNumber {
|
||||||
num: (blackness * 100),
|
num: color.blackness() * 100,
|
||||||
unit: Unit::Percent,
|
unit: Unit::Percent,
|
||||||
as_slash: None,
|
as_slash: None,
|
||||||
}))
|
}))
|
||||||
@ -26,10 +23,8 @@ pub(crate) fn whiteness(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass
|
|||||||
.get_err(0, "color")?
|
.get_err(0, "color")?
|
||||||
.assert_color_with_name("color", args.span())?;
|
.assert_color_with_name("color", args.span())?;
|
||||||
|
|
||||||
let whiteness = color.red().min(color.green()).min(color.blue()) / Number(255.0);
|
|
||||||
|
|
||||||
Ok(Value::Dimension(SassNumber {
|
Ok(Value::Dimension(SassNumber {
|
||||||
num: (whiteness * 100),
|
num: color.whiteness() * 100,
|
||||||
unit: Unit::Percent,
|
unit: Unit::Percent,
|
||||||
as_slash: None,
|
as_slash: None,
|
||||||
}))
|
}))
|
||||||
@ -102,9 +97,11 @@ pub(crate) fn hwb(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult
|
|||||||
visitor,
|
visitor,
|
||||||
args.span(),
|
args.span(),
|
||||||
)? {
|
)? {
|
||||||
ParsedChannels::String(s) => {
|
ParsedChannels::String(s) => Err((
|
||||||
Err((format!("Expected numeric channels, got {}", s), args.span()).into())
|
format!("Expected numeric channels, got \"{}\"", s),
|
||||||
}
|
args.span(),
|
||||||
|
)
|
||||||
|
.into()),
|
||||||
ParsedChannels::List(list) => {
|
ParsedChannels::List(list) => {
|
||||||
let args = ArgumentResult {
|
let args = ArgumentResult {
|
||||||
positional: list,
|
positional: list,
|
||||||
|
@ -79,15 +79,15 @@ pub(crate) fn change_color(mut args: ArgumentResult, visitor: &mut Visitor) -> S
|
|||||||
};
|
};
|
||||||
|
|
||||||
opt_hsl!(args, saturation, "saturation", 0, 100);
|
opt_hsl!(args, saturation, "saturation", 0, 100);
|
||||||
opt_hsl!(args, luminance, "lightness", 0, 100);
|
opt_hsl!(args, lightness, "lightness", 0, 100);
|
||||||
|
|
||||||
if hue.is_some() || saturation.is_some() || luminance.is_some() {
|
if hue.is_some() || saturation.is_some() || lightness.is_some() {
|
||||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||||
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
|
let (this_hue, this_saturation, this_lightness, this_alpha) = color.as_hsla();
|
||||||
return Ok(Value::Color(Box::new(Color::from_hsla(
|
return Ok(Value::Color(Box::new(Color::from_hsla(
|
||||||
hue.unwrap_or(this_hue),
|
hue.unwrap_or(this_hue),
|
||||||
saturation.unwrap_or(this_saturation),
|
saturation.unwrap_or(this_saturation),
|
||||||
luminance.unwrap_or(this_luminance),
|
lightness.unwrap_or(this_lightness),
|
||||||
alpha.unwrap_or(this_alpha),
|
alpha.unwrap_or(this_alpha),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
@ -132,15 +132,15 @@ pub(crate) fn adjust_color(mut args: ArgumentResult, visitor: &mut Visitor) -> S
|
|||||||
};
|
};
|
||||||
|
|
||||||
opt_hsl!(args, saturation, "saturation", -100, 100);
|
opt_hsl!(args, saturation, "saturation", -100, 100);
|
||||||
opt_hsl!(args, luminance, "lightness", -100, 100);
|
opt_hsl!(args, lightness, "lightness", -100, 100);
|
||||||
|
|
||||||
if hue.is_some() || saturation.is_some() || luminance.is_some() {
|
if hue.is_some() || saturation.is_some() || lightness.is_some() {
|
||||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||||
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
|
let (this_hue, this_saturation, this_lightness, this_alpha) = color.as_hsla();
|
||||||
return Ok(Value::Color(Box::new(Color::from_hsla(
|
return Ok(Value::Color(Box::new(Color::from_hsla(
|
||||||
this_hue + hue.unwrap_or_else(Number::zero),
|
this_hue + hue.unwrap_or_else(Number::zero),
|
||||||
this_saturation + saturation.unwrap_or_else(Number::zero),
|
this_saturation + saturation.unwrap_or_else(Number::zero),
|
||||||
this_luminance + luminance.unwrap_or_else(Number::zero),
|
this_lightness + lightness.unwrap_or_else(Number::zero),
|
||||||
this_alpha + alpha.unwrap_or_else(Number::zero),
|
this_alpha + alpha.unwrap_or_else(Number::zero),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
@ -163,47 +163,39 @@ pub(crate) fn scale_color(mut args: ArgumentResult, visitor: &mut Visitor) -> Sa
|
|||||||
val + (if by.is_positive() { max - val } else { val }) * by
|
val + (if by.is_positive() { max - val } else { val }) * by
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_num(num: Spanned<Value>, name: &str, min: f64, max: f64) -> SassResult<Number> {
|
||||||
|
let span = num.span;
|
||||||
|
let mut num = num.node.assert_number_with_name(name, span)?;
|
||||||
|
|
||||||
|
num.assert_unit(&Unit::Percent, name, span)?;
|
||||||
|
num.assert_bounds(name, min, max, span)?;
|
||||||
|
|
||||||
|
num.num /= Number(100.0);
|
||||||
|
|
||||||
|
Ok(num.num)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_arg(
|
||||||
|
args: &mut ArgumentResult,
|
||||||
|
name: &str,
|
||||||
|
min: f64,
|
||||||
|
max: f64,
|
||||||
|
) -> SassResult<Option<Number>> {
|
||||||
|
Ok(match args.get(usize::MAX, name) {
|
||||||
|
Some(v) => Some(check_num(v, name, min, max)?),
|
||||||
|
None => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let span = args.span();
|
let span = args.span();
|
||||||
let color = args
|
let color = args
|
||||||
.get_err(0, "color")?
|
.get_err(0, "color")?
|
||||||
.assert_color_with_name("color", args.span())?;
|
.assert_color_with_name("color", args.span())?;
|
||||||
|
|
||||||
macro_rules! opt_scale_arg {
|
let red = get_arg(&mut args, "red", -100.0, 100.0)?;
|
||||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => {
|
let green = get_arg(&mut args, "green", -100.0, 100.0)?;
|
||||||
let $name = match $args.default_named_arg($arg, Value::Null) {
|
let blue = get_arg(&mut args, "blue", -100.0, 100.0)?;
|
||||||
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(),
|
let alpha = get_arg(&mut args, "alpha", -100.0, 100.0)?;
|
||||||
Value::Dimension(SassNumber {
|
|
||||||
num: n,
|
|
||||||
unit: Unit::Percent,
|
|
||||||
..
|
|
||||||
}) => Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number(100.0)),
|
|
||||||
v @ Value::Dimension { .. } => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"${}: Expected {} to have unit \"%\".",
|
|
||||||
$arg,
|
|
||||||
v.inspect($args.span())?
|
|
||||||
),
|
|
||||||
$args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
Value::Null => None,
|
|
||||||
v => {
|
|
||||||
return Err((
|
|
||||||
format!("${}: {} is not a number.", $arg, v.inspect($args.span())?),
|
|
||||||
$args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
opt_scale_arg!(args, alpha, "alpha", -100, 100);
|
|
||||||
opt_scale_arg!(args, red, "red", -100, 100);
|
|
||||||
opt_scale_arg!(args, green, "green", -100, 100);
|
|
||||||
opt_scale_arg!(args, blue, "blue", -100, 100);
|
|
||||||
|
|
||||||
if red.is_some() || green.is_some() || blue.is_some() {
|
if red.is_some() || green.is_some() || blue.is_some() {
|
||||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||||
@ -226,12 +218,12 @@ pub(crate) fn scale_color(mut args: ArgumentResult, visitor: &mut Visitor) -> Sa
|
|||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
|
|
||||||
opt_scale_arg!(args, saturation, "saturation", -100, 100);
|
let saturation = get_arg(&mut args, "saturation", -100.0, 100.0)?;
|
||||||
opt_scale_arg!(args, luminance, "lightness", -100, 100);
|
let lightness = get_arg(&mut args, "lightness", -100.0, 100.0)?;
|
||||||
|
|
||||||
if saturation.is_some() || luminance.is_some() {
|
if saturation.is_some() || lightness.is_some() {
|
||||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||||
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
|
let (this_hue, this_saturation, this_lightness, this_alpha) = color.as_hsla();
|
||||||
return Ok(Value::Color(Box::new(Color::from_hsla(
|
return Ok(Value::Color(Box::new(Color::from_hsla(
|
||||||
scale(this_hue, Number::zero(), Number(360.0)),
|
scale(this_hue, Number::zero(), Number(360.0)),
|
||||||
scale(
|
scale(
|
||||||
@ -240,8 +232,8 @@ pub(crate) fn scale_color(mut args: ArgumentResult, visitor: &mut Visitor) -> Sa
|
|||||||
Number::one(),
|
Number::one(),
|
||||||
),
|
),
|
||||||
scale(
|
scale(
|
||||||
this_luminance,
|
this_lightness,
|
||||||
luminance.unwrap_or_else(Number::zero),
|
lightness.unwrap_or_else(Number::zero),
|
||||||
Number::one(),
|
Number::one(),
|
||||||
),
|
),
|
||||||
scale(
|
scale(
|
||||||
@ -252,6 +244,34 @@ pub(crate) fn scale_color(mut args: ArgumentResult, visitor: &mut Visitor) -> Sa
|
|||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let whiteness = get_arg(&mut args, "whiteness", -100.0, 100.0)?;
|
||||||
|
let blackness = get_arg(&mut args, "blackness", -100.0, 100.0)?;
|
||||||
|
|
||||||
|
if whiteness.is_some() || blackness.is_some() {
|
||||||
|
let this_hue = color.hue();
|
||||||
|
let this_whiteness = color.whiteness() * Number(100.0);
|
||||||
|
let this_blackness = color.blackness() * Number(100.0);
|
||||||
|
|
||||||
|
return Ok(Value::Color(Box::new(Color::from_hwb(
|
||||||
|
scale(this_hue, Number::zero(), Number(360.0)),
|
||||||
|
scale(
|
||||||
|
this_whiteness,
|
||||||
|
whiteness.unwrap_or_else(Number::zero),
|
||||||
|
Number(100.0),
|
||||||
|
),
|
||||||
|
scale(
|
||||||
|
this_blackness,
|
||||||
|
blackness.unwrap_or_else(Number::zero),
|
||||||
|
Number(100.0),
|
||||||
|
),
|
||||||
|
scale(
|
||||||
|
color.alpha(),
|
||||||
|
alpha.unwrap_or_else(Number::zero),
|
||||||
|
Number::one(),
|
||||||
|
),
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Value::Color(if let Some(a) = alpha {
|
Ok(Value::Color(if let Some(a) = alpha {
|
||||||
let temp_alpha = color.alpha();
|
let temp_alpha = color.alpha();
|
||||||
Box::new(color.with_alpha(scale(temp_alpha, a, Number::one())))
|
Box::new(color.with_alpha(scale(temp_alpha, a, Number::one())))
|
||||||
|
@ -77,7 +77,7 @@ pub(crate) fn set_nth(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRe
|
|||||||
Brackets::None,
|
Brackets::None,
|
||||||
),
|
),
|
||||||
Value::Map(m) => (m.as_list(), ListSeparator::Comma, Brackets::None),
|
Value::Map(m) => (m.as_list(), ListSeparator::Comma, Brackets::None),
|
||||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
v => (vec![v], ListSeparator::Undecided, Brackets::None),
|
||||||
};
|
};
|
||||||
let (n, unit) = match args.get_err(1, "n")? {
|
let (n, unit) = match args.get_err(1, "n")? {
|
||||||
Value::Dimension(SassNumber {
|
Value::Dimension(SassNumber {
|
||||||
@ -133,7 +133,7 @@ pub(crate) fn append(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRes
|
|||||||
args.max_args(3)?;
|
args.max_args(3)?;
|
||||||
let (mut list, sep, brackets) = match args.get_err(0, "list")? {
|
let (mut list, sep, brackets) = match args.get_err(0, "list")? {
|
||||||
Value::List(v, sep, b) => (v, sep, b),
|
Value::List(v, sep, b) => (v, sep, b),
|
||||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
v => (vec![v], ListSeparator::Undecided, Brackets::None),
|
||||||
};
|
};
|
||||||
let val = args.get_err(1, "val")?;
|
let val = args.get_err(1, "val")?;
|
||||||
let sep = match args.default_arg(
|
let sep = match args.default_arg(
|
||||||
@ -142,7 +142,13 @@ pub(crate) fn append(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRes
|
|||||||
Value::String("auto".to_owned(), QuoteKind::None),
|
Value::String("auto".to_owned(), QuoteKind::None),
|
||||||
) {
|
) {
|
||||||
Value::String(s, ..) => match s.as_str() {
|
Value::String(s, ..) => match s.as_str() {
|
||||||
"auto" => sep,
|
"auto" => {
|
||||||
|
if sep == ListSeparator::Undecided {
|
||||||
|
ListSeparator::Space
|
||||||
|
} else {
|
||||||
|
sep
|
||||||
|
}
|
||||||
|
}
|
||||||
"comma" => ListSeparator::Comma,
|
"comma" => ListSeparator::Comma,
|
||||||
"space" => ListSeparator::Space,
|
"space" => ListSeparator::Space,
|
||||||
"slash" => ListSeparator::Slash,
|
"slash" => ListSeparator::Slash,
|
||||||
@ -173,12 +179,12 @@ pub(crate) fn join(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResul
|
|||||||
let (mut list1, sep1, brackets) = match args.get_err(0, "list1")? {
|
let (mut list1, sep1, brackets) = match args.get_err(0, "list1")? {
|
||||||
Value::List(v, sep, brackets) => (v, sep, brackets),
|
Value::List(v, sep, brackets) => (v, sep, brackets),
|
||||||
Value::Map(m) => (m.as_list(), ListSeparator::Comma, Brackets::None),
|
Value::Map(m) => (m.as_list(), ListSeparator::Comma, Brackets::None),
|
||||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
v => (vec![v], ListSeparator::Undecided, Brackets::None),
|
||||||
};
|
};
|
||||||
let (list2, sep2) = match args.get_err(1, "list2")? {
|
let (list2, sep2) = match args.get_err(1, "list2")? {
|
||||||
Value::List(v, sep, ..) => (v, sep),
|
Value::List(v, sep, ..) => (v, sep),
|
||||||
Value::Map(m) => (m.as_list(), ListSeparator::Comma),
|
Value::Map(m) => (m.as_list(), ListSeparator::Comma),
|
||||||
v => (vec![v], ListSeparator::Space),
|
v => (vec![v], ListSeparator::Undecided),
|
||||||
};
|
};
|
||||||
let sep = match args.default_arg(
|
let sep = match args.default_arg(
|
||||||
2,
|
2,
|
||||||
@ -187,10 +193,12 @@ pub(crate) fn join(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResul
|
|||||||
) {
|
) {
|
||||||
Value::String(s, ..) => match s.as_str() {
|
Value::String(s, ..) => match s.as_str() {
|
||||||
"auto" => {
|
"auto" => {
|
||||||
if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) {
|
if sep1 != ListSeparator::Undecided {
|
||||||
|
sep1
|
||||||
|
} else if sep2 != ListSeparator::Undecided {
|
||||||
sep2
|
sep2
|
||||||
} else {
|
} else {
|
||||||
sep1
|
ListSeparator::Space
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"comma" => ListSeparator::Comma,
|
"comma" => ListSeparator::Comma,
|
||||||
|
@ -75,16 +75,9 @@ pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass
|
|||||||
|
|
||||||
let span = args.span();
|
let span = args.span();
|
||||||
|
|
||||||
let (string, quotes) = match args.get_err(0, "string")? {
|
let (string, quotes) = args
|
||||||
Value::String(s, q) => (s, q),
|
.get_err(0, "string")?
|
||||||
v => {
|
.assert_string_with_name("string", args.span())?;
|
||||||
return Err((
|
|
||||||
format!("$string: {} is not a string.", v.inspect(args.span())?),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let str_len = string.chars().count();
|
let str_len = string.chars().count();
|
||||||
|
|
||||||
@ -141,63 +134,40 @@ pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass
|
|||||||
|
|
||||||
pub(crate) fn str_index(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
|
pub(crate) fn str_index(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
|
||||||
args.max_args(2)?;
|
args.max_args(2)?;
|
||||||
let s1 = match args.get_err(0, "string")? {
|
let s1 = args
|
||||||
Value::String(i, _) => i,
|
.get_err(0, "string")?
|
||||||
v => {
|
.assert_string_with_name("string", args.span())?
|
||||||
return Err((
|
.0;
|
||||||
format!("$string: {} is not a string.", v.inspect(args.span())?),
|
|
||||||
args.span(),
|
let substr = args
|
||||||
)
|
.get_err(1, "substring")?
|
||||||
.into())
|
.assert_string_with_name("substring", args.span())?
|
||||||
}
|
.0;
|
||||||
|
|
||||||
|
let char_position = match s1.find(&substr) {
|
||||||
|
Some(i) => s1[0..i].chars().count() + 1,
|
||||||
|
None => return Ok(Value::Null),
|
||||||
};
|
};
|
||||||
|
|
||||||
let substr = match args.get_err(1, "substring")? {
|
Ok(Value::Dimension(SassNumber {
|
||||||
Value::String(i, _) => i,
|
num: Number::from(char_position),
|
||||||
v => {
|
unit: Unit::None,
|
||||||
return Err((
|
as_slash: None,
|
||||||
format!("$substring: {} is not a string.", v.inspect(args.span())?),
|
}))
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(match s1.find(&substr) {
|
|
||||||
Some(v) => Value::Dimension(SassNumber {
|
|
||||||
num: (Number::from(v + 1)),
|
|
||||||
unit: Unit::None,
|
|
||||||
as_slash: None,
|
|
||||||
}),
|
|
||||||
None => Value::Null,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn str_insert(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
|
pub(crate) fn str_insert(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
|
||||||
args.max_args(3)?;
|
args.max_args(3)?;
|
||||||
let span = args.span();
|
let span = args.span();
|
||||||
|
|
||||||
let (s1, quotes) = match args.get_err(0, "string")? {
|
let (s1, quotes) = args
|
||||||
Value::String(i, q) => (i, q),
|
.get_err(0, "string")?
|
||||||
v => {
|
.assert_string_with_name("string", args.span())?;
|
||||||
return Err((
|
|
||||||
format!("$string: {} is not a string.", v.inspect(span)?),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let substr = match args.get_err(1, "insert")? {
|
let substr = args
|
||||||
Value::String(i, _) => i,
|
.get_err(1, "insert")?
|
||||||
v => {
|
.assert_string_with_name("insert", args.span())?
|
||||||
return Err((
|
.0;
|
||||||
format!("$insert: {} is not a string.", v.inspect(span)?),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let index = args
|
let index = args
|
||||||
.get_err(2, "index")?
|
.get_err(2, "index")?
|
||||||
|
@ -189,55 +189,20 @@ fn hypot(args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
|||||||
fn log(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
fn log(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||||
args.max_args(2)?;
|
args.max_args(2)?;
|
||||||
|
|
||||||
let number = match args.get_err(0, "number")? {
|
let span = args.span();
|
||||||
// Value::Dimension { num: n, .. } if n.is_nan() => todo!(),
|
|
||||||
Value::Dimension(SassNumber {
|
let number = args
|
||||||
num,
|
.get_err(0, "number")?
|
||||||
unit: Unit::None,
|
.assert_number_with_name("number", span)?;
|
||||||
..
|
number.assert_no_units("number", span)?;
|
||||||
}) => num,
|
let number = number.num;
|
||||||
v @ Value::Dimension(SassNumber { .. }) => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"$number: Expected {} to be unitless.",
|
|
||||||
v.inspect(args.span())?
|
|
||||||
),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
v => {
|
|
||||||
return Err((
|
|
||||||
format!("$number: {} is not a number.", v.inspect(args.span())?),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let base = match args.default_arg(1, "base", Value::Null) {
|
let base = match args.default_arg(1, "base", Value::Null) {
|
||||||
Value::Null => None,
|
Value::Null => None,
|
||||||
Value::Dimension(SassNumber {
|
|
||||||
num,
|
|
||||||
unit: Unit::None,
|
|
||||||
..
|
|
||||||
}) => Some(num),
|
|
||||||
v @ Value::Dimension(SassNumber { .. }) => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"$number: Expected {} to be unitless.",
|
|
||||||
v.inspect(args.span())?
|
|
||||||
),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
v => {
|
v => {
|
||||||
return Err((
|
let base = v.assert_number_with_name("base", span)?;
|
||||||
format!("$base: {} is not a number.", v.inspect(args.span())?),
|
base.assert_no_units("base", span)?;
|
||||||
args.span(),
|
Some(base.num)
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -264,58 +229,20 @@ fn log(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
|||||||
fn pow(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
fn pow(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||||
args.max_args(2)?;
|
args.max_args(2)?;
|
||||||
|
|
||||||
let base = match args.get_err(0, "base")? {
|
let span = args.span();
|
||||||
Value::Dimension(SassNumber {
|
|
||||||
num,
|
|
||||||
unit: Unit::None,
|
|
||||||
..
|
|
||||||
}) => num,
|
|
||||||
v @ Value::Dimension(SassNumber { .. }) => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"$base: Expected {} to have no units.",
|
|
||||||
v.inspect(args.span())?
|
|
||||||
),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
v => {
|
|
||||||
return Err((
|
|
||||||
format!("$base: {} is not a number.", v.inspect(args.span())?),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let exponent = match args.get_err(1, "exponent")? {
|
let base = args
|
||||||
Value::Dimension(SassNumber {
|
.get_err(0, "base")?
|
||||||
num,
|
.assert_number_with_name("base", span)?;
|
||||||
unit: Unit::None,
|
base.assert_no_units("base", span)?;
|
||||||
..
|
|
||||||
}) => num,
|
let exponent = args
|
||||||
v @ Value::Dimension(SassNumber { .. }) => {
|
.get_err(1, "exponent")?
|
||||||
return Err((
|
.assert_number_with_name("exponent", span)?;
|
||||||
format!(
|
exponent.assert_no_units("exponent", span)?;
|
||||||
"$exponent: Expected {} to have no units.",
|
|
||||||
v.inspect(args.span())?
|
|
||||||
),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
v => {
|
|
||||||
return Err((
|
|
||||||
format!("$exponent: {} is not a number.", v.inspect(args.span())?),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Value::Dimension(SassNumber {
|
Ok(Value::Dimension(SassNumber {
|
||||||
num: base.pow(exponent),
|
num: base.num.pow(exponent.num),
|
||||||
unit: Unit::None,
|
unit: Unit::None,
|
||||||
as_slash: None,
|
as_slash: None,
|
||||||
}))
|
}))
|
||||||
@ -323,36 +250,16 @@ fn pow(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
|||||||
|
|
||||||
fn sqrt(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
fn sqrt(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||||
args.max_args(1)?;
|
args.max_args(1)?;
|
||||||
let number = args.get_err(0, "number")?;
|
let number = args
|
||||||
|
.get_err(0, "number")?
|
||||||
|
.assert_number_with_name("number", args.span())?;
|
||||||
|
number.assert_no_units("number", args.span())?;
|
||||||
|
|
||||||
Ok(match number {
|
Ok(Value::Dimension(SassNumber {
|
||||||
Value::Dimension(SassNumber {
|
num: number.num.sqrt(),
|
||||||
num,
|
unit: Unit::None,
|
||||||
unit: Unit::None,
|
as_slash: None,
|
||||||
..
|
}))
|
||||||
}) => Value::Dimension(SassNumber {
|
|
||||||
num: num.sqrt(),
|
|
||||||
unit: Unit::None,
|
|
||||||
as_slash: None,
|
|
||||||
}),
|
|
||||||
v @ Value::Dimension(SassNumber { .. }) => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"$number: Expected {} to have no units.",
|
|
||||||
v.inspect(args.span())?
|
|
||||||
),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
v => {
|
|
||||||
return Err((
|
|
||||||
format!("$number: {} is not a number.", v.inspect(args.span())?),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! trig_fn {
|
macro_rules! trig_fn {
|
||||||
@ -400,136 +307,83 @@ trig_fn!(tan);
|
|||||||
|
|
||||||
fn acos(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
fn acos(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||||
args.max_args(1)?;
|
args.max_args(1)?;
|
||||||
let number = args.get_err(0, "number")?;
|
|
||||||
|
|
||||||
Ok(match number {
|
let span = args.span();
|
||||||
Value::Dimension(SassNumber {
|
|
||||||
num,
|
let number = args
|
||||||
unit: Unit::None,
|
.get_err(0, "number")?
|
||||||
..
|
.assert_number_with_name("number", span)?;
|
||||||
}) => Value::Dimension(SassNumber {
|
number.assert_no_units("number", span)?;
|
||||||
num: if num > Number(1.0) || num < Number(-1.0) {
|
let number = number.num;
|
||||||
Number(f64::NAN)
|
|
||||||
} else if num.is_one() {
|
Ok(Value::Dimension(SassNumber {
|
||||||
Number::zero()
|
num: if number > Number(1.0) || number < Number(-1.0) {
|
||||||
} else {
|
Number(f64::NAN)
|
||||||
num.acos()
|
} else if number.is_one() {
|
||||||
},
|
Number::zero()
|
||||||
unit: Unit::Deg,
|
} else {
|
||||||
as_slash: None,
|
number.acos()
|
||||||
}),
|
},
|
||||||
v @ Value::Dimension(SassNumber { .. }) => {
|
unit: Unit::Deg,
|
||||||
return Err((
|
as_slash: None,
|
||||||
format!(
|
}))
|
||||||
"$number: Expected {} to be unitless.",
|
|
||||||
v.inspect(args.span())?
|
|
||||||
),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
v => {
|
|
||||||
return Err((
|
|
||||||
format!("$number: {} is not a number.", v.inspect(args.span())?),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn asin(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
fn asin(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||||
args.max_args(1)?;
|
args.max_args(1)?;
|
||||||
let number = args.get_err(0, "number")?;
|
|
||||||
|
|
||||||
Ok(match number {
|
let span = args.span();
|
||||||
Value::Dimension(SassNumber {
|
|
||||||
num,
|
|
||||||
unit: Unit::None,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
if num > Number(1.0) || num < Number(-1.0) {
|
|
||||||
return Ok(Value::Dimension(SassNumber {
|
|
||||||
num: Number(f64::NAN),
|
|
||||||
unit: Unit::Deg,
|
|
||||||
as_slash: None,
|
|
||||||
}));
|
|
||||||
} else if num.is_zero() {
|
|
||||||
return Ok(Value::Dimension(SassNumber {
|
|
||||||
num: Number::zero(),
|
|
||||||
unit: Unit::Deg,
|
|
||||||
as_slash: None,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Dimension(SassNumber {
|
let number = args
|
||||||
num: num.asin(),
|
.get_err(0, "number")?
|
||||||
unit: Unit::Deg,
|
.assert_number_with_name("number", span)?;
|
||||||
as_slash: None,
|
number.assert_no_units("number", span)?;
|
||||||
})
|
let number = number.num;
|
||||||
}
|
|
||||||
v @ Value::Dimension(SassNumber { .. }) => {
|
if number > Number(1.0) || number < Number(-1.0) {
|
||||||
return Err((
|
return Ok(Value::Dimension(SassNumber {
|
||||||
format!(
|
num: Number(f64::NAN),
|
||||||
"$number: Expected {} to be unitless.",
|
unit: Unit::Deg,
|
||||||
v.inspect(args.span())?
|
as_slash: None,
|
||||||
),
|
}));
|
||||||
args.span(),
|
} else if number.is_zero() {
|
||||||
)
|
return Ok(Value::Dimension(SassNumber {
|
||||||
.into())
|
num: Number::zero(),
|
||||||
}
|
unit: Unit::Deg,
|
||||||
v => {
|
as_slash: None,
|
||||||
return Err((
|
}));
|
||||||
format!("$number: {} is not a number.", v.inspect(args.span())?),
|
}
|
||||||
args.span(),
|
|
||||||
)
|
Ok(Value::Dimension(SassNumber {
|
||||||
.into())
|
num: number.asin(),
|
||||||
}
|
unit: Unit::Deg,
|
||||||
})
|
as_slash: None,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn atan(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
fn atan(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||||
args.max_args(1)?;
|
args.max_args(1)?;
|
||||||
let number = args.get_err(0, "number")?;
|
|
||||||
|
|
||||||
Ok(match number {
|
let span = args.span();
|
||||||
Value::Dimension(SassNumber {
|
|
||||||
num: n,
|
|
||||||
unit: Unit::None,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
if n.is_zero() {
|
|
||||||
return Ok(Value::Dimension(SassNumber {
|
|
||||||
num: (Number::zero()),
|
|
||||||
unit: Unit::Deg,
|
|
||||||
as_slash: None,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Dimension(SassNumber {
|
let number = args
|
||||||
num: n.atan(),
|
.get_err(0, "number")?
|
||||||
unit: Unit::Deg,
|
.assert_number_with_name("number", span)?;
|
||||||
as_slash: None,
|
number.assert_no_units("number", span)?;
|
||||||
})
|
|
||||||
}
|
if number.num.is_zero() {
|
||||||
v @ Value::Dimension(SassNumber { .. }) => {
|
return Ok(Value::Dimension(SassNumber {
|
||||||
return Err((
|
num: (Number::zero()),
|
||||||
format!(
|
unit: Unit::Deg,
|
||||||
"$number: Expected {} to be unitless.",
|
as_slash: None,
|
||||||
v.inspect(args.span())?
|
}));
|
||||||
),
|
}
|
||||||
args.span(),
|
|
||||||
)
|
Ok(Value::Dimension(SassNumber {
|
||||||
.into())
|
num: number.num.atan(),
|
||||||
}
|
unit: Unit::Deg,
|
||||||
v => {
|
as_slash: None,
|
||||||
return Err((
|
}))
|
||||||
format!("$number: {} is not a number.", v.inspect(args.span())?),
|
|
||||||
args.span(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn atan2(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
fn atan2(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||||
|
@ -505,4 +505,12 @@ impl Color {
|
|||||||
|
|
||||||
Color::new_rgba(red, green, blue, alpha, ColorFormat::Infer)
|
Color::new_rgba(red, green, blue, alpha, ColorFormat::Infer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn whiteness(&self) -> Number {
|
||||||
|
self.red().min(self.green()).min(self.blue()) / Number(255.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blackness(&self) -> Number {
|
||||||
|
Number(1.0) - (self.red().max(self.green()).max(self.blue()) / Number(255.0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,8 @@ impl PartialEq for ListSeparator {
|
|||||||
#[allow(clippy::match_like_matches_macro)]
|
#[allow(clippy::match_like_matches_macro)]
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Self::Space | Self::Undecided, Self::Space | Self::Undecided) => true,
|
(Self::Space, Self::Space) => true,
|
||||||
|
(Self::Undecided, Self::Undecided) => true,
|
||||||
(Self::Comma, Self::Comma) => true,
|
(Self::Comma, Self::Comma) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use codemap::Span;
|
use codemap::Span;
|
||||||
@ -350,7 +348,7 @@ pub(crate) fn sub(left: Value, right: Value, options: &Options, span: Span) -> S
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn mul(left: Value, right: Value, options: &Options, span: Span) -> SassResult<Value> {
|
pub(crate) fn mul(left: Value, right: Value, _: &Options, span: Span) -> SassResult<Value> {
|
||||||
Ok(match left {
|
Ok(match left {
|
||||||
Value::Dimension(SassNumber {
|
Value::Dimension(SassNumber {
|
||||||
num,
|
num,
|
||||||
@ -412,7 +410,7 @@ pub(crate) fn mul(left: Value, right: Value, options: &Options, span: Span) -> S
|
|||||||
pub(crate) fn cmp(
|
pub(crate) fn cmp(
|
||||||
left: &Value,
|
left: &Value,
|
||||||
right: &Value,
|
right: &Value,
|
||||||
options: &Options,
|
_: &Options,
|
||||||
span: Span,
|
span: Span,
|
||||||
op: BinaryOp,
|
op: BinaryOp,
|
||||||
) -> SassResult<Value> {
|
) -> SassResult<Value> {
|
||||||
@ -469,7 +467,7 @@ pub(crate) fn div(left: Value, right: Value, options: &Options, span: Span) -> S
|
|||||||
Value::Dimension(SassNumber {
|
Value::Dimension(SassNumber {
|
||||||
num: num2,
|
num: num2,
|
||||||
unit: unit2,
|
unit: unit2,
|
||||||
as_slash: as_slash2,
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if unit2 == Unit::None {
|
if unit2 == Unit::None {
|
||||||
return Ok(Value::Dimension(SassNumber {
|
return Ok(Value::Dimension(SassNumber {
|
||||||
@ -540,7 +538,7 @@ pub(crate) fn div(left: Value, right: Value, options: &Options, span: Span) -> S
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rem(left: Value, right: Value, options: &Options, span: Span) -> SassResult<Value> {
|
pub(crate) fn rem(left: Value, right: Value, _: &Options, span: Span) -> SassResult<Value> {
|
||||||
Ok(match left {
|
Ok(match left {
|
||||||
Value::Dimension(SassNumber {
|
Value::Dimension(SassNumber {
|
||||||
num: n,
|
num: n,
|
||||||
|
@ -76,6 +76,15 @@ pub(crate) fn serialize_number(
|
|||||||
Ok(serializer.finish_for_expr())
|
Ok(serializer.finish_for_expr())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn inspect_float(number: f64, options: &Options, span: Span) -> String {
|
||||||
|
let map = CodeMap::new();
|
||||||
|
let mut serializer = Serializer::new(options, &map, true, span);
|
||||||
|
|
||||||
|
serializer.write_float(number);
|
||||||
|
|
||||||
|
serializer.finish_for_expr()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn inspect_number(
|
pub(crate) fn inspect_number(
|
||||||
number: &SassNumber,
|
number: &SassNumber,
|
||||||
options: &Options,
|
options: &Options,
|
||||||
|
@ -44,7 +44,7 @@ impl ArgList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.elems.len() + self.keywords.len()
|
self.elems.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
@ -52,7 +52,6 @@ impl ArgList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_null(&self) -> bool {
|
pub fn is_null(&self) -> bool {
|
||||||
// todo: include keywords
|
|
||||||
!self.is_empty() && (self.elems.iter().all(Value::is_null))
|
!self.is_empty() && (self.elems.iter().all(Value::is_null))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +270,7 @@ impl SassCalculation {
|
|||||||
return Err((
|
return Err((
|
||||||
format!(
|
format!(
|
||||||
"Number {} isn't compatible with CSS calculations.",
|
"Number {} isn't compatible with CSS calculations.",
|
||||||
value.to_css_string(span, false)?
|
value.inspect(span)?
|
||||||
),
|
),
|
||||||
span,
|
span,
|
||||||
)
|
)
|
||||||
|
@ -236,6 +236,25 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assert_string_with_name(
|
||||||
|
self,
|
||||||
|
name: &str,
|
||||||
|
span: Span,
|
||||||
|
) -> SassResult<(String, QuoteKind)> {
|
||||||
|
match self {
|
||||||
|
Value::String(s, quotes) => Ok((s, quotes)),
|
||||||
|
_ => Err((
|
||||||
|
format!(
|
||||||
|
"${name}: {} is not a string.",
|
||||||
|
self.inspect(span)?,
|
||||||
|
name = name,
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// todo: rename is_blank
|
// todo: rename is_blank
|
||||||
pub fn is_null(&self) -> bool {
|
pub fn is_null(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@ -599,8 +618,8 @@ impl Value {
|
|||||||
.join(", ")
|
.join(", ")
|
||||||
)),
|
)),
|
||||||
Value::Dimension(n) => Cow::Owned(inspect_number(n, &Options::default(), span)?),
|
Value::Dimension(n) => Cow::Owned(inspect_number(n, &Options::default(), span)?),
|
||||||
Value::ArgList(args) if args.is_empty() => Cow::Borrowed("()"),
|
Value::ArgList(args) if args.elems.is_empty() => Cow::Borrowed("()"),
|
||||||
Value::ArgList(args) if args.len() == 1 => Cow::Owned(format!(
|
Value::ArgList(args) if args.elems.len() == 1 => Cow::Owned(format!(
|
||||||
"({},)",
|
"({},)",
|
||||||
args.elems
|
args.elems
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -4,7 +4,7 @@ use codemap::Span;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
serializer::inspect_number,
|
serializer::{inspect_float, inspect_number},
|
||||||
unit::{are_any_convertible, known_compatibilities_by_unit, Unit, UNIT_CONVERSION_TABLE},
|
unit::{are_any_convertible, known_compatibilities_by_unit, Unit, UNIT_CONVERSION_TABLE},
|
||||||
Options,
|
Options,
|
||||||
};
|
};
|
||||||
@ -162,6 +162,26 @@ impl SassNumber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assert_bounds(&self, name: &str, min: f64, max: f64, span: Span) -> SassResult<()> {
|
||||||
|
if !(self.num <= Number(max) && self.num >= Number(min)) {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"${}: Expected {} to be within {}{} and {}{}.",
|
||||||
|
name,
|
||||||
|
inspect_number(self, &Options::default(), span)?,
|
||||||
|
inspect_float(min, &Options::default(), span),
|
||||||
|
self.unit,
|
||||||
|
inspect_float(max, &Options::default(), span),
|
||||||
|
self.unit,
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_comparable_to(&self, other: &Self) -> bool {
|
pub fn is_comparable_to(&self, other: &Self) -> bool {
|
||||||
self.unit.comparable(&other.unit)
|
self.unit.comparable(&other.unit)
|
||||||
}
|
}
|
||||||
|
@ -92,3 +92,51 @@ test!(
|
|||||||
}",
|
}",
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
keyword_args_no_positional,
|
||||||
|
"@mixin foo($a...) {
|
||||||
|
pos: inspect($a);
|
||||||
|
kw: inspect(keywords($a));
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include foo($a: b);
|
||||||
|
}",
|
||||||
|
"a {\n pos: ();\n kw: (a: b);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
keyword_args_one_positional,
|
||||||
|
"@mixin foo($a...) {
|
||||||
|
pos: inspect($a);
|
||||||
|
kw: inspect(keywords($a));
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include foo(a, $b: c);
|
||||||
|
}",
|
||||||
|
"a {\n pos: (a,);\n kw: (b: c);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
keyword_args_length_no_positional,
|
||||||
|
"@mixin foo($a...) {
|
||||||
|
pos: length($a);
|
||||||
|
kw: length(keywords($a));
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include foo($a: b);
|
||||||
|
}",
|
||||||
|
"a {\n pos: 0;\n kw: 1;\n}\n"
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
keyword_args_no_positional_is_invalid,
|
||||||
|
"@mixin foo($a...) {
|
||||||
|
pos: $a;
|
||||||
|
kw: length(keywords($a));
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include foo($a: b);
|
||||||
|
}",
|
||||||
|
"Error: () isn't a valid CSS value."
|
||||||
|
);
|
||||||
|
@ -307,3 +307,37 @@ test!(
|
|||||||
"a {\n color: hue(adjust-hue(hsla(200, 50%, 50%), (0/0)));\n}\n",
|
"a {\n color: hue(adjust-hue(hsla(200, 50%, 50%), (0/0)));\n}\n",
|
||||||
"a {\n color: NaNdeg;\n}\n"
|
"a {\n color: NaNdeg;\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
hsl_special_two_arg_var_first,
|
||||||
|
"a {\n color: hsl(var(--foo), --bar);\n}\n",
|
||||||
|
"a {\n color: hsl(var(--foo), --bar);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
hsl_special_two_arg_var_second,
|
||||||
|
"a {\n color: hsl(--foo, var(--bar));\n}\n",
|
||||||
|
"a {\n color: hsl(--foo, var(--bar));\n}\n"
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
hsl_special_two_arg_neither_var,
|
||||||
|
"a {\n color: hsl(--foo, --bar);\n}\n", "Error: Missing argument $lightness."
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
hsl_special_four_arg_var_last,
|
||||||
|
"a {\n color: hsl(a, b, c, var(--bar));\n}\n",
|
||||||
|
"a {\n color: hsl(a, b, c, var(--bar));\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
hsl_special_three_arg_var_last,
|
||||||
|
"a {\n color: hsl(a, b, var(--bar));\n}\n",
|
||||||
|
"a {\n color: hsl(a, b, var(--bar));\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
darken_all_channels_equal,
|
||||||
|
"a {\n color: darken(#fff, 10);\n}\n",
|
||||||
|
"a {\n color: #e6e6e6;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
darken_green_channel_max,
|
||||||
|
"a {\n color: darken(rgb(50, 200, 100), 10);\n}\n",
|
||||||
|
"a {\n color: #289f50;\n}\n"
|
||||||
|
);
|
||||||
|
@ -86,6 +86,11 @@ test!(
|
|||||||
"@use \"sass:color\";\na {\n color: color.hwb(180 30% 40% / 0);\n}\n",
|
"@use \"sass:color\";\na {\n color: color.hwb(180 30% 40% / 0);\n}\n",
|
||||||
"a {\n color: rgba(77, 153, 153, 0);\n}\n"
|
"a {\n color: rgba(77, 153, 153, 0);\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
scale_whiteness,
|
||||||
|
"a {\n color: scale-color(#cc6666, $whiteness: 100%);\n}\n",
|
||||||
|
"a {\n color: #d5d5d5;\n}\n"
|
||||||
|
);
|
||||||
error!(
|
error!(
|
||||||
hwb_whiteness_missing_pct,
|
hwb_whiteness_missing_pct,
|
||||||
"@use \"sass:color\";\na {\n color: color.hwb(0, 0, 100);\n}\n",
|
"@use \"sass:color\";\na {\n color: color.hwb(0, 0, 100);\n}\n",
|
||||||
|
@ -99,3 +99,7 @@ error!(
|
|||||||
missing_closing_curly_brace,
|
missing_closing_curly_brace,
|
||||||
"@each $i in 1 {", "Error: expected \"}\"."
|
"@each $i in 1 {", "Error: expected \"}\"."
|
||||||
);
|
);
|
||||||
|
error!(
|
||||||
|
in_has_characters_after,
|
||||||
|
"@each $i inaa 0 1 2 {}", "Error: Expected \"in\"."
|
||||||
|
);
|
||||||
|
@ -1974,8 +1974,7 @@ error!(
|
|||||||
);
|
);
|
||||||
error!(
|
error!(
|
||||||
extend_at_root_of_document,
|
extend_at_root_of_document,
|
||||||
"@extend a;",
|
"@extend a;", "Error: @extend may only be used within style rules."
|
||||||
"Error: @extend may only be used within style rules."
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// todo: extend_loop (massive test)
|
// todo: extend_loop (massive test)
|
||||||
|
@ -215,4 +215,4 @@ error!(
|
|||||||
@forward "foo";
|
@forward "foo";
|
||||||
"#,
|
"#,
|
||||||
"Error: @forward rules must be written before any other rules."
|
"Error: @forward rules must be written before any other rules."
|
||||||
);
|
);
|
||||||
|
@ -228,11 +228,12 @@ error!(
|
|||||||
invalid_toplevel_selector,
|
invalid_toplevel_selector,
|
||||||
"@if true { & { } }", "Error: Top-level selectors may not contain the parent selector \"&\"."
|
"@if true { & { } }", "Error: Top-level selectors may not contain the parent selector \"&\"."
|
||||||
);
|
);
|
||||||
error!(
|
test!(
|
||||||
#[ignore = "unsure what the exact rule is here wrt denying interpolation (@media allows this)"]
|
treats_interpolated_if_as_unknown_at_rule,
|
||||||
denies_interpolated_at_rule,
|
"@#{if} true { a { color: red; } }",
|
||||||
"@#{if} true { a { color: red; } }", "Error: expected \"(\"."
|
"@if true {\n a {\n color: red;\n }\n}\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
else_if_escaped_lower_i,
|
else_if_escaped_lower_i,
|
||||||
r"@if false {
|
r"@if false {
|
||||||
|
@ -485,6 +485,11 @@ test!(
|
|||||||
"@import \"foo.css\";",
|
"@import \"foo.css\";",
|
||||||
"@import \"foo.css\";\n"
|
"@import \"foo.css\";\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
newline_in_plain_css,
|
||||||
|
"@import \"fo\\\no.css\";",
|
||||||
|
"@import \"fo\\\no.css\";\n"
|
||||||
|
);
|
||||||
test!(import_url, "@import url(foo..);", "@import url(foo..);\n");
|
test!(import_url, "@import url(foo..);", "@import url(foo..);\n");
|
||||||
test!(
|
test!(
|
||||||
import_url_interpolation,
|
import_url_interpolation,
|
||||||
|
@ -437,6 +437,18 @@ test!(
|
|||||||
"a {\n color: a, A, \"Noto Color Emoji\";\n}\n",
|
"a {\n color: a, A, \"Noto Color Emoji\";\n}\n",
|
||||||
"a {\n color: a, A, \"Noto Color Emoji\";\n}\n"
|
"a {\n color: a, A, \"Noto Color Emoji\";\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
list_separator_of_empty_list_after_join,
|
||||||
|
"a {
|
||||||
|
color: list-separator(join(join((), (), comma), 1 2));
|
||||||
|
color: list-separator(join(join((), (), comma), (1, 2)));
|
||||||
|
}",
|
||||||
|
"a {\n color: comma;\n color: comma;\n}\n"
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
nth_list_index_0,
|
||||||
|
"a {\n color: nth(a b c, 0);\n}\n", "Error: $n: List index may not be 0."
|
||||||
|
);
|
||||||
error!(
|
error!(
|
||||||
invalid_item_in_space_separated_list,
|
invalid_item_in_space_separated_list,
|
||||||
"a {\n color: red color * #abc;\n}\n", "Error: Undefined operation \"color * #abc\"."
|
"a {\n color: red color * #abc;\n}\n", "Error: Undefined operation \"color * #abc\"."
|
||||||
|
@ -126,3 +126,35 @@ error!(
|
|||||||
"a {\n color: random(1000000000000000001 - 1000000000000000000);\n}\n",
|
"a {\n color: random(1000000000000000001 - 1000000000000000000);\n}\n",
|
||||||
"Error: $limit: Must be greater than 0, was 0."
|
"Error: $limit: Must be greater than 0, was 0."
|
||||||
);
|
);
|
||||||
|
error!(
|
||||||
|
percentage_non_number_arg,
|
||||||
|
"a {\n color: percentage(a);\n}\n", "Error: $number: a is not a number."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
round_non_number_arg,
|
||||||
|
"a {\n color: round(a);\n}\n", "Error: $number: a is not a number."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
ceil_non_number_arg,
|
||||||
|
"a {\n color: ceil(a);\n}\n", "Error: $number: a is not a number."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
floor_non_number_arg,
|
||||||
|
"a {\n color: floor(a);\n}\n", "Error: $number: a is not a number."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
abs_non_number_arg,
|
||||||
|
"a {\n color: abs(a);\n}\n", "Error: $number: a is not a number."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
comparable_non_number_arg_both,
|
||||||
|
"a {\n color: comparable(a, b);\n}\n", "Error: $number1: a is not a number."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
comparable_non_number_arg_first,
|
||||||
|
"a {\n color: comparable(a, 1);\n}\n", "Error: $number1: a is not a number."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
comparable_non_number_arg_last,
|
||||||
|
"a {\n color: comparable(1, b);\n}\n", "Error: $number2: b is not a number."
|
||||||
|
);
|
||||||
|
@ -885,6 +885,48 @@ test!(
|
|||||||
}"#,
|
}"#,
|
||||||
"\\\\ {\n color: \\\\;\n}\n"
|
"\\\\ {\n color: \\\\;\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
pseudo_element_double_quotes,
|
||||||
|
r#"::foo("red") {
|
||||||
|
color: &;
|
||||||
|
}"#,
|
||||||
|
"::foo(\"red\") {\n color: ::foo(\"red\");\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
pseudo_element_single_quotes,
|
||||||
|
r#"::foo('red') {
|
||||||
|
color: &;
|
||||||
|
}"#,
|
||||||
|
"::foo(\"red\") {\n color: ::foo(\"red\");\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
pseudo_element_loud_comments,
|
||||||
|
r#"::foo(/**/a/**/b/**/) {
|
||||||
|
color: &;
|
||||||
|
}"#,
|
||||||
|
"::foo(a/**/b/**/) {\n color: ::foo(a/**/b/**/);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
pseudo_element_forward_slash,
|
||||||
|
r#"::foo(/a/b/) {
|
||||||
|
color: &;
|
||||||
|
}"#,
|
||||||
|
"::foo(/a/b/) {\n color: ::foo(/a/b/);\n}\n"
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
pseudo_element_interpolated_semicolon_no_brackets,
|
||||||
|
r#"::foo(#{";"}) {
|
||||||
|
color: &;
|
||||||
|
}"#,
|
||||||
|
r#"Error: expected ")"."#
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
pseudo_element_interpolated_semicolon_with_parens,
|
||||||
|
r#"::foo((#{";"})) {
|
||||||
|
color: &;
|
||||||
|
}"#,
|
||||||
|
"::foo((;)) {\n color: ::foo((;));\n}\n"
|
||||||
|
);
|
||||||
error!(
|
error!(
|
||||||
a_n_plus_b_n_invalid_odd,
|
a_n_plus_b_n_invalid_odd,
|
||||||
":nth-child(ofdd) {\n color: &;\n}\n", "Error: Expected \"odd\"."
|
":nth-child(ofdd) {\n color: &;\n}\n", "Error: Expected \"odd\"."
|
||||||
|
@ -42,6 +42,11 @@ error!(
|
|||||||
calc_retains_multiline_comment,
|
calc_retains_multiline_comment,
|
||||||
"a {\n color: calc(/**/);\n}\n", "Error: Expected number, variable, function, or calculation."
|
"a {\n color: calc(/**/);\n}\n", "Error: Expected number, variable, function, or calculation."
|
||||||
);
|
);
|
||||||
|
error!(
|
||||||
|
calc_complex_unit,
|
||||||
|
"a {\n color: calc(1% + 1px * 2px);\n}\n",
|
||||||
|
"Error: Number 2px*px isn't compatible with CSS calculations."
|
||||||
|
);
|
||||||
error!(
|
error!(
|
||||||
calc_nested_parens,
|
calc_nested_parens,
|
||||||
"a {\n color: calc((((()))));\n}\n",
|
"a {\n color: calc((((()))));\n}\n",
|
||||||
|
@ -244,3 +244,13 @@ test!(
|
|||||||
"a {\n color: \"f\\\n oo\";\n}\n",
|
"a {\n color: \"f\\\n oo\";\n}\n",
|
||||||
"a {\n color: \"f oo\";\n}\n"
|
"a {\n color: \"f oo\";\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
str_index_double_width_character,
|
||||||
|
"a {\n color: str-index(\"👭a\", \"a\");\n}\n",
|
||||||
|
"a {\n color: 2;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
str_index_combining_character,
|
||||||
|
"a {\n color: str-index(\"c\\0308 a\", \"a\");\n}\n",
|
||||||
|
"a {\n color: 3;\n}\n"
|
||||||
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user