implement special-cased functions min and max
This commit is contained in:
parent
1362d747a4
commit
082d58853b
10
README.md
10
README.md
@ -31,13 +31,15 @@ for this version will be provided when the library becomes more stable.
|
|||||||
The large features remaining are
|
The large features remaining are
|
||||||
|
|
||||||
```
|
```
|
||||||
builtin functions min, max
|
indented syntax
|
||||||
indented syntax (27 tests)
|
|
||||||
css imports
|
css imports
|
||||||
@use, @forward, and the module system (~1000 tests)
|
@use, @forward, and the module system
|
||||||
@keyframes (~30 tests)
|
@keyframes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This is in addition to dozens of smaller features, edge cases, and miscompilations.
|
||||||
|
Features currently blocking Bootstrap are tracked [here](https://github.com/connorskees/grass/issues/4).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### commandline
|
### commandline
|
||||||
|
11
src/args.rs
11
src/args.rs
@ -145,6 +145,17 @@ impl CallArgs {
|
|||||||
self.0.is_empty()
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn min_args(&self, min: usize) -> SassResult<()> {
|
||||||
|
let len = self.len();
|
||||||
|
if len < min {
|
||||||
|
if min == 1 {
|
||||||
|
return Err(("At least one argument must be passed.", self.span()).into());
|
||||||
|
}
|
||||||
|
todo!("min args greater than one")
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn max_args(&self, max: usize) -> SassResult<()> {
|
pub fn max_args(&self, max: usize) -> SassResult<()> {
|
||||||
let len = self.len();
|
let len = self.len();
|
||||||
if len > max {
|
if len > max {
|
||||||
|
@ -7,6 +7,7 @@ use rand::Rng;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
args::CallArgs,
|
args::CallArgs,
|
||||||
|
common::Op,
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
parse::Parser,
|
parse::Parser,
|
||||||
unit::Unit,
|
unit::Unit,
|
||||||
@ -189,12 +190,78 @@ fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
|
args.min_args(1)?;
|
||||||
|
let span = args.span();
|
||||||
|
let mut nums = parser
|
||||||
|
.variadic_args(args)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|val| match val.node {
|
||||||
|
Value::Dimension(number, unit) => Ok((number, unit)),
|
||||||
|
v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()),
|
||||||
|
})
|
||||||
|
.collect::<SassResult<Vec<(Number, Unit)>>>()?
|
||||||
|
.into_iter();
|
||||||
|
|
||||||
|
// we know that there *must* be at least one item
|
||||||
|
let mut min = nums.next().unwrap();
|
||||||
|
|
||||||
|
for num in nums {
|
||||||
|
if Value::Dimension(num.0.clone(), num.1.clone())
|
||||||
|
.cmp(
|
||||||
|
Value::Dimension(min.0.clone(), min.1.clone()),
|
||||||
|
Op::LessThan,
|
||||||
|
span,
|
||||||
|
)?
|
||||||
|
.node
|
||||||
|
.is_true(span)?
|
||||||
|
{
|
||||||
|
min = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Value::Dimension(min.0, min.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
|
args.min_args(1)?;
|
||||||
|
let span = args.span();
|
||||||
|
let mut nums = parser
|
||||||
|
.variadic_args(args)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|val| match val.node {
|
||||||
|
Value::Dimension(number, unit) => Ok((number, unit)),
|
||||||
|
v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()),
|
||||||
|
})
|
||||||
|
.collect::<SassResult<Vec<(Number, Unit)>>>()?
|
||||||
|
.into_iter();
|
||||||
|
|
||||||
|
// we know that there *must* be at least one item
|
||||||
|
let mut max = nums.next().unwrap();
|
||||||
|
|
||||||
|
for num in nums {
|
||||||
|
if Value::Dimension(num.0.clone(), num.1.clone())
|
||||||
|
.cmp(
|
||||||
|
Value::Dimension(max.0.clone(), max.1.clone()),
|
||||||
|
Op::GreaterThan,
|
||||||
|
span,
|
||||||
|
)?
|
||||||
|
.node
|
||||||
|
.is_true(span)?
|
||||||
|
{
|
||||||
|
max = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Value::Dimension(max.0, max.1))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn declare(f: &mut GlobalFunctionMap) {
|
pub(crate) fn declare(f: &mut GlobalFunctionMap) {
|
||||||
f.insert("percentage", Builtin::new(percentage));
|
f.insert("percentage", Builtin::new(percentage));
|
||||||
f.insert("round", Builtin::new(round));
|
f.insert("round", Builtin::new(round));
|
||||||
f.insert("ceil", Builtin::new(ceil));
|
f.insert("ceil", Builtin::new(ceil));
|
||||||
f.insert("floor", Builtin::new(floor));
|
f.insert("floor", Builtin::new(floor));
|
||||||
f.insert("abs", Builtin::new(abs));
|
f.insert("abs", Builtin::new(abs));
|
||||||
|
f.insert("min", Builtin::new(min));
|
||||||
|
f.insert("max", Builtin::new(max));
|
||||||
f.insert("comparable", Builtin::new(comparable));
|
f.insert("comparable", Builtin::new(comparable));
|
||||||
#[cfg(feature = "random")]
|
#[cfg(feature = "random")]
|
||||||
f.insert("random", Builtin::new(random));
|
f.insert("random", Builtin::new(random));
|
||||||
|
@ -15,9 +15,9 @@ use crate::{
|
|||||||
error::SassResult,
|
error::SassResult,
|
||||||
unit::Unit,
|
unit::Unit,
|
||||||
utils::{
|
utils::{
|
||||||
as_hex, devour_whitespace, eat_number, hex_char_for, is_name,
|
as_hex, devour_whitespace, eat_number, hex_char_for, is_name, peek_ident_no_interpolation,
|
||||||
peek_until_closing_curly_brace, peek_whitespace, read_until_char, read_until_closing_paren,
|
peek_until_closing_curly_brace, peek_whitespace, read_until_char, read_until_closing_paren,
|
||||||
read_until_closing_square_brace, IsWhitespace,
|
read_until_closing_square_brace, IsWhitespace,
|
||||||
},
|
},
|
||||||
value::Value,
|
value::Value,
|
||||||
value::{Number, SassMap},
|
value::{Number, SassMap},
|
||||||
@ -183,6 +183,35 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
if let Some(Token { kind: '(', .. }) = self.toks.peek() {
|
if let Some(Token { kind: '(', .. }) = self.toks.peek() {
|
||||||
self.toks.next();
|
self.toks.next();
|
||||||
|
|
||||||
|
if lower == "min" {
|
||||||
|
match self.try_parse_min_max("min", true)? {
|
||||||
|
Some((val, len)) => {
|
||||||
|
self.toks.take(len).for_each(drop);
|
||||||
|
return Ok(
|
||||||
|
IntermediateValue::Value(Value::String(val, QuoteKind::None))
|
||||||
|
.span(span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.toks.reset_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if lower == "max" {
|
||||||
|
match self.try_parse_min_max("max", true)? {
|
||||||
|
Some((val, len)) => {
|
||||||
|
self.toks.take(len).for_each(drop);
|
||||||
|
return Ok(
|
||||||
|
IntermediateValue::Value(Value::String(val, QuoteKind::None))
|
||||||
|
.span(span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.toks.reset_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let as_ident = Identifier::from(&s);
|
let as_ident = Identifier::from(&s);
|
||||||
let ident_as_string = as_ident.clone().into_inner();
|
let ident_as_string = as_ident.clone().into_inner();
|
||||||
let func = match self.scopes.last().get_fn(
|
let func = match self.scopes.last().get_fn(
|
||||||
@ -206,8 +235,6 @@ impl<'a> Parser<'a> {
|
|||||||
s = lower;
|
s = lower;
|
||||||
self.eat_calc_args(&mut s)?;
|
self.eat_calc_args(&mut s)?;
|
||||||
}
|
}
|
||||||
// "min" => {}
|
|
||||||
// "max" => {}
|
|
||||||
"url" => match self.try_eat_url()? {
|
"url" => match self.try_eat_url()? {
|
||||||
Some(val) => s = val,
|
Some(val) => s = val,
|
||||||
None => s.push_str(&self.parse_call_args()?.to_css_string(self)?),
|
None => s.push_str(&self.parse_call_args()?.to_css_string(self)?),
|
||||||
@ -715,6 +742,243 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn peek_number(&mut self) -> SassResult<Option<(String, usize)>> {
|
||||||
|
let mut buf = String::new();
|
||||||
|
let mut peek_counter = 0;
|
||||||
|
|
||||||
|
let (num, count) = self.peek_whole_number();
|
||||||
|
peek_counter += count;
|
||||||
|
buf.push_str(&num);
|
||||||
|
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
|
||||||
|
if let Some(Token { kind: '.', .. }) = self.toks.peek() {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
let (num, count) = self.peek_whole_number();
|
||||||
|
if count == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
peek_counter += count;
|
||||||
|
buf.push_str(&num);
|
||||||
|
} else {
|
||||||
|
self.toks.move_cursor_back().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = match self.toks.peek() {
|
||||||
|
Some(tok) => tok,
|
||||||
|
None => return Ok(Some((buf, peek_counter))),
|
||||||
|
};
|
||||||
|
|
||||||
|
match next.kind {
|
||||||
|
'a'..='z' | 'A'..='Z' | '-' | '_' | '\\' => {
|
||||||
|
let unit = peek_ident_no_interpolation(self.toks, true, self.span_before)?.node;
|
||||||
|
|
||||||
|
buf.push_str(&unit);
|
||||||
|
peek_counter += unit.chars().count();
|
||||||
|
}
|
||||||
|
'%' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
peek_counter += 1;
|
||||||
|
buf.push('%');
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some((buf, peek_counter)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_whole_number(&mut self) -> (String, usize) {
|
||||||
|
let mut buf = String::new();
|
||||||
|
let mut peek_counter = 0;
|
||||||
|
while let Some(tok) = self.toks.peek() {
|
||||||
|
if tok.kind.is_ascii_digit() {
|
||||||
|
buf.push(tok.kind);
|
||||||
|
peek_counter += 1;
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
} else {
|
||||||
|
return (buf, peek_counter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(buf, peek_counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_parse_min_max(
|
||||||
|
&mut self,
|
||||||
|
fn_name: &str,
|
||||||
|
allow_comma: bool,
|
||||||
|
) -> SassResult<Option<(String, usize)>> {
|
||||||
|
let mut buf = if allow_comma {
|
||||||
|
format!("{}(", fn_name)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
let mut peek_counter = 0;
|
||||||
|
peek_counter += peek_whitespace(self.toks);
|
||||||
|
while let Some(tok) = self.toks.peek() {
|
||||||
|
let kind = tok.kind;
|
||||||
|
peek_counter += 1;
|
||||||
|
match kind {
|
||||||
|
'+' | '-' | '0'..='9' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
if let Some((number, count)) = self.peek_number()? {
|
||||||
|
buf.push(kind);
|
||||||
|
buf.push_str(&number);
|
||||||
|
peek_counter += count;
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'#' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
if let Some(Token { kind: '{', .. }) = self.toks.peek() {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
peek_counter += 1;
|
||||||
|
let (interpolation, count) = self.peek_interpolation()?;
|
||||||
|
peek_counter += count;
|
||||||
|
match interpolation.node {
|
||||||
|
Value::String(ref s, ..) => buf.push_str(s),
|
||||||
|
v => buf.push_str(v.to_css_string(interpolation.span)?.borrow()),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'c' | 'C' => {
|
||||||
|
if let Some((name, additional_peek_count)) =
|
||||||
|
self.try_parse_min_max_function("calc")?
|
||||||
|
{
|
||||||
|
peek_counter += additional_peek_count;
|
||||||
|
buf.push_str(&name);
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'e' | 'E' => {
|
||||||
|
if let Some((name, additional_peek_count)) =
|
||||||
|
self.try_parse_min_max_function("env")?
|
||||||
|
{
|
||||||
|
peek_counter += additional_peek_count;
|
||||||
|
buf.push_str(&name);
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'v' | 'V' => {
|
||||||
|
if let Some((name, additional_peek_count)) =
|
||||||
|
self.try_parse_min_max_function("var")?
|
||||||
|
{
|
||||||
|
peek_counter += additional_peek_count;
|
||||||
|
buf.push_str(&name);
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'(' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
buf.push('(');
|
||||||
|
if let Some((val, len)) = self.try_parse_min_max(fn_name, false)? {
|
||||||
|
buf.push_str(&val);
|
||||||
|
peek_counter += len;
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'm' | 'M' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
match self.toks.peek() {
|
||||||
|
Some(Token { kind: 'i', .. }) | Some(Token { kind: 'I', .. }) => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
if !matches!(self.toks.peek(), Some(Token { kind: 'n', .. }) | Some(Token { kind: 'N', .. }))
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
buf.push_str("min(")
|
||||||
|
}
|
||||||
|
Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
if !matches!(self.toks.peek(), Some(Token { kind: 'x', .. }) | Some(Token { kind: 'X', .. }))
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
buf.push_str("max(")
|
||||||
|
}
|
||||||
|
_ => return Ok(None),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
|
||||||
|
if !matches!(self.toks.peek(), Some(Token { kind: '(', .. })) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
peek_counter += 1;
|
||||||
|
|
||||||
|
if let Some((val, len)) = self.try_parse_min_max(fn_name, false)? {
|
||||||
|
buf.push_str(&val);
|
||||||
|
peek_counter += len;
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Ok(None),
|
||||||
|
}
|
||||||
|
|
||||||
|
peek_counter += peek_whitespace(self.toks);
|
||||||
|
|
||||||
|
let next = match self.toks.peek() {
|
||||||
|
Some(tok) => tok,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
match next.kind {
|
||||||
|
')' => {
|
||||||
|
peek_counter += 1;
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
buf.push(')');
|
||||||
|
return Ok(Some((buf, peek_counter)));
|
||||||
|
}
|
||||||
|
'+' | '-' | '*' | '/' => {
|
||||||
|
buf.push(' ');
|
||||||
|
buf.push(next.kind);
|
||||||
|
buf.push(' ');
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
}
|
||||||
|
',' => {
|
||||||
|
if !allow_comma {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
buf.push(',');
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
_ => return Ok(None),
|
||||||
|
}
|
||||||
|
|
||||||
|
peek_counter += peek_whitespace(self.toks);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some((buf, peek_counter)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code, unused_mut, unused_variables, unused_assignments)]
|
||||||
|
fn try_parse_min_max_function(
|
||||||
|
&mut self,
|
||||||
|
fn_name: &'static str,
|
||||||
|
) -> SassResult<Option<(String, usize)>> {
|
||||||
|
let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?.node;
|
||||||
|
let mut peek_counter = ident.chars().count();
|
||||||
|
ident.make_ascii_lowercase();
|
||||||
|
if ident != fn_name {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if !matches!(self.toks.peek(), Some(Token { kind: '(', .. })) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
ident.push('(');
|
||||||
|
peek_counter += 1;
|
||||||
|
todo!("special functions inside `min()` or `max()`")
|
||||||
|
}
|
||||||
|
|
||||||
fn peek_interpolation(&mut self) -> SassResult<(Spanned<Value>, usize)> {
|
fn peek_interpolation(&mut self) -> SassResult<(Spanned<Value>, usize)> {
|
||||||
let vec = peek_until_closing_curly_brace(self.toks)?;
|
let vec = peek_until_closing_curly_brace(self.toks)?;
|
||||||
let peek_counter = vec.len();
|
let peek_counter = vec.len();
|
||||||
|
104
tests/min-max.rs
Normal file
104
tests/min-max.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
test!(
|
||||||
|
min_not_evaluated_units_percent,
|
||||||
|
"a {\n color: min(1%, 2%);\n}\n",
|
||||||
|
"a {\n color: min(1%, 2%);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
min_not_evaluated_units_px,
|
||||||
|
"a {\n color: min(1px, 2px);\n}\n",
|
||||||
|
"a {\n color: min(1px, 2px);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
min_not_evaluated_no_units,
|
||||||
|
"a {\n color: min(1, 2);\n}\n",
|
||||||
|
"a {\n color: min(1, 2);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
min_not_evaluated_incompatible_units,
|
||||||
|
"a {\n color: min(1%, 2vh);\n}\n",
|
||||||
|
"a {\n color: min(1%, 2vh);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
min_not_evaluated_interpolation,
|
||||||
|
"$a: 1%;\n$b: 2%;\na {\n color: min(#{$a}, #{$b});;\n}\n",
|
||||||
|
"a {\n color: min(1%, 2%);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
min_evaluated_variable_units_percent,
|
||||||
|
"$a: 1%;\n$b: 2%;\na {\n color: min($a, $b);\n}\n",
|
||||||
|
"a {\n color: 1%;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
min_evaluated_variable_units_px,
|
||||||
|
"$a: 1px;\n$b: 2px;\na {\n color: min($a, $b);\n}\n",
|
||||||
|
"a {\n color: 1px;\n}\n"
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
min_arg_of_incorrect_type,
|
||||||
|
"$a: 1px;\n$b: 2px;\na {\n color: min($a, $b, foo);\n}\n", "Error: foo is not a number."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
min_too_few_args,
|
||||||
|
"a {\n color: min();\n}\n", "Error: At least one argument must be passed."
|
||||||
|
);
|
||||||
|
// note: we explicitly have units in the opposite order of `dart-sass`.
|
||||||
|
// see https://github.com/sass/dart-sass/issues/766
|
||||||
|
error!(
|
||||||
|
min_incompatible_units,
|
||||||
|
"$a: 1px;\n$b: 2%;\na {\n color: min($a, $b);\n}\n", "Error: Incompatible units px and %."
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
max_not_evaluated_units_percent,
|
||||||
|
"a {\n color: max(1%, 2%);\n}\n",
|
||||||
|
"a {\n color: max(1%, 2%);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
max_not_evaluated_units_px,
|
||||||
|
"a {\n color: max(1px, 2px);\n}\n",
|
||||||
|
"a {\n color: max(1px, 2px);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
max_not_evaluated_no_units,
|
||||||
|
"a {\n color: max(1, 2);\n}\n",
|
||||||
|
"a {\n color: max(1, 2);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
max_not_evaluated_incompatible_units,
|
||||||
|
"a {\n color: max(1%, 2vh);\n}\n",
|
||||||
|
"a {\n color: max(1%, 2vh);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
max_not_evaluated_interpolation,
|
||||||
|
"$a: 1%;\n$b: 2%;\na {\n color: max(#{$a}, #{$b});;\n}\n",
|
||||||
|
"a {\n color: max(1%, 2%);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
max_evaluated_variable_units_percent,
|
||||||
|
"$a: 1%;\n$b: 2%;\na {\n color: max($a, $b);\n}\n",
|
||||||
|
"a {\n color: 2%;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
max_evaluated_variable_units_px,
|
||||||
|
"$a: 1px;\n$b: 2px;\na {\n color: max($a, $b);\n}\n",
|
||||||
|
"a {\n color: 2px;\n}\n"
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
max_arg_of_incorrect_type,
|
||||||
|
"$a: 1px;\n$b: 2px;\na {\n color: max($a, $b, foo);\n}\n", "Error: foo is not a number."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
max_too_few_args,
|
||||||
|
"a {\n color: max();\n}\n", "Error: At least one argument must be passed."
|
||||||
|
);
|
||||||
|
// note: we explicitly have units in the opposite order of `dart-sass`.
|
||||||
|
// see https://github.com/sass/dart-sass/issues/766
|
||||||
|
error!(
|
||||||
|
max_incompatible_units,
|
||||||
|
"$a: 1px;\n$b: 2%;\na {\n color: max($a, $b);\n}\n", "Error: Incompatible units px and %."
|
||||||
|
);
|
||||||
|
// todo: special functions, min(calc(1), $b);
|
Loading…
x
Reference in New Issue
Block a user