improve code coverage

This commit is contained in:
connorskees 2023-01-21 23:00:53 +00:00
parent 5889859968
commit 40d2aa232a
17 changed files with 289 additions and 78 deletions

View File

@ -11,32 +11,20 @@ pub(crate) fn length(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRes
pub(crate) fn nth(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(2)?;
let mut list = args.get_err(0, "list")?.as_list();
let (n, unit) = match args.get_err(1, "n")? {
Value::Dimension(SassNumber {
num: n, unit: u, ..
}) if n.is_nan() => {
return Err((format!("$n: NaN{} is not an int.", u), args.span()).into())
}
Value::Dimension(SassNumber { num, unit, .. }) => (num, unit),
v => {
return Err((
format!("$n: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
let index = args
.get_err(1, "n")?
.assert_number_with_name("n", args.span())?;
if n.is_zero() {
if index.num.is_zero() {
return Err(("$n: List index may not be 0.", args.span()).into());
}
if n.abs() > Number::from(list.len()) {
if index.num.abs() > Number::from(list.len()) {
return Err((
format!(
"$n: Invalid index {}{} for a list with {} elements.",
n.inspect(),
unit,
index.num.inspect(),
index.unit,
list.len()
),
args.span(),
@ -44,12 +32,13 @@ pub(crate) fn nth(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult
.into());
}
Ok(list.remove(if n.is_positive() {
let index = n.assert_int_with_name("n", args.span())? - 1;
debug_assert!(index > -1);
index as usize
let index_int = index.assert_int_with_name("n", args.span())?;
Ok(list.remove(if index.num.is_positive() {
debug_assert!(index_int > 0);
index_int as usize - 1
} else {
list.len() - n.abs().assert_int_with_name("n", args.span())? as usize
list.len() - index_int.unsigned_abs() as usize
}))
}
@ -73,34 +62,24 @@ pub(crate) fn set_nth(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRe
Value::Map(m) => (m.as_list(), ListSeparator::Comma, Brackets::None),
v => (vec![v], ListSeparator::Undecided, Brackets::None),
};
let (n, unit) = match args.get_err(1, "n")? {
Value::Dimension(SassNumber {
num: n, unit: u, ..
}) if n.is_nan() => {
return Err((format!("$n: NaN{} is not an int.", u), args.span()).into())
}
Value::Dimension(SassNumber { num, unit, .. }) => (num, unit),
v => {
return Err((
format!("$n: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
let index = args
.get_err(1, "n")?
.assert_number_with_name("n", args.span())?;
if n.is_zero() {
if index.num.is_zero() {
return Err(("$n: List index may not be 0.", args.span()).into());
}
let index_int = index.assert_int_with_name("n", args.span())?;
let len = list.len();
if n.abs() > Number::from(len) {
if index.num.abs() > Number::from(len) {
return Err((
format!(
"$n: Invalid index {}{} for a list with {} elements.",
n.inspect(),
unit,
index.num.inspect(),
index.unit,
len
),
args.span(),
@ -108,16 +87,12 @@ pub(crate) fn set_nth(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRe
.into());
}
if n.is_decimal() {
return Err((format!("$n: {} is not an int.", n.inspect()), args.span()).into());
}
let val = args.get_err(2, "value")?;
if n.is_positive() {
list[n.assert_int_with_name("n", args.span())? as usize - 1] = val;
if index_int.is_positive() {
list[index_int as usize - 1] = val;
} else {
list[len - n.abs().assert_int_with_name("n", args.span())? as usize] = val;
list[len - index_int.unsigned_abs() as usize] = val;
}
Ok(Value::List(list, sep, brackets))

View File

@ -99,8 +99,9 @@ pub(crate) fn random(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRes
)));
}
let limit = limit.assert_number_with_name("limit", args.span())?.num;
let limit = limit.assert_number_with_name("limit", args.span())?;
let limit_int = limit.assert_int_with_name("limit", args.span())?;
let limit = limit.num;
if limit.is_one() {
return Ok(Value::Dimension(SassNumber::new_unitless(1.0)));

View File

@ -152,7 +152,7 @@ pub(crate) fn str_insert(mut args: ArgumentResult, visitor: &mut Visitor) -> Sas
.get_err(2, "index")?
.assert_number_with_name("index", span)?;
index.assert_no_units("index", span)?;
let index_int = index.num.assert_int_with_name("index", span)?;
let index_int = index.assert_int_with_name("index", span)?;
if s1.is_empty() {
return Ok(Value::String(substr, quotes));

View File

@ -309,10 +309,6 @@ pub(crate) trait StylesheetParser<'a>: BaseParser<'a> + Sized {
}
fn parse_at_root_query(&mut self) -> SassResult<Interpolation> {
if self.toks_mut().next_char_is('#') {
return self.parse_single_interpolation();
}
let mut buffer = Interpolation::new();
self.expect_char('(')?;
buffer.add_char('(');

View File

@ -116,21 +116,6 @@ impl Number {
}
}
pub fn assert_int_with_name(self, name: &'static str, span: Span) -> SassResult<i64> {
match fuzzy_as_int(self.0) {
Some(i) => Ok(i),
None => Err((
format!(
"${name}: {} is not an int.",
self.to_string(false),
name = name,
),
span,
)
.into()),
}
}
pub fn round(self) -> Self {
Self(self.0.round())
}
@ -147,10 +132,6 @@ impl Number {
Self(self.0.abs())
}
pub fn is_decimal(self) -> bool {
self.0.fract() != 0.0
}
pub fn clamp(self, min: f64, max: f64) -> Self {
Number(min.max(self.0.min(max)))
}

View File

@ -12,7 +12,7 @@ use crate::{
Options,
};
use super::Number;
use super::{fuzzy_as_int, Number};
#[derive(Debug, Clone)]
pub(crate) struct SassNumber {
@ -176,6 +176,21 @@ impl SassNumber {
self.assert_bounds_with_unit(name, min, max, &self.unit, span)
}
pub fn assert_int_with_name(&self, name: &'static str, span: Span) -> SassResult<i64> {
match fuzzy_as_int(self.num.0) {
Some(i) => Ok(i),
None => Err((
format!(
"${name}: {} is not an int.",
inspect_number(self, &Options::default(), span)?,
name = name,
),
span,
)
.into()),
}
}
pub fn assert_bounds_with_unit(
&self,
name: &str,

View File

@ -171,3 +171,5 @@ test!(
"a { /**/ }\n"
);
test!(silent_comment_as_child, "a {\n// silent\n}\n", "");
test!(single_hash_in_loud_comment, "/*#*/", "/*#*/\n");
error!(unclosed_loud_comment, "/*", "Error: expected more input.");

View File

@ -128,3 +128,21 @@ test!(
"a{color:0;color:0}",
grass::Options::default().style(grass::OutputStyle::Compressed)
);
test!(
color_can_be_three_hex,
"a {\n color: white;\n}\n",
"a{color:#fff}",
grass::Options::default().style(grass::OutputStyle::Compressed)
);
test!(
color_cant_be_three_hex_but_hex_is_shorter,
"a {\n color: aquamarine;\n}\n",
"a{color:#7fffd4}",
grass::Options::default().style(grass::OutputStyle::Compressed)
);
test!(
slash_list,
"@use 'sass:list'; a {\n color: list.slash(a, b, c);\n}\n",
"a{color:a/b/c}",
grass::Options::default().style(grass::OutputStyle::Compressed)
);

View File

@ -581,6 +581,15 @@ test!(
r#"@import "a" b c d(e) supports(f: g) h i j(k) l m (n: o), (p: q);"#,
"@import \"a\" b c d(e) supports(f: g) h i j(k) l m (n: o), (p: q);\n"
);
test!(
import_supports_condition_with_paren,
r#"@import "a" supports(a( /**/ ));"#,
"@import \"a\" supports(a( /**/ ));\n"
);
error!(
import_supports_condition_non_ident,
r#"@import "a" supports(1a)"#, "Error: expected \":\"."
);
error!(unclosed_single_quote, r#"@import '"#, "Error: Expected '.");
error!(unclosed_double_quote, r#"@import ""#, "Error: Expected \".");
error!(

View File

@ -86,6 +86,21 @@ test!(
"a {\n color: inspect([(1, 2), (3, 4)]);\n}\n",
"a {\n color: [(1, 2), (3, 4)];\n}\n"
);
test!(
inspect_map_with_bracketed_key_and_value,
"a {\n color: inspect(([a, b]: [c, d]));\n}\n",
"a {\n color: ([a, b]: [c, d]);\n}\n"
);
test!(
inspect_map_with_comma_separated_key_and_value,
"a {\n color: inspect(((a, b): (c, d)));\n}\n",
"a {\n color: ((a, b): (c, d));\n}\n"
);
test!(
inspect_slash_list_singleton,
"a {\n color: inspect(join((a,), (), slash));\n}\n",
"a {\n color: (a/);\n}\n"
);
test!(
inspect_empty_list,
"a {\n color: inspect(())\n}\n",

View File

@ -528,3 +528,110 @@ error!(
empty_list_is_invalid,
"a {\n color: ();\n}\n", "Error: () isn't a valid CSS value."
);
test!(
is_bracketed_empty_bracket_list,
"a {\n color: is-bracketed([]);\n}\n",
"a {\n color: true;\n}\n"
);
test!(
is_bracketed_bracket_list_containing_space_list,
"a {\n color: is-bracketed([a b]);\n}\n",
"a {\n color: true;\n}\n"
);
test!(
is_bracketed_bracket_list_containing_comma_list,
"a {\n color: is-bracketed([a, b]);\n}\n",
"a {\n color: true;\n}\n"
);
test!(
is_bracketed_space_list,
"a {\n color: is-bracketed(a b);\n}\n",
"a {\n color: false;\n}\n"
);
test!(
is_bracketed_number,
"a {\n color: is-bracketed(1);\n}\n",
"a {\n color: false;\n}\n"
);
error!(
is_bracketed_two_args,
"a {\n color: is-bracketed(a, b);\n}\n", "Error: Only 1 argument allowed, but 2 were passed."
);
error!(
nth_non_numeric_index,
"a {\n color: nth(a b, c);\n}\n", "Error: $n: c is not a number."
);
error!(
set_nth_non_numeric_index,
"a {\n color: set-nth(a b, c, d);\n}\n", "Error: $n: c is not a number."
);
error!(
set_nth_index_zero,
"a {\n color: set-nth(a b, 0, d);\n}\n", "Error: $n: List index may not be 0."
);
error!(
set_nth_index_decimal,
"a {\n color: set-nth(a b, 1.5, d);\n}\n", "Error: $n: 1.5 is not an int."
);
error!(
set_nth_index_negative_outside_range,
"a {\n color: set-nth(a b, -3, d);\n}\n",
"Error: $n: Invalid index -3 for a list with 2 elements."
);
test!(
set_nth_index_negative_inside_range,
"a {\n color: set-nth(a b, -1, d);\n}\n",
"a {\n color: a d;\n}\n"
);
error!(
set_nth_index_infinity,
"a {\n color: set-nth(a b, 1/0, d);\n}\n", "Error: $n: Infinity is not an int."
);
error!(
set_nth_index_negative_infinity,
"a {\n color: set-nth(a b, -1/0, d);\n}\n", "Error: $n: -Infinity is not an int."
);
error!(
set_nth_decimal_outside_range,
"a {\n color: set-nth(a b, 8.5, d);\n}\n", "Error: $n: 8.5 is not an int."
);
test!(
append_with_slash_separator,
"a {\n color: append(a b, c, slash);\n}\n",
"a {\n color: a / b / c;\n}\n"
);
error!(
append_invalid_separator,
"a {\n color: append(a b, c, foo);\n}\n",
"Error: $separator: Must be \"space\", \"comma\", \"slash\", or \"auto\"."
);
test!(
join_with_slash_separator,
"a {\n color: join(a, b, slash);\n}\n",
"a {\n color: a / b;\n}\n"
);
error!(
join_invalid_separator,
"a {\n color: join(a b, c, foo);\n}\n",
"Error: $separator: Must be \"space\", \"comma\", \"slash\", or \"auto\"."
);
error!(
join_invalid_separator_non_string,
"a {\n color: join(a b, c, 1);\n}\n", "Error: $separator: 1 is not a string."
);
test!(
join_bracketed_true,
"a {\n color: join(a, b, space, true);\n}\n",
"a {\n color: [a b];\n}\n"
);
test!(
join_bracketed_truthy,
"a {\n color: join(a, b, space, a);\n}\n",
"a {\n color: [a b];\n}\n"
);
test!(
join_bracketed_falsey,
"a {\n color: join(a, b, space, null);\n}\n",
"a {\n color: a b;\n}\n"
);
test!(zip_no_args, "a {\n color: zip();\n}\n", "");

View File

@ -115,7 +115,6 @@ test!(
"a {\n color: NaNdeg;\n}\n"
);
error!(
#[ignore = "we dont emit units"]
unitful_nan_random,
"@use \"sass:math\";\na {\n color: random(math.acos(2));\n}\n",
"Error: $limit: NaNdeg is not an int."

View File

@ -221,3 +221,31 @@ test!(
"@supports (foo) {\n a {\n color: red;\n }\n}\n",
grass::Options::default().input_syntax(InputSyntax::Css)
);
test!(
custom_property,
"a {
--foo: /* */;
}",
"a {\n --foo: /* */;\n}\n",
grass::Options::default().input_syntax(InputSyntax::Css)
);
error!(
single_nested_property,
"a {
b: {
c: d;
}
}",
"Error: Nested declarations aren't allowed in plain CSS.",
grass::Options::default().input_syntax(InputSyntax::Css)
);
error!(
single_nested_property_with_expression,
"a {
b: 2 {
c: d;
}
}",
"Error: Nested declarations aren't allowed in plain CSS.",
grass::Options::default().input_syntax(InputSyntax::Css)
);

View File

@ -103,6 +103,20 @@ a
"a + b {\n color: red;\n}\n",
grass::Options::default().input_syntax(InputSyntax::Sass)
);
test!(
if_else_if_else,
r#"
a
@if false
color: red
@else if false
color: blue
@else
color: orange
"#,
"a {\n color: orange;\n}\n",
grass::Options::default().input_syntax(InputSyntax::Sass)
);
error!(
multiline_comment_in_value_position,
r#"

View File

@ -1008,6 +1008,35 @@ error!(
child_selector_starts_with_forward_slash,
"a { /b { } }", "Error: expected selector."
);
test!(
selector_module_exists,
"@use 'sass:selector';
a {
color: selector.parse('a');
}
",
"a {\n color: a;\n}\n"
);
test!(
selector_contains_url_without_parens,
"a url {\n color: red;\n}\n",
"a url {\n color: red;\n}\n"
);
test!(
selector_contains_capital_u,
"a U {\n color: red;\n}\n",
"a U {\n color: red;\n}\n"
);
test!(
selector_contains_url_with_quoted_string_inside_parens,
"a :url(\"foo.css\") {\n color: red;\n}\n",
"a :url(\"foo.css\") {\n color: red;\n}\n"
);
test!(
selector_contains_url_with_hash_inside_parens,
"a :url(#) {\n color: red;\n}\n",
"a :url(#) {\n color: red;\n}\n"
);
// todo:
// [attr=url] {

View File

@ -299,3 +299,12 @@ test!(
}",
""
);
test!(
string_module_exists,
"@use 'sass:string';
a {
color: string.to-lower-case('AAA');
}
",
"a {\n color: \"aaa\";\n}\n"
);

View File

@ -202,7 +202,6 @@ test!(
"a {\n color /**/ : red;\n}\n",
"a {\n color: red;\n}\n"
);
// todo: many other strange edge cases like `.style: val` (dealing with ambiguity is hard for very little gain)
test!(
style_begins_with_asterisk_without_whitespace,
"a {\n *zoom: 1;\n}\n",
@ -231,6 +230,20 @@ test!(
}",
"a {\n position: relative;\n}\nc {\n white-space: nowrap;\n}\n"
);
test!(
symbol_before_property_name_hacks,
"a {
.color: foo;
#color: foo;
:color: foo;
*color: foo;
.--color: foo;
#--color: foo;
:--color: foo;
*--color: foo;
}",
"a {\n .color: foo;\n #color: foo;\n :color: foo;\n *color: foo;\n .--color: foo;\n #--color: foo;\n :--color: foo;\n *--color: foo;\n}\n"
);
error!(
media_inside_nested_declaration,
"a {