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
|
||||
|
||||
- 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.
|
||||
|
||||
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).
|
||||
|
||||
@ -71,8 +71,8 @@ Using a modified version of the spec runner that ignores warnings and error span
|
||||
|
||||
```
|
||||
2022-12-26
|
||||
PASSING: 6024
|
||||
FAILING: 881
|
||||
PASSING: 6077
|
||||
FAILING: 828
|
||||
TOTAL: 6905
|
||||
```
|
||||
|
||||
|
@ -9,11 +9,8 @@ pub(crate) fn blackness(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass
|
||||
.get_err(0, "color")?
|
||||
.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 {
|
||||
num: (blackness * 100),
|
||||
num: color.blackness() * 100,
|
||||
unit: Unit::Percent,
|
||||
as_slash: None,
|
||||
}))
|
||||
@ -26,10 +23,8 @@ pub(crate) fn whiteness(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass
|
||||
.get_err(0, "color")?
|
||||
.assert_color_with_name("color", args.span())?;
|
||||
|
||||
let whiteness = color.red().min(color.green()).min(color.blue()) / Number(255.0);
|
||||
|
||||
Ok(Value::Dimension(SassNumber {
|
||||
num: (whiteness * 100),
|
||||
num: color.whiteness() * 100,
|
||||
unit: Unit::Percent,
|
||||
as_slash: None,
|
||||
}))
|
||||
@ -102,9 +97,11 @@ pub(crate) fn hwb(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult
|
||||
visitor,
|
||||
args.span(),
|
||||
)? {
|
||||
ParsedChannels::String(s) => {
|
||||
Err((format!("Expected numeric channels, got {}", s), args.span()).into())
|
||||
}
|
||||
ParsedChannels::String(s) => Err((
|
||||
format!("Expected numeric channels, got \"{}\"", s),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
ParsedChannels::List(list) => {
|
||||
let args = ArgumentResult {
|
||||
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, 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.
|
||||
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(
|
||||
hue.unwrap_or(this_hue),
|
||||
saturation.unwrap_or(this_saturation),
|
||||
luminance.unwrap_or(this_luminance),
|
||||
lightness.unwrap_or(this_lightness),
|
||||
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, 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.
|
||||
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(
|
||||
this_hue + hue.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),
|
||||
))));
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
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 color = args
|
||||
.get_err(0, "color")?
|
||||
.assert_color_with_name("color", args.span())?;
|
||||
|
||||
macro_rules! opt_scale_arg {
|
||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => {
|
||||
let $name = match $args.default_named_arg($arg, Value::Null) {
|
||||
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(),
|
||||
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);
|
||||
let red = get_arg(&mut args, "red", -100.0, 100.0)?;
|
||||
let green = get_arg(&mut args, "green", -100.0, 100.0)?;
|
||||
let blue = get_arg(&mut args, "blue", -100.0, 100.0)?;
|
||||
let alpha = get_arg(&mut args, "alpha", -100.0, 100.0)?;
|
||||
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
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);
|
||||
opt_scale_arg!(args, luminance, "lightness", -100, 100);
|
||||
let saturation = get_arg(&mut args, "saturation", -100.0, 100.0)?;
|
||||
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.
|
||||
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(
|
||||
scale(this_hue, Number::zero(), Number(360.0)),
|
||||
scale(
|
||||
@ -240,8 +232,8 @@ pub(crate) fn scale_color(mut args: ArgumentResult, visitor: &mut Visitor) -> Sa
|
||||
Number::one(),
|
||||
),
|
||||
scale(
|
||||
this_luminance,
|
||||
luminance.unwrap_or_else(Number::zero),
|
||||
this_lightness,
|
||||
lightness.unwrap_or_else(Number::zero),
|
||||
Number::one(),
|
||||
),
|
||||
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 {
|
||||
let temp_alpha = color.alpha();
|
||||
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,
|
||||
),
|
||||
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")? {
|
||||
Value::Dimension(SassNumber {
|
||||
@ -133,7 +133,7 @@ pub(crate) fn append(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRes
|
||||
args.max_args(3)?;
|
||||
let (mut list, sep, brackets) = match args.get_err(0, "list")? {
|
||||
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 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(s, ..) => match s.as_str() {
|
||||
"auto" => sep,
|
||||
"auto" => {
|
||||
if sep == ListSeparator::Undecided {
|
||||
ListSeparator::Space
|
||||
} else {
|
||||
sep
|
||||
}
|
||||
}
|
||||
"comma" => ListSeparator::Comma,
|
||||
"space" => ListSeparator::Space,
|
||||
"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")? {
|
||||
Value::List(v, sep, brackets) => (v, sep, brackets),
|
||||
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")? {
|
||||
Value::List(v, sep, ..) => (v, sep),
|
||||
Value::Map(m) => (m.as_list(), ListSeparator::Comma),
|
||||
v => (vec![v], ListSeparator::Space),
|
||||
v => (vec![v], ListSeparator::Undecided),
|
||||
};
|
||||
let sep = match args.default_arg(
|
||||
2,
|
||||
@ -187,10 +193,12 @@ pub(crate) fn join(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResul
|
||||
) {
|
||||
Value::String(s, ..) => match s.as_str() {
|
||||
"auto" => {
|
||||
if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) {
|
||||
if sep1 != ListSeparator::Undecided {
|
||||
sep1
|
||||
} else if sep2 != ListSeparator::Undecided {
|
||||
sep2
|
||||
} else {
|
||||
sep1
|
||||
ListSeparator::Space
|
||||
}
|
||||
}
|
||||
"comma" => ListSeparator::Comma,
|
||||
|
@ -75,16 +75,9 @@ pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass
|
||||
|
||||
let span = args.span();
|
||||
|
||||
let (string, quotes) = match args.get_err(0, "string")? {
|
||||
Value::String(s, q) => (s, q),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$string: {} is not a string.", v.inspect(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let (string, quotes) = args
|
||||
.get_err(0, "string")?
|
||||
.assert_string_with_name("string", args.span())?;
|
||||
|
||||
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> {
|
||||
args.max_args(2)?;
|
||||
let s1 = match args.get_err(0, "string")? {
|
||||
Value::String(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$string: {} is not a string.", v.inspect(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
let s1 = args
|
||||
.get_err(0, "string")?
|
||||
.assert_string_with_name("string", args.span())?
|
||||
.0;
|
||||
|
||||
let substr = args
|
||||
.get_err(1, "substring")?
|
||||
.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")? {
|
||||
Value::String(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
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)),
|
||||
Ok(Value::Dimension(SassNumber {
|
||||
num: Number::from(char_position),
|
||||
unit: Unit::None,
|
||||
as_slash: None,
|
||||
}),
|
||||
None => Value::Null,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn str_insert(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let span = args.span();
|
||||
|
||||
let (s1, quotes) = match args.get_err(0, "string")? {
|
||||
Value::String(i, q) => (i, q),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$string: {} is not a string.", v.inspect(span)?),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let (s1, quotes) = args
|
||||
.get_err(0, "string")?
|
||||
.assert_string_with_name("string", args.span())?;
|
||||
|
||||
let substr = match args.get_err(1, "insert")? {
|
||||
Value::String(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$insert: {} is not a string.", v.inspect(span)?),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let substr = args
|
||||
.get_err(1, "insert")?
|
||||
.assert_string_with_name("insert", args.span())?
|
||||
.0;
|
||||
|
||||
let index = args
|
||||
.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> {
|
||||
args.max_args(2)?;
|
||||
|
||||
let number = match args.get_err(0, "number")? {
|
||||
// Value::Dimension { num: n, .. } if n.is_nan() => todo!(),
|
||||
Value::Dimension(SassNumber {
|
||||
num,
|
||||
unit: Unit::None,
|
||||
..
|
||||
}) => 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 span = args.span();
|
||||
|
||||
let number = args
|
||||
.get_err(0, "number")?
|
||||
.assert_number_with_name("number", span)?;
|
||||
number.assert_no_units("number", span)?;
|
||||
let number = number.num;
|
||||
|
||||
let base = match args.default_arg(1, "base", Value::Null) {
|
||||
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 => {
|
||||
return Err((
|
||||
format!("$base: {} is not a number.", v.inspect(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
let base = v.assert_number_with_name("base", span)?;
|
||||
base.assert_no_units("base", span)?;
|
||||
Some(base.num)
|
||||
}
|
||||
};
|
||||
|
||||
@ -264,58 +229,20 @@ fn log(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||
fn pow(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
|
||||
let base = match args.get_err(0, "base")? {
|
||||
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 span = args.span();
|
||||
|
||||
let exponent = match args.get_err(1, "exponent")? {
|
||||
Value::Dimension(SassNumber {
|
||||
num,
|
||||
unit: Unit::None,
|
||||
..
|
||||
}) => num,
|
||||
v @ Value::Dimension(SassNumber { .. }) => {
|
||||
return Err((
|
||||
format!(
|
||||
"$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())
|
||||
}
|
||||
};
|
||||
let base = args
|
||||
.get_err(0, "base")?
|
||||
.assert_number_with_name("base", span)?;
|
||||
base.assert_no_units("base", span)?;
|
||||
|
||||
let exponent = args
|
||||
.get_err(1, "exponent")?
|
||||
.assert_number_with_name("exponent", span)?;
|
||||
exponent.assert_no_units("exponent", span)?;
|
||||
|
||||
Ok(Value::Dimension(SassNumber {
|
||||
num: base.pow(exponent),
|
||||
num: base.num.pow(exponent.num),
|
||||
unit: Unit::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> {
|
||||
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 {
|
||||
Value::Dimension(SassNumber {
|
||||
num,
|
||||
unit: Unit::None,
|
||||
..
|
||||
}) => Value::Dimension(SassNumber {
|
||||
num: num.sqrt(),
|
||||
Ok(Value::Dimension(SassNumber {
|
||||
num: number.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 {
|
||||
@ -400,61 +307,46 @@ trig_fn!(tan);
|
||||
|
||||
fn acos(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let number = args.get_err(0, "number")?;
|
||||
|
||||
Ok(match number {
|
||||
Value::Dimension(SassNumber {
|
||||
num,
|
||||
unit: Unit::None,
|
||||
..
|
||||
}) => Value::Dimension(SassNumber {
|
||||
num: if num > Number(1.0) || num < Number(-1.0) {
|
||||
let span = args.span();
|
||||
|
||||
let number = args
|
||||
.get_err(0, "number")?
|
||||
.assert_number_with_name("number", span)?;
|
||||
number.assert_no_units("number", span)?;
|
||||
let number = number.num;
|
||||
|
||||
Ok(Value::Dimension(SassNumber {
|
||||
num: if number > Number(1.0) || number < Number(-1.0) {
|
||||
Number(f64::NAN)
|
||||
} else if num.is_one() {
|
||||
} else if number.is_one() {
|
||||
Number::zero()
|
||||
} else {
|
||||
num.acos()
|
||||
number.acos()
|
||||
},
|
||||
unit: Unit::Deg,
|
||||
as_slash: None,
|
||||
}),
|
||||
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())
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn asin(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let number = args.get_err(0, "number")?;
|
||||
|
||||
Ok(match number {
|
||||
Value::Dimension(SassNumber {
|
||||
num,
|
||||
unit: Unit::None,
|
||||
..
|
||||
}) => {
|
||||
if num > Number(1.0) || num < Number(-1.0) {
|
||||
let span = args.span();
|
||||
|
||||
let number = args
|
||||
.get_err(0, "number")?
|
||||
.assert_number_with_name("number", span)?;
|
||||
number.assert_no_units("number", span)?;
|
||||
let number = number.num;
|
||||
|
||||
if number > Number(1.0) || number < Number(-1.0) {
|
||||
return Ok(Value::Dimension(SassNumber {
|
||||
num: Number(f64::NAN),
|
||||
unit: Unit::Deg,
|
||||
as_slash: None,
|
||||
}));
|
||||
} else if num.is_zero() {
|
||||
} else if number.is_zero() {
|
||||
return Ok(Value::Dimension(SassNumber {
|
||||
num: Number::zero(),
|
||||
unit: Unit::Deg,
|
||||
@ -462,43 +354,24 @@ fn asin(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||
}));
|
||||
}
|
||||
|
||||
Value::Dimension(SassNumber {
|
||||
num: num.asin(),
|
||||
Ok(Value::Dimension(SassNumber {
|
||||
num: number.asin(),
|
||||
unit: Unit::Deg,
|
||||
as_slash: None,
|
||||
})
|
||||
}
|
||||
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())
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn atan(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let number = args.get_err(0, "number")?;
|
||||
|
||||
Ok(match number {
|
||||
Value::Dimension(SassNumber {
|
||||
num: n,
|
||||
unit: Unit::None,
|
||||
..
|
||||
}) => {
|
||||
if n.is_zero() {
|
||||
let span = args.span();
|
||||
|
||||
let number = args
|
||||
.get_err(0, "number")?
|
||||
.assert_number_with_name("number", span)?;
|
||||
number.assert_no_units("number", span)?;
|
||||
|
||||
if number.num.is_zero() {
|
||||
return Ok(Value::Dimension(SassNumber {
|
||||
num: (Number::zero()),
|
||||
unit: Unit::Deg,
|
||||
@ -506,30 +379,11 @@ fn atan(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||
}));
|
||||
}
|
||||
|
||||
Value::Dimension(SassNumber {
|
||||
num: n.atan(),
|
||||
Ok(Value::Dimension(SassNumber {
|
||||
num: number.num.atan(),
|
||||
unit: Unit::Deg,
|
||||
as_slash: None,
|
||||
})
|
||||
}
|
||||
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())
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn atan2(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
|
||||
|
@ -505,4 +505,12 @@ impl Color {
|
||||
|
||||
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)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
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,
|
||||
_ => false,
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
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 {
|
||||
Value::Dimension(SassNumber {
|
||||
num,
|
||||
@ -412,7 +410,7 @@ pub(crate) fn mul(left: Value, right: Value, options: &Options, span: Span) -> S
|
||||
pub(crate) fn cmp(
|
||||
left: &Value,
|
||||
right: &Value,
|
||||
options: &Options,
|
||||
_: &Options,
|
||||
span: Span,
|
||||
op: BinaryOp,
|
||||
) -> SassResult<Value> {
|
||||
@ -469,7 +467,7 @@ pub(crate) fn div(left: Value, right: Value, options: &Options, span: Span) -> S
|
||||
Value::Dimension(SassNumber {
|
||||
num: num2,
|
||||
unit: unit2,
|
||||
as_slash: as_slash2,
|
||||
..
|
||||
}) => {
|
||||
if unit2 == Unit::None {
|
||||
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 {
|
||||
Value::Dimension(SassNumber {
|
||||
num: n,
|
||||
|
@ -76,6 +76,15 @@ pub(crate) fn serialize_number(
|
||||
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(
|
||||
number: &SassNumber,
|
||||
options: &Options,
|
||||
|
@ -44,7 +44,7 @@ impl ArgList {
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.elems.len() + self.keywords.len()
|
||||
self.elems.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
@ -52,7 +52,6 @@ impl ArgList {
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
// todo: include keywords
|
||||
!self.is_empty() && (self.elems.iter().all(Value::is_null))
|
||||
}
|
||||
|
||||
|
@ -270,7 +270,7 @@ impl SassCalculation {
|
||||
return Err((
|
||||
format!(
|
||||
"Number {} isn't compatible with CSS calculations.",
|
||||
value.to_css_string(span, false)?
|
||||
value.inspect(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
|
||||
pub fn is_null(&self) -> bool {
|
||||
match self {
|
||||
@ -599,8 +618,8 @@ impl Value {
|
||||
.join(", ")
|
||||
)),
|
||||
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.len() == 1 => Cow::Owned(format!(
|
||||
Value::ArgList(args) if args.elems.is_empty() => Cow::Borrowed("()"),
|
||||
Value::ArgList(args) if args.elems.len() == 1 => Cow::Owned(format!(
|
||||
"({},)",
|
||||
args.elems
|
||||
.iter()
|
||||
|
@ -4,7 +4,7 @@ use codemap::Span;
|
||||
|
||||
use crate::{
|
||||
error::SassResult,
|
||||
serializer::inspect_number,
|
||||
serializer::{inspect_float, inspect_number},
|
||||
unit::{are_any_convertible, known_compatibilities_by_unit, Unit, UNIT_CONVERSION_TABLE},
|
||||
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 {
|
||||
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: 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",
|
||||
"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!(
|
||||
hwb_whiteness_missing_pct,
|
||||
"@use \"sass:color\";\na {\n color: color.hwb(0, 0, 100);\n}\n",
|
||||
|
@ -99,3 +99,7 @@ error!(
|
||||
missing_closing_curly_brace,
|
||||
"@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!(
|
||||
extend_at_root_of_document,
|
||||
"@extend a;",
|
||||
"Error: @extend may only be used within style rules."
|
||||
"@extend a;", "Error: @extend may only be used within style rules."
|
||||
);
|
||||
|
||||
// todo: extend_loop (massive test)
|
||||
|
@ -228,11 +228,12 @@ error!(
|
||||
invalid_toplevel_selector,
|
||||
"@if true { & { } }", "Error: Top-level selectors may not contain the parent selector \"&\"."
|
||||
);
|
||||
error!(
|
||||
#[ignore = "unsure what the exact rule is here wrt denying interpolation (@media allows this)"]
|
||||
denies_interpolated_at_rule,
|
||||
"@#{if} true { a { color: red; } }", "Error: expected \"(\"."
|
||||
test!(
|
||||
treats_interpolated_if_as_unknown_at_rule,
|
||||
"@#{if} true { a { color: red; } }",
|
||||
"@if true {\n a {\n color: red;\n }\n}\n"
|
||||
);
|
||||
|
||||
test!(
|
||||
else_if_escaped_lower_i,
|
||||
r"@if false {
|
||||
|
@ -485,6 +485,11 @@ test!(
|
||||
"@import \"foo.css\";",
|
||||
"@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_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"
|
||||
);
|
||||
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!(
|
||||
invalid_item_in_space_separated_list,
|
||||
"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",
|
||||
"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"
|
||||
);
|
||||
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!(
|
||||
a_n_plus_b_n_invalid_odd,
|
||||
":nth-child(ofdd) {\n color: &;\n}\n", "Error: Expected \"odd\"."
|
||||
|
@ -42,6 +42,11 @@ error!(
|
||||
calc_retains_multiline_comment,
|
||||
"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!(
|
||||
calc_nested_parens,
|
||||
"a {\n color: calc((((()))));\n}\n",
|
||||
|
@ -244,3 +244,13 @@ test!(
|
||||
"a {\n color: \"f\\\n 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