increase code coverage

This commit is contained in:
connorskees 2023-01-18 01:37:59 +00:00
parent ef7d188062
commit 23abe152bd
25 changed files with 505 additions and 42 deletions

View File

@ -165,8 +165,6 @@ pub(crate) fn adjust_hue(mut args: ArgumentResult, visitor: &mut Visitor) -> Sas
.assert_color_with_name("color", args.span())?;
let degrees = angle_value(args.get_err(1, "degrees")?, "degrees", args.span())?;
dbg!(degrees);
Ok(Value::Color(Arc::new(color.adjust_hue(degrees))))
}

View File

@ -67,7 +67,6 @@ pub(crate) fn alpha(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResu
pub(crate) fn opacity(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?;
match args.get_err(0, "color")? {
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(),
Value::Color(c) => Ok(Value::Dimension(SassNumber::new_unitless(c.alpha()))),
Value::Dimension(SassNumber {
num,

View File

@ -56,12 +56,7 @@ pub(crate) fn nth(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult
pub(crate) fn list_separator(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?;
Ok(Value::String(
match args.get_err(0, "list")? {
Value::List(_, sep, ..) => sep.name(),
Value::Map(..) | Value::ArgList(..) => ListSeparator::Comma.name(),
_ => ListSeparator::Space.name(),
}
.to_owned(),
args.get_err(0, "list")?.separator().name().to_owned(),
QuoteKind::None,
))
}

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 = name)
format!("This module and the new module both define a variable named \"${name}\".", name = name)
, span).into());
}
}

View File

@ -829,13 +829,7 @@ impl<'a> Visitor<'a> {
span: Span,
) -> SassResult<StyleSheet> {
if let Some(name) = self.find_import(url.as_ref()) {
// assumption: most users use regular file paths for their imports.
// we do support importing syntactically invalid paths and paths that
// do not exist through the `Options::fs` API, so we fallback to the
// original name if necessary
let canonical = std::fs::canonicalize(&name).unwrap_or_else(|_| name.to_path_buf());
if let Some(style_sheet) = self.import_cache.get(&canonical) {
if let Some(style_sheet) = self.import_cache.get(&name) {
return Ok(style_sheet.clone());
}
@ -853,10 +847,10 @@ impl<'a> Visitor<'a> {
self.flags
.set(ContextFlags::IS_USE_ALLOWED, old_is_use_allowed);
if self.files_seen.contains(&canonical) {
self.import_cache.insert(canonical, style_sheet.clone());
if self.files_seen.contains(&name) {
self.import_cache.insert(name, style_sheet.clone());
} else {
self.files_seen.insert(canonical);
self.files_seen.insert(name);
}
return Ok(style_sheet);
@ -2079,16 +2073,14 @@ impl<'a> Visitor<'a> {
touched: BTreeSet::new(),
})
}
v => {
return Err((
format!(
"Variable keyword arguments must be a map (was {}).",
v.inspect(arguments.span)?
),
arguments.span,
)
.into());
}
v => Err((
format!(
"Variable keyword arguments must be a map (was {}).",
v.inspect(arguments.span)?
),
arguments.span,
)
.into()),
}
}

View File

@ -32,7 +32,7 @@ grass input.scss
```
*/
#![warn(clippy::all, clippy::cargo)]
#![warn(clippy::all, clippy::cargo, clippy::dbg_macro)]
#![deny(missing_debug_implementations)]
#![allow(
clippy::use_self,

View File

@ -2,8 +2,7 @@ use std::{
convert::From,
fmt, mem,
ops::{
Add, AddAssign, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub,
SubAssign,
Add, AddAssign, Deref, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
},
};
@ -231,12 +230,6 @@ impl Deref for Number {
}
}
impl DerefMut for Number {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
macro_rules! from_integer {
($ty:ty) => {
impl From<$ty> for Number {

View File

@ -32,7 +32,7 @@ grass input.scss
*/
#![cfg_attr(doc, feature(doc_cfg))]
#![warn(clippy::all, clippy::cargo)]
#![warn(clippy::all, clippy::cargo, clippy::dbg_macro)]
#![deny(missing_debug_implementations)]
#![allow(
clippy::use_self,

View File

@ -690,7 +690,75 @@ test!(
"a {\n color: #0000z;\n}\n",
"a {\n color: rgba(0, 0, 0, 0) z;\n}\n"
);
test!(
opacity_nan,
"a {\n color: opacity(0/0);\n}\n",
"a {\n color: opacity(NaN);\n}\n"
);
test!(
change_color_no_change,
"a {\n color: change-color(red);\n}\n",
"a {\n color: red;\n}\n"
);
test!(
change_color_hwb_hue,
"a {\n color: change-color(red, $whiteness: 50%, $hue: 230);\n}\n",
"a {\n color: #8095ff;\n}\n"
);
error!(
hex_color_starts_with_number_non_hex_digit_at_position_6,
"a {\n color: #00000z;\n}\n", "Error: Expected hex digit."
);
error!(
opacity_arg_not_color_or_number,
"a {\n color: opacity(a);\n}\n", "Error: $color: a is not a color."
);
error!(
ie_hex_str_no_args,
"a {\n color: ie-hex-str();\n}\n", "Error: Missing argument $color."
);
error!(
opacify_no_args,
"a {\n color: opacify();\n}\n", "Error: Missing argument $color."
);
error!(
opacify_one_arg,
"a {\n color: opacify(red);\n}\n", "Error: Missing argument $amount."
);
error!(
transparentize_no_args,
"a {\n color: transparentize();\n}\n", "Error: Missing argument $color."
);
error!(
transparentize_one_arg,
"a {\n color: transparentize(red);\n}\n", "Error: Missing argument $amount."
);
error!(
adjust_color_sl_and_wb,
"a {\n color: adjust-color(red, $saturation: 50%, $whiteness: 50%);\n}\n",
"Error: HSL parameters may not be passed along with HWB parameters."
);
error!(
adjust_color_rgb_and_sl,
"a {\n color: adjust-color(red, $red: 50%, $saturation: 50%);\n}\n",
"Error: RGB parameters may not be passed along with HSL parameters."
);
error!(
adjust_color_rgb_and_wb,
"a {\n color: adjust-color(red, $red: 50%, $whiteness: 50%);\n}\n",
"Error: RGB parameters may not be passed along with HWB parameters."
);
error!(
adjust_color_two_unknown_named_args,
"a {\n color: adjust-color(red, $foo: 50%, $bar: 50%);\n}\n",
"Error: No arguments named $foo or $bar."
);
error!(
adjust_color_two_positional_args,
"a {\n color: adjust-color(red, 50%);\n}\n",
"Error: Only one positional argument is allowed. All other arguments must be passed by name."
);
error!(
adjust_color_no_args,
"a {\n color: adjust-color();\n}\n", "Error: Missing argument $color."
);

View File

@ -203,7 +203,7 @@ test!(
"a {\n color: 100%;\n}\n"
);
test!(
saturate_one_arg,
saturate_one_named_arg,
"a {\n color: saturate($amount: 50%);\n}\n",
"a {\n color: saturate(50%);\n}\n"
);
@ -356,3 +356,84 @@ test!(
"a {\n color: hsl(60rad, 100%, 50%);\n}\n",
"a {\n color: hsl(197.7467707849deg, 100%, 50%);\n}\n"
);
error!(
hsl_one_arg_bracketed,
"a {\n color: hsl([1, 2, 3]);\n}\n",
"Error: $channels must be an unbracketed, space-separated list."
);
error!(
hue_arg_not_color,
"a {\n color: hue(1);\n}\n", "Error: $color: 1 is not a color."
);
error!(
saturation_arg_not_color,
"a {\n color: saturation(1);\n}\n", "Error: $color: 1 is not a color."
);
error!(
lightness_arg_not_color,
"a {\n color: lightness(1);\n}\n", "Error: $color: 1 is not a color."
);
error!(
adjust_hue_first_arg_not_color,
"a {\n color: adjust-hue(1, 5deg);\n}\n", "Error: $color: 1 is not a color."
);
error!(
hue_no_args,
"a {\n color: hue();\n}\n", "Error: Missing argument $color."
);
error!(
saturation_no_args,
"a {\n color: saturation();\n}\n", "Error: Missing argument $color."
);
error!(
lightness_no_args,
"a {\n color: lightness();\n}\n", "Error: Missing argument $color."
);
error!(
adjust_hue_no_args,
"a {\n color: adjust-hue();\n}\n", "Error: Missing argument $color."
);
error!(
adjust_hue_one_arg,
"a {\n color: adjust-hue(red);\n}\n", "Error: Missing argument $degrees."
);
error!(
lighten_no_args,
"a {\n color: lighten();\n}\n", "Error: Missing argument $color."
);
error!(
lighten_one_arg,
"a {\n color: lighten(red);\n}\n", "Error: Missing argument $amount."
);
error!(
darken_no_args,
"a {\n color: darken();\n}\n", "Error: Missing argument $color."
);
error!(
darken_one_arg,
"a {\n color: darken(red);\n}\n", "Error: Missing argument $amount."
);
error!(
saturate_single_named_arg_not_amount,
"a {\n color: saturate($a: red);\n}\n", "Error: Missing argument $amount."
);
error!(
saturate_no_args,
"a {\n color: saturate();\n}\n", "Error: Missing argument $amount."
);
error!(
saturate_one_arg,
"a {\n color: saturate(red);\n}\n", "Error: $amount: red is not a number."
);
error!(
desaturate_no_args,
"a {\n color: desaturate();\n}\n", "Error: Missing argument $color."
);
error!(
desaturate_one_arg,
"a {\n color: desaturate(red);\n}\n", "Error: Missing argument $amount."
);
error!(
complement_no_args,
"a {\n color: complement();\n}\n", "Error: Missing argument $color."
);

View File

@ -11,6 +11,17 @@ test!(
"@use \"sass:color\";\na {\n color: color.blackness(white);\n}\n",
"a {\n color: 0%;\n}\n"
);
test!(
whiteness_black,
"@use \"sass:color\";\na {\n color: color.whiteness(black);\n}\n",
"a {\n color: 0%;\n}\n"
);
test!(
whiteness_white,
"@use \"sass:color\";\na {\n color: color.whiteness(white);\n}\n",
"a {\n color: 100%;\n}\n"
);
test!(
blackness_approx_50_pct,
"@use \"sass:color\";\na {\n color: color.blackness(color.hwb(0, 0%, 50%));\n}\n",
@ -111,3 +122,18 @@ error!(
"@use \"sass:color\";\na {\n color: color.hwb(0, 30%, 101%, 0.5);\n}\n",
"Error: $blackness: Expected 101% to be within 0% and 100%."
);
error!(
blackness_no_args,
"@use \"sass:color\";\na {\n color: color.blackness();\n}\n",
"Error: Missing argument $color."
);
error!(
whiteness_no_args,
"@use \"sass:color\";\na {\n color: color.whiteness();\n}\n",
"Error: Missing argument $color."
);
error!(
hwb_var_channels,
"@use \"sass:color\";\na {\n color: color.hwb(var(--foo));\n}\n",
"Error: Expected numeric channels, got \"hwb(var(--foo))\"."
);

View File

@ -3,3 +3,10 @@ mod macros;
test!(simple_debug, "@debug 2", "");
test!(simple_debug_with_semicolon, "@debug 2;", "");
test!(
// todo: test stdout
debug_while_quiet,
"@debug 2;",
"",
grass::Options::default().quiet(true)
);

View File

@ -317,6 +317,23 @@ fn member_as_variable_assignment_toplevel() {
);
}
#[test]
fn forward_module_with_error() {
let mut fs = TestFs::new();
fs.add_file("_error.scss", r#"a { color: 1 + red; }"#);
let input = r#"
@forward "error";
"#;
assert_err!(
input,
r#"Error: Undefined operation "1 + red"."#,
grass::Options::default().fs(&fs)
);
}
error!(
after_style_rule,
r#"

View File

@ -315,6 +315,41 @@ fn imports_absolute_scss() {
);
}
#[test]
fn imports_same_file_twice() {
let mut fs = TestFs::new();
fs.add_file("a.scss", r#"a { color: red; }"#);
let input = r#"
@import "a";
@import "a";
"#;
assert_eq!(
"a {\n color: red;\n}\n\na {\n color: red;\n}\n",
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
);
}
#[test]
fn imports_same_file_thrice() {
let mut fs = TestFs::new();
fs.add_file("a.scss", r#"a { color: red; }"#);
let input = r#"
@import "a";
@import "a";
@import "a";
"#;
assert_eq!(
"a {\n color: red;\n}\n\na {\n color: red;\n}\n\na {\n color: red;\n}\n",
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
);
}
#[test]
fn imports_explicit_file_extension() {
let mut fs = TestFs::new();

View File

@ -437,6 +437,31 @@ 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_map,
"a {\n color: list-separator((a: b, c: d));\n}\n",
"a {\n color: comma;\n}\n"
);
test!(
list_separator_of_empty_parens,
"a {\n color: list-separator(());\n}\n",
"a {\n color: space;\n}\n"
);
test!(
list_separator_of_unquoted_string,
"a {\n color: list-separator(a);\n}\n",
"a {\n color: space;\n}\n"
);
test!(
list_separator_of_arglist,
"@function foo($a...) {
@return list-separator($a);
}
a {
color: foo();
}",
"a {\n color: comma;\n}\n"
);
test!(
list_separator_of_empty_list_after_join,
"a {

View File

@ -112,6 +112,19 @@ macro_rules! assert_err {
),
}
};
($input:expr, $err:expr, $options:expr) => {
match grass::from_string($input.to_string(), &$options) {
Ok(..) => panic!("did not fail"),
Err(e) => assert_eq!(
$err,
e.to_string()
.chars()
.take_while(|c| *c != '\n')
.collect::<String>()
.as_str()
),
}
};
}
/// Suitable for simple import tests. Does not properly implement path resolution --

View File

@ -158,3 +158,31 @@ error!(
comparable_non_number_arg_last,
"a {\n color: comparable(1, b);\n}\n", "Error: $number2: b is not a number."
);
error!(
percentage_no_args,
"a {\n color: percentage();\n}\n", "Error: Missing argument $number."
);
error!(
round_no_args,
"a {\n color: round();\n}\n", "Error: Missing argument $number."
);
error!(
ceil_no_args,
"a {\n color: ceil();\n}\n", "Error: Missing argument $number."
);
error!(
floor_no_args,
"a {\n color: floor();\n}\n", "Error: Missing argument $number."
);
error!(
abs_no_args,
"a {\n color: abs();\n}\n", "Error: Missing argument $number."
);
error!(
comparable_no_args,
"a {\n color: comparable();\n}\n", "Error: Missing argument $number1."
);
error!(
comparable_one_arg,
"a {\n color: comparable(1);\n}\n", "Error: Missing argument $number2."
);

View File

@ -26,6 +26,23 @@ test!(
"a {\n color: if(false, 1, 2);\n}\n",
"a {\n color: 2;\n}\n"
);
test!(
if_is_global_fn,
"a {
$a: get-function(if);
color: call($a, true, 2, 3);
color: call($a, false, 2, 3);
}",
"a {\n color: 2;\n color: 3;\n}\n"
);
error!(
if_inside_call_does_not_lazily_eval_args,
"a {
$a: get-function(if);
color: call($a, true, 2, red % 5);
}",
"Error: Undefined operation \"red % 5\"."
);
test!(
feature_exists_dbl_quoted,
"a {\n color: feature-exists(\"at-error\")\n}\n",
@ -359,5 +376,17 @@ test!(
}",
"a {\n color: get-function(\"empty\");\n}\n"
);
error!(
feature_exists_no_args,
"a {\n color: feature-exists();\n}\n", "Error: Missing argument $feature."
);
error!(
unit_no_args,
"a {\n color: unit();\n}\n", "Error: Missing argument $number."
);
error!(
unitless_no_args,
"a {\n color: unitless();\n}\n", "Error: Missing argument $number."
);
// todo: if() with different combinations of named and positional args

View File

@ -175,6 +175,11 @@ test!(
"a {\n color: -5 % (-1/0);\n}\n",
"a {\n color: NaN;\n}\n"
);
test!(
zero_mod_negative,
"a {\n color: 0 % -5;\n}\n",
"a {\n color: 0;\n}\n"
);
error!(
calculation_mod_calculation,
"a {\n color: calc(1rem + 1px) % calc(1rem + 1px);\n}\n",

View File

@ -81,6 +81,16 @@ test!(
"a {\n color: or(foo);\n}\n",
"a {\n color: or(foo);\n}\n"
);
test!(
rest_arg,
"a {
color: foo(red...);
color: foo(a, red...);
color: f#{o}o(red...);
color: f#{o}o(a, red...);
}",
"a {\n color: foo(red);\n color: foo(a, red);\n color: foo(red);\n color: foo(a, red);\n}\n"
);
error!(
denies_keyword_arguments_to_interpolated_function,
"a {\n color: f#{o}o($a: red);\n}\n",

View File

@ -251,6 +251,16 @@ test!(
"a {\n color: calc(1% + 3px - 2px);\n}\n",
"a {\n color: calc(1% + 3px - 2px);\n}\n"
);
test!(
inspect_calc,
"a {\n color: inspect(calc(1% + 3px - 2px));\n}\n",
"a {\n color: calc(1% + 3px - 2px);\n}\n"
);
test!(
calc_ne_number,
"a {\n color: calc(1% + 3px - 2px) == 1px;\n}\n",
"a {\n color: false;\n}\n"
);
test!(
calc_num_plus_interpolation,
"a {\n color: calc(1 + #{c});\n}\n",

View File

@ -254,3 +254,48 @@ test!(
"a {\n color: str-index(\"c\\0308 a\", \"a\");\n}\n",
"a {\n color: 3;\n}\n"
);
error!(
to_lower_case_no_args,
"a {\n color: to_lower_case();\n}\n", "Error: Missing argument $string."
);
error!(
str_length_no_args,
"a {\n color: str_length();\n}\n", "Error: Missing argument $string."
);
error!(
quote_no_args,
"a {\n color: quote();\n}\n", "Error: Missing argument $string."
);
error!(
unquote_no_args,
"a {\n color: unquote();\n}\n", "Error: Missing argument $string."
);
error!(
str_slice_no_args,
"a {\n color: str_slice();\n}\n", "Error: Missing argument $string."
);
error!(
str_index_no_args,
"a {\n color: str_index();\n}\n", "Error: Missing argument $string."
);
error!(
str_insert_no_args,
"a {\n color: str_insert();\n}\n", "Error: Missing argument $string."
);
test!(
unique_id_is_unique,
"$init: unique-id();
@for $_ from 0 to 100 {
@if $init == unique-id() {
@error 'got duplicate unique id: #{$init}';
}
}",
""
);
test!(
unique_id_is_valid_identifier,
"@for $_ from 0 to 100 {
#{unique-id()} {}
}",
""
);

View File

@ -138,7 +138,7 @@ test!(
);
test!(supports_empty_body, "@supports (a: b) {}", "");
test!(
does_not_simplify_calculation_in_args,
calculation_not_in_declaration,
"@supports (calc(1 + 1)) {
a {
color: red;
@ -146,6 +146,24 @@ test!(
}",
"@supports (calc(1 + 1)) {\n a {\n color: red;\n }\n}\n"
);
test!(
ident_addition_on_rhs_of_declaration,
"@supports (a: a + b) {
a {
color: red;
}
}",
"@supports (a: ab) {\n a {\n color: red;\n }\n}\n"
);
test!(
calculation_on_rhs_of_declaration,
"@supports (a: calc(1px + 1px)) {
a {
color: red;
}
}",
"@supports (a: calc(1px + 1px)) {\n a {\n color: red;\n }\n}\n"
);
error!(
supports_inside_declaration_body,
"@mixin foo() {

View File

@ -640,4 +640,66 @@ fn module_functions_through_forward() {
);
}
#[test]
fn use_variable_declared_in_this_and_other_module() {
let mut fs = TestFs::new();
fs.add_file(
"_a.scss",
r#"
$a: blue;
"#,
);
let input = r#"
$a: red;
@use "a" as *;
a {
color: $a;
}
"#;
assert_err!(
input,
"Error: This module and the new module both define a variable named \"$a\".",
grass::Options::default().fs(&fs)
);
}
#[test]
#[ignore = "we don't check for this"]
fn use_variable_declared_in_two_modules() {
let mut fs = TestFs::new();
fs.add_file(
"_a.scss",
r#"
$a: blue;
"#,
);
fs.add_file(
"_b.scss",
r#"
$a: red;
"#,
);
let input = r#"
@use "a" as *;
@use "b" as *;
a {
color: $a;
}
"#;
assert_err!(
input,
"Error: This variable is available from multiple global modules.",
grass::Options::default().fs(&fs)
);
}
// todo: refactor these tests to use testfs where possible

View File

@ -2,3 +2,10 @@
mod macros;
test!(simple_warn, "@warn 2", "");
test!(
// todo: test stdout
warn_while_quiet,
"@warn 2;",
"",
grass::Options::default().quiet(true)
);