From 40d2aa232a1385c7a2eba933d8dd19ffc6db7d9d Mon Sep 17 00:00:00 2001 From: connorskees Date: Sat, 21 Jan 2023 23:00:53 +0000 Subject: [PATCH] improve code coverage --- crates/compiler/src/builtin/functions/list.rs | 75 ++++-------- crates/compiler/src/builtin/functions/math.rs | 3 +- .../compiler/src/builtin/functions/string.rs | 2 +- crates/compiler/src/parse/stylesheet.rs | 4 - crates/compiler/src/value/number.rs | 19 ---- crates/compiler/src/value/sass_number.rs | 17 ++- crates/lib/tests/comments.rs | 2 + crates/lib/tests/compressed.rs | 18 +++ crates/lib/tests/imports.rs | 9 ++ crates/lib/tests/inspect.rs | 15 +++ crates/lib/tests/list.rs | 107 ++++++++++++++++++ crates/lib/tests/nan.rs | 1 - crates/lib/tests/plain-css.rs | 28 +++++ crates/lib/tests/sass.rs | 14 +++ crates/lib/tests/selectors.rs | 29 +++++ crates/lib/tests/strings.rs | 9 ++ crates/lib/tests/styles.rs | 15 ++- 17 files changed, 289 insertions(+), 78 deletions(-) diff --git a/crates/compiler/src/builtin/functions/list.rs b/crates/compiler/src/builtin/functions/list.rs index b10fd6a..ded1780 100644 --- a/crates/compiler/src/builtin/functions/list.rs +++ b/crates/compiler/src/builtin/functions/list.rs @@ -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 { 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)) diff --git a/crates/compiler/src/builtin/functions/math.rs b/crates/compiler/src/builtin/functions/math.rs index 44fd698..932c7d3 100644 --- a/crates/compiler/src/builtin/functions/math.rs +++ b/crates/compiler/src/builtin/functions/math.rs @@ -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))); diff --git a/crates/compiler/src/builtin/functions/string.rs b/crates/compiler/src/builtin/functions/string.rs index b4a909e..3606415 100644 --- a/crates/compiler/src/builtin/functions/string.rs +++ b/crates/compiler/src/builtin/functions/string.rs @@ -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)); diff --git a/crates/compiler/src/parse/stylesheet.rs b/crates/compiler/src/parse/stylesheet.rs index 8c3a1a8..88b01d1 100644 --- a/crates/compiler/src/parse/stylesheet.rs +++ b/crates/compiler/src/parse/stylesheet.rs @@ -309,10 +309,6 @@ pub(crate) trait StylesheetParser<'a>: BaseParser<'a> + Sized { } fn parse_at_root_query(&mut self) -> SassResult { - if self.toks_mut().next_char_is('#') { - return self.parse_single_interpolation(); - } - let mut buffer = Interpolation::new(); self.expect_char('(')?; buffer.add_char('('); diff --git a/crates/compiler/src/value/number.rs b/crates/compiler/src/value/number.rs index 637f27b..7017cef 100644 --- a/crates/compiler/src/value/number.rs +++ b/crates/compiler/src/value/number.rs @@ -116,21 +116,6 @@ impl Number { } } - pub fn assert_int_with_name(self, name: &'static str, span: Span) -> SassResult { - 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))) } diff --git a/crates/compiler/src/value/sass_number.rs b/crates/compiler/src/value/sass_number.rs index cd06f8f..c915586 100644 --- a/crates/compiler/src/value/sass_number.rs +++ b/crates/compiler/src/value/sass_number.rs @@ -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 { + 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, diff --git a/crates/lib/tests/comments.rs b/crates/lib/tests/comments.rs index 4785bfa..47103f5 100644 --- a/crates/lib/tests/comments.rs +++ b/crates/lib/tests/comments.rs @@ -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."); diff --git a/crates/lib/tests/compressed.rs b/crates/lib/tests/compressed.rs index 8f32de9..975e95d 100644 --- a/crates/lib/tests/compressed.rs +++ b/crates/lib/tests/compressed.rs @@ -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) +); diff --git a/crates/lib/tests/imports.rs b/crates/lib/tests/imports.rs index d13d8ba..9a71dd4 100644 --- a/crates/lib/tests/imports.rs +++ b/crates/lib/tests/imports.rs @@ -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!( diff --git a/crates/lib/tests/inspect.rs b/crates/lib/tests/inspect.rs index 4c02dbd..5e28ad0 100644 --- a/crates/lib/tests/inspect.rs +++ b/crates/lib/tests/inspect.rs @@ -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", diff --git a/crates/lib/tests/list.rs b/crates/lib/tests/list.rs index c68a358..5064f2b 100644 --- a/crates/lib/tests/list.rs +++ b/crates/lib/tests/list.rs @@ -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", ""); diff --git a/crates/lib/tests/nan.rs b/crates/lib/tests/nan.rs index 10b8b1a..c1aa447 100644 --- a/crates/lib/tests/nan.rs +++ b/crates/lib/tests/nan.rs @@ -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." diff --git a/crates/lib/tests/plain-css.rs b/crates/lib/tests/plain-css.rs index 069def7..aa70290 100644 --- a/crates/lib/tests/plain-css.rs +++ b/crates/lib/tests/plain-css.rs @@ -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) +); diff --git a/crates/lib/tests/sass.rs b/crates/lib/tests/sass.rs index 71be04f..d6e5075 100644 --- a/crates/lib/tests/sass.rs +++ b/crates/lib/tests/sass.rs @@ -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#" diff --git a/crates/lib/tests/selectors.rs b/crates/lib/tests/selectors.rs index 7bb84fb..94e829f 100644 --- a/crates/lib/tests/selectors.rs +++ b/crates/lib/tests/selectors.rs @@ -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] { diff --git a/crates/lib/tests/strings.rs b/crates/lib/tests/strings.rs index 259cb0b..b4f1862 100644 --- a/crates/lib/tests/strings.rs +++ b/crates/lib/tests/strings.rs @@ -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" +); diff --git a/crates/lib/tests/styles.rs b/crates/lib/tests/styles.rs index 79e1e7d..8119d83 100644 --- a/crates/lib/tests/styles.rs +++ b/crates/lib/tests/styles.rs @@ -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 {