increase code coverage

This commit is contained in:
Connor Skees 2022-12-28 21:42:58 -05:00
parent 6cd208f41d
commit 743ad7a340
22 changed files with 344 additions and 101 deletions

View File

@ -263,7 +263,7 @@ impl Environment {
for name in (*self.scopes.global_variables()).borrow().keys() {
if (*module).borrow().var_exists(*name) {
return Err((
format!("This module and the new module both define a variable named \"{}\".", name = name)
format!("This module and the new module both define a variable named \"{name}\".", name = name)
, span).into());
}
}

View File

@ -60,7 +60,7 @@ impl<'a, 'c, P: StylesheetParser<'a>> ValueParser<'a, 'c, P> {
}
if value_parser.inside_bracketed_list {
let start = parser.toks().cursor();
let bracket_start = parser.toks().cursor();
parser.expect_char('[')?;
parser.whitespace()?;
@ -71,14 +71,12 @@ impl<'a, 'c, P: StylesheetParser<'a>> ValueParser<'a, 'c, P> {
separator: ListSeparator::Undecided,
brackets: Brackets::Bracketed,
})
.span(parser.toks_mut().span_from(start)));
.span(parser.toks_mut().span_from(bracket_start)));
}
Some(start)
} else {
None
};
value_parser.start = parser.toks().cursor();
value_parser.single_expression = Some(value_parser.parse_single_expression(parser)?);
let mut value = value_parser.parse_value(parser)?;
@ -392,6 +390,7 @@ impl<'a, 'c, P: StylesheetParser<'a>> ValueParser<'a, 'c, P> {
self.reset_state(parser)?;
continue;
}
// todo: does this branch ever get hit
}
if self.single_expression.is_none() {
@ -400,7 +399,7 @@ impl<'a, 'c, P: StylesheetParser<'a>> ValueParser<'a, 'c, P> {
self.resolve_space_expressions(parser)?;
// [resolveSpaceExpressions can modify [singleExpression_], but it
// [resolveSpaceExpressions] can modify [singleExpression_], but it
// can't set it to null`.
self.comma_expressions
.get_or_insert_with(Default::default)
@ -1368,62 +1367,6 @@ impl<'a, 'c, P: StylesheetParser<'a>> ValueParser<'a, 'c, P> {
.span(span))
}
fn try_parse_url_contents(
parser: &mut P,
name: Option<String>,
) -> SassResult<Option<Interpolation>> {
let start = parser.toks().cursor();
if !parser.scan_char('(') {
return Ok(None);
}
parser.whitespace_without_comments();
// Match Ruby Sass's behavior: parse a raw URL() if possible, and if not
// backtrack and re-parse as a function expression.
let mut buffer = Interpolation::new();
buffer.add_string(name.unwrap_or_else(|| "url".to_owned()));
buffer.add_char('(');
while let Some(next) = parser.toks().peek() {
match next.kind {
'\\' => {
buffer.add_string(parser.parse_escape(false)?);
}
'!' | '%' | '&' | '*'..='~' | '\u{80}'..=char::MAX => {
parser.toks_mut().next();
buffer.add_char(next.kind);
}
'#' => {
if matches!(parser.toks().peek_n(1), Some(Token { kind: '{', .. })) {
buffer.add_interpolation(parser.parse_single_interpolation()?);
} else {
parser.toks_mut().next();
buffer.add_char(next.kind);
}
}
')' => {
parser.toks_mut().next();
buffer.add_char(next.kind);
return Ok(Some(buffer));
}
' ' | '\t' | '\n' | '\r' => {
parser.whitespace_without_comments();
if !parser.toks().next_char_is(')') {
break;
}
}
_ => break,
}
}
parser.toks_mut().set_cursor(start);
Ok(None)
}
pub(crate) fn try_parse_special_function(
parser: &mut P,
name: &str,
@ -1466,15 +1409,13 @@ impl<'a, 'c, P: StylesheetParser<'a>> ValueParser<'a, 'c, P> {
buffer.add_char('(');
}
"url" => {
return Ok(
ValueParser::try_parse_url_contents(parser, None)?.map(|contents| {
AstExpr::String(
StringExpr(contents, QuoteKind::None),
parser.toks_mut().span_from(start),
)
.span(parser.toks_mut().span_from(start))
}),
)
return Ok(parser.try_url_contents(None)?.map(|contents| {
AstExpr::String(
StringExpr(contents, QuoteKind::None),
parser.toks_mut().span_from(start),
)
.span(parser.toks_mut().span_from(start))
}))
}
_ => return Ok(None),
}

View File

@ -21,6 +21,6 @@ pub(crate) fn as_hex(c: char) -> u32 {
'0'..='9' => c as u32 - '0' as u32,
'A'..='F' => 10 + c as u32 - 'A' as u32,
'a'..='f' => 10 + c as u32 - 'a' as u32,
_ => panic!(),
_ => unreachable!(),
}
}

View File

@ -285,7 +285,7 @@ impl<V: fmt::Debug + Clone> MapView for MergedMapView<V> {
}
}
panic!("New entries may not be added to MergedMapView")
unreachable!("New entries may not be added to MergedMapView")
}
fn keys(&self) -> Vec<Identifier> {

View File

@ -55,26 +55,8 @@ impl PartialEq for Value {
Value::String(s2, ..) => s1 == s2,
_ => false,
},
Value::Dimension(SassNumber {
num: n,
unit,
as_slash: _,
}) => match other {
Value::Dimension(SassNumber {
num: n2,
unit: unit2,
as_slash: _,
}) => {
if !unit.comparable(unit2) {
return false;
}
if (*unit2 == Unit::None || *unit == Unit::None) && unit != unit2 {
return false;
}
*n == n2.convert(unit2, unit)
}
Value::Dimension(n1) => match other {
Value::Dimension(n2) => n1 == n2,
_ => false,
},
Value::List(list1, sep1, brackets1) => match other {

View File

@ -189,10 +189,20 @@ impl SassNumber {
impl PartialEq for SassNumber {
fn eq(&self, other: &Self) -> bool {
self.num == other.num && self.unit == other.unit
if !self.unit.comparable(&other.unit) {
return false;
}
if (other.unit == Unit::None || self.unit == Unit::None) && self.unit != other.unit {
return false;
}
self.num == other.num.convert(&other.unit, &self.unit)
}
}
impl Eq for SassNumber {}
impl Add<SassNumber> for SassNumber {
type Output = SassNumber;
fn add(self, rhs: SassNumber) -> Self::Output {
@ -285,5 +295,3 @@ impl Div<SassNumber> for SassNumber {
self.multiply_units(self.num.0 / rhs.num.0, rhs.unit.invert())
}
}
impl Eq for SassNumber {}

View File

@ -262,6 +262,15 @@ test!(
}",
"@unknown {\n .bar {\n a: b;\n }\n}\n"
);
test!(
query_begins_with_interpolation,
"a {
@at-root (#{wi}th: rule) {
color: red;
}
}",
"a {\n color: red;\n}\n"
);
error!(
missing_closing_curly_brace,
"@at-root {", "Error: expected \"}\"."

View File

@ -635,3 +635,26 @@ error!(
single_arg_saturate_expects_number,
"a {\n color: saturate(red);\n}\n", "Error: $amount: red is not a number."
);
error!(
hex_color_starts_with_number_non_hex_digit_at_position_2,
"a {\n color: #0zz;\n}\n", "Error: Expected hex digit."
);
error!(
hex_color_starts_with_number_non_hex_digit_at_position_3,
"a {\n color: #00z;\n}\n", "Error: Expected hex digit."
);
test!(
hex_color_starts_with_number_non_hex_digit_at_position_4,
"a {\n color: #000z;\n}\n",
"a {\n color: #000 z;\n}\n"
);
test!(
#[ignore = "we don't emit 4 character hex colors correctly"]
hex_color_starts_with_number_non_hex_digit_at_position_5,
"a {\n color: #0000z;\n}\n",
"a {\n color: rgba(0, 0, 0, 0) z;\n}\n"
);
error!(
hex_color_starts_with_number_non_hex_digit_at_position_6,
"a {\n color: #00000z;\n}\n", "Error: Expected hex digit."
);

View File

@ -170,3 +170,4 @@ test!(
"a {/**/}",
"a { /**/ }\n"
);
test!(silent_comment_as_child, "a {\n// silent\n}\n", "");

View File

@ -267,3 +267,10 @@ test!(
}",
"a {\n color: true;\n}\n"
);
test!(
calculation_equality_converts_units,
"a {
color: calc(1in + 1rem) == calc(2.54cm + 1rem);
}",
"a {\n color: true;\n}\n"
);

View File

@ -273,3 +273,7 @@ error!(
nothing_after_dot_in_value_preceded_by_minus_sign,
"a { color: -.", "Error: Expected digit."
);
error!(
nothing_after_bang_in_space_separated_list,
"a { color: a !", r#"Error: Expected "important"."#
);

View File

@ -409,6 +409,13 @@ test!(
}",
"a {\n color: before;\n}\n"
);
test!(
can_parse_module_variable_declaration,
"@function foo() {
foo.$bar: red;
}",
""
);
error!(
function_no_return,
"@function foo() {}

View File

@ -523,6 +523,72 @@ test!(
);
error!(unclosed_single_quote, r#"@import '"#, "Error: Expected '.");
error!(unclosed_double_quote, r#"@import ""#, "Error: Expected \".");
error!(
dynamic_disallowed_inside_if,
r#"@if true {
@import "foo";
}"#,
"Error: This at-rule is not allowed here."
);
error!(
dynamic_disallowed_inside_while,
r#"@while true {
@import "foo";
}"#,
"Error: This at-rule is not allowed here."
);
error!(
dynamic_disallowed_inside_for,
r#"@for $i from 0 through 1 {
@import "foo";
}"#,
"Error: This at-rule is not allowed here."
);
error!(
dynamic_disallowed_inside_each,
r#"@each $i in a {
@import "foo";
}"#,
"Error: This at-rule is not allowed here."
);
test!(
static_allowed_inside_if,
r#"@if true {
@import "foo.css";
}"#,
"@import \"foo.css\";\n"
);
test!(
static_allowed_inside_while,
r#"
$a: 0;
@while $a == 0 {
@import "foo.css";
$a: 1;
}"#,
"@import \"foo.css\";\n"
);
test!(
static_allowed_inside_for,
r#"@for $i from 0 to 1 {
@import "foo.css";
}"#,
"@import \"foo.css\";\n"
);
test!(
static_allowed_inside_each,
r#"@each $i in a {
@import "foo.css";
}"#,
"@import \"foo.css\";\n"
);
error!(
dynamic_disallowed_inside_mixin,
r#"@mixin foo {
@import "foo";
}"#,
"Error: This at-rule is not allowed here."
);
// todo: edge case tests for plain css imports moved to top
// todo: test for calling paths, e.g. `grass b\index.scss`

View File

@ -412,6 +412,26 @@ test!(
"a {\n color: [null];\n}\n",
"a {\n color: [];\n}\n"
);
test!(
space_separated_bracketed_list_in_parens,
"a {\n color: ([a b]);\n}\n",
"a {\n color: [a b];\n}\n"
);
test!(
does_not_eval_division_inside_space_separated_bracketed_list_in_parens,
"a {\n color: ([1/2 1/2]);\n}\n",
"a {\n color: [1/2 1/2];\n}\n"
);
test!(
comma_separated_bracketed_list_in_parens,
"a {\n color: ([a, b]);\n}\n",
"a {\n color: [a, b];\n}\n"
);
test!(
does_not_eval_division_inside_comma_separated_bracketed_list_in_parens,
"a {\n color: ([1/2, 1/2]);\n}\n",
"a {\n color: [1/2, 1/2];\n}\n"
);
test!(
comma_separated_list_has_element_beginning_with_capital_A,
"a {\n color: a, A, \"Noto Color Emoji\";\n}\n",

View File

@ -280,6 +280,27 @@ test!(
"a {\n color: (a: b)==(a: c);\n}\n",
"a {\n color: false;\n}\n"
);
test!(
important_as_key,
"a {\n color: inspect((a: b, !important: c));\n}\n",
"a {\n color: (a: b, !important: c);\n}\n"
);
error!(
bang_identifier_not_important_as_key,
"a {\n color: inspect((a: b, !a: c));\n}\n", r#"Error: expected ")"."#
);
error!(
bang_identifier_not_important_but_starts_with_i_as_key,
"a {\n color: inspect((a: b, !i: c));\n}\n", r#"Error: Expected "important"."#
);
error!(
bang_identifier_not_important_ascii_whitespace_as_key,
"a {\n color: inspect((a: b, ! : c));\n}\n", r#"Error: Expected "important"."#
);
error!(
bang_identifier_not_important_loud_comment_as_key,
"a {\n color: inspect((a: b, !/**/: c));\n}\n", r#"Error: expected ")"."#
);
test!(
empty_with_single_line_comments,
"$foo: (\n \n // :/a.b\n \n );
@ -314,3 +335,7 @@ error!(
denies_comma_separated_list_without_parens_as_key,
"$map: (a: 1, b, c, d: e);", "Error: expected \":\"."
);
error!(
nothing_after_first_comma,
"$map: (a: b,", "Error: expected \")\"."
);

View File

@ -570,6 +570,17 @@ test!(
}"#,
"@media (min-width: \\0 ) {\n a {\n color: red;\n }\n}\n"
);
test!(
simple_unmergeable,
"a {
@media a {
@media b {
color: red;
}
}
}",
""
);
error!(
media_query_has_quoted_closing_paren,
r#"@media ('a)'w) {

View File

@ -35,6 +35,32 @@ test!(
"a {\n color: 1 or 2;\n}\n",
grass::Options::default().input_syntax(InputSyntax::Css)
);
test!(
simple_calculation,
"a {
color: calc(1 + 1);
}",
"a {\n color: 2;\n}\n",
grass::Options::default().input_syntax(InputSyntax::Css)
);
test!(
simple_url_import,
r#"@import url("foo");"#,
"@import url(\"foo\");\n",
grass::Options::default().input_syntax(InputSyntax::Css)
);
test!(
import_no_file_extension,
r#"@import "foo";"#,
"@import \"foo\";\n",
grass::Options::default().input_syntax(InputSyntax::Css)
);
test!(
import_with_condition,
r#"@import "foo" screen and (foo: bar);"#,
"@import \"foo\" screen and (foo: bar);\n",
grass::Options::default().input_syntax(InputSyntax::Css)
);
test!(
does_not_evaluate_not,
"a {
@ -155,6 +181,14 @@ error!(
"Error: Operators aren't allowed in plain CSS.",
grass::Options::default().input_syntax(InputSyntax::Css)
);
error!(
disallows_interpolation,
"a {
color: a#{b}c;
}",
"Error: Interpolation isn't allowed in plain CSS.",
grass::Options::default().input_syntax(InputSyntax::Css)
);
test!(
allows_rgb_function,
"a {
@ -163,3 +197,13 @@ test!(
"a {\n color: rgb(true, a, b);\n}\n",
grass::Options::default().input_syntax(InputSyntax::Css)
);
test!(
simple_supports,
"@supports (foo) {
a {
color: red;
}
}",
"@supports (foo) {\n a {\n color: red;\n }\n}\n",
grass::Options::default().input_syntax(InputSyntax::Css)
);

View File

@ -81,6 +81,18 @@ test!(
"/* loud */\n",
grass::Options::default().input_syntax(InputSyntax::Sass)
);
test!(
special_mixin_and_include_characters,
r#"
=foo
color: red
a
+foo
"#,
"a {\n color: red;\n}\n",
grass::Options::default().input_syntax(InputSyntax::Sass)
);
error!(
multiline_comment_in_value_position,
r#"
@ -90,3 +102,15 @@ loud */ red
"Error: expected */.",
grass::Options::default().input_syntax(InputSyntax::Sass)
);
error!(
document_starts_with_spaces,
r#" "#,
"Error: Indenting at the beginning of the document is illegal.",
grass::Options::default().input_syntax(InputSyntax::Sass)
);
error!(
document_starts_with_tab,
"\t",
"Error: Indenting at the beginning of the document is illegal.",
grass::Options::default().input_syntax(InputSyntax::Sass)
);

View File

@ -927,6 +927,10 @@ error!(
denies_optional_in_selector,
"a !optional {}", "Error: expected \"{\"."
);
error!(
child_selector_starts_with_forward_slash,
"a { /b { } }", "Error: expected selector."
);
// todo:
// [attr=url] {

View File

@ -276,6 +276,30 @@ test!(
"a {\n color: calc(1dpi + 1dppx);\n}\n",
"a {\n color: 97dpi;\n}\n"
);
test!(
ternary_inside_calc,
"a {\n color: calc(if(true, 1, unit(foo)));\n}\n",
"a {\n color: 1;\n}\n"
);
test!(
retains_parens_around_var_in_calc,
"a {\n color: calc((var(--a)) + 1rem);\n}\n",
"a {\n color: calc((var(--a)) + 1rem);\n}\n"
);
test!(
removes_superfluous_parens_around_function_call_in_calc,
"a {\n color: calc((foo(--a)) + 1rem);\n}\n",
"a {\n color: calc(foo(--a) + 1rem);\n}\n"
);
test!(
calculation_inside_calc,
"a {\n color: calc(calc(1px + 1rem) * calc(2px - 2in));\n}\n",
"a {\n color: calc((1px + 1rem) * -190px);\n}\n"
);
error!(
escaped_close_paren_inside_calc,
"a {\n color: calc(\\));\n}\n", r#"Error: Expected "(" or "."."#
);
error!(
nothing_after_last_arg,
"a { color: calc(1 + 1", r#"Error: expected "+", "-", "*", "/", or ")"."#

View File

@ -21,6 +21,31 @@ test!(
"a {\n color: u+27a;\n}\n",
"a {\n color: u+27a;\n}\n"
);
test!(
second_element_in_list,
"a {\n color: a u+55;\n}\n",
"a {\n color: a u+55;\n}\n"
);
test!(
escaped_lowercase_u,
"a {\n color: \\75+55;\n}\n",
"a {\n color: u55;\n}\n"
);
test!(
escaped_uppercase_u,
"a {\n color: \\55+55;\n}\n",
"a {\n color: U55;\n}\n"
);
test!(
escaped_lowercase_u_with_space_after_escape,
"a {\n color: \\75 +55;\n}\n",
"a {\n color: u55;\n}\n"
);
test!(
escaped_uppercase_u_with_space_after_escape,
"a {\n color: \\55 +55;\n}\n",
"a {\n color: U55;\n}\n"
);
error!(
interpolated_range,
"a {\n color: U+2A#{70}C;\n}\n", "Error: Expected end of identifier."
@ -37,6 +62,19 @@ error!(
length_of_6_with_question_mark,
"a {\n color: U+123456?;\n}\n", "Error: Expected at most 6 digits."
);
// todo: escaped u at start \75 and \55
// with and without space
error!(
nothing_after_plus_lowercase,
"a {\n color: u+;\n}\n", r#"Error: Expected hex digit or "?"."#
);
error!(
nothing_after_plus_uppercase,
"a {\n color: U+;\n}\n", r#"Error: Expected hex digit or "?"."#
);
error!(
second_part_of_range_is_empty,
"a {\n color: u+55-;\n}\n", r#"Error: Expected hex digit."#
);
error!(
second_part_of_range_is_more_than_6_chars,
"a {\n color: u+55-1234567;\n}\n", r#"Error: Expected at most 6 digits."#
);

View File

@ -171,6 +171,11 @@ test!(
"a {\n color: url(#);\n}\n",
"a {\n color: url(#);\n}\n"
);
test!(
escaped_close_paren,
"a {\n color: url(\\));\n}\n",
"a {\n color: url(\\));\n}\n"
);
error!(
url_nothing_after_forward_slash_in_interpolation,
"a { color: url(#{/", "Error: Expected expression."