diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 436d28a..d6be1c5 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -24,6 +24,7 @@ impl Mixin { } } +#[derive(Debug, Clone)] pub(crate) struct Content { pub content: Option>, pub content_args: Option, diff --git a/src/builtin/meta.rs b/src/builtin/meta.rs index ac1638d..1e90787 100644 --- a/src/builtin/meta.rs +++ b/src/builtin/meta.rs @@ -245,7 +245,13 @@ fn content_exists(args: CallArgs, parser: &mut Parser<'_>) -> SassResult ) .into()); } - Ok(Value::bool(parser.content.content.is_some())) + Ok(Value::bool( + parser + .content + .last() + .map(|c| c.content.is_some()) + .unwrap_or(false), + )) } pub(crate) fn declare(f: &mut GlobalFunctionMap) { diff --git a/src/lib.rs b/src/lib.rs index a1278b1..12e435d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,7 +160,7 @@ pub fn from_path(p: &str) -> Result { global_scope: &mut Scope::new(), super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), span_before: empty_span, - content: &Content::new(), + content: &mut vec![Content::new()], in_mixin: false, in_function: false, in_control_flow: false, @@ -206,7 +206,7 @@ pub fn from_string(p: String) -> Result { global_scope: &mut Scope::new(), super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), span_before: empty_span, - content: &Content::new(), + content: &mut vec![Content::new()], in_mixin: false, in_function: false, in_control_flow: false, @@ -242,7 +242,7 @@ pub fn from_string(p: String) -> std::result::Result { global_scope: &mut Scope::new(), super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), span_before: empty_span, - content: &Content::new(), + content: &mut vec![Content::new()], in_mixin: false, in_function: false, in_control_flow: false, diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index 26e8866..45b420b 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -113,6 +113,11 @@ impl<'a> Parser<'a> { } = self.scopes.last().get_mixin(name, self.global_scope)?; self.eval_args(fn_args, args, &mut scope)?; + self.content.push(Content { + content, + content_args, + }); + let body = Parser { toks: &mut body.into_iter().peekmore(), map: self.map, @@ -124,16 +129,15 @@ impl<'a> Parser<'a> { in_mixin: true, in_function: self.in_function, in_control_flow: self.in_control_flow, - content: &Content { - content, - content_args, - }, + content: self.content, at_root: false, at_root_has_selector: self.at_root_has_selector, extender: self.extender, } .parse()?; + self.content.pop(); + Ok(body) } @@ -143,7 +147,9 @@ impl<'a> Parser<'a> { if let Some(Token { kind: '(', .. }) = self.toks.peek() { self.toks.next(); let args = self.parse_call_args()?; - if let Some(content_args) = self.content.content_args.clone() { + if let Some(Some(content_args)) = + self.content.last().map(|v| v.content_args.clone()) + { args.max_args(content_args.len())?; self.eval_args(content_args, args, &mut scope)?; @@ -152,24 +158,30 @@ impl<'a> Parser<'a> { } } - Ok(if let Some(content) = &self.content.content { - Parser { - toks: &mut content.to_vec().into_iter().peekmore(), - map: self.map, - path: self.path, - scopes: &mut NeverEmptyVec::new(scope), - global_scope: self.global_scope, - super_selectors: self.super_selectors, - span_before: self.span_before, - in_mixin: false, - in_function: self.in_function, - in_control_flow: self.in_control_flow, - content: self.content, - at_root: false, - at_root_has_selector: self.at_root_has_selector, - extender: self.extender, - } - .parse()? + Ok(if let Some(content) = &self.content.pop() { + let stmts = if let Some(body) = content.content.clone() { + Parser { + toks: &mut body.into_iter().peekmore(), + map: self.map, + path: self.path, + scopes: &mut NeverEmptyVec::new(scope), + global_scope: self.global_scope, + super_selectors: self.super_selectors, + span_before: self.span_before, + in_mixin: self.in_mixin, + in_function: self.in_function, + in_control_flow: self.in_control_flow, + content: self.content, + at_root: self.at_root, + at_root_has_selector: self.at_root_has_selector, + extender: self.extender, + } + .parse()? + } else { + Vec::new() + }; + self.content.push(content.clone()); + stmts } else { Vec::new() }) diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 2bdb86e..cf9a38a 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -70,7 +70,7 @@ pub(crate) struct Parser<'a> { pub scopes: &'a mut NeverEmptyVec, pub super_selectors: &'a mut NeverEmptyVec, pub span_before: Span, - pub content: &'a Content, + pub content: &'a mut Vec, pub in_mixin: bool, pub in_function: bool, pub in_control_flow: bool, diff --git a/tests/content-exists.rs b/tests/content-exists.rs index 693381f..2a83336 100644 --- a/tests/content-exists.rs +++ b/tests/content-exists.rs @@ -33,6 +33,22 @@ test!( "@mixin foo {\n color: content-exists();\n @content;\n}\n\na {\n @include foo{color: red};\n}\n", "a {\n color: true;\n color: red;\n}\n" ); +test!( + chained_mixin_second_doesnt_have_content, + "@mixin foo { + color: content-exists(); + } + + @mixin bar { + @include foo; + @content; + } + + a { + @include bar {} + }", + "a {\n color: false;\n}\n" +); error!( #[ignore = "haven't yet figured out a good way to check for whether an @content block exists"] include_empty_braces_no_args_no_at_content, diff --git a/tests/mixins.rs b/tests/mixins.rs index 51f9299..e68c3b0 100644 --- a/tests/mixins.rs +++ b/tests/mixins.rs @@ -286,6 +286,43 @@ test!( }", "a {\n color: red;\n}\n" ); +test!( + multiple_content_using_different_args, + "@mixin foo { + @content(1); + @content(2); + } + + @mixin bar { + @include foo using ($a) { + color: $a + } + } + + a { + @include bar; + }", + "a {\n color: 1;\n color: 2;\n}\n" +); +test!( + chained_content, + "@mixin foo { + @content; + } + + @mixin bar { + @include foo { + @content; + } + } + + a { + @include bar { + color: red; + } + }", + "a {\n color: red;\n}\n" +); error!( content_using_too_many_args, "@mixin foo {