diff --git a/src/atrule/each_rule.rs b/src/atrule/each_rule.rs index 99d2ec8..cea1bb4 100644 --- a/src/atrule/each_rule.rs +++ b/src/atrule/each_rule.rs @@ -26,6 +26,7 @@ impl Each { self, scope: &mut Scope, super_selector: &Selector, + content: Option<&[Spanned]>, ) -> SassResult>> { let mut stmts = Vec::new(); for row in self.iter { @@ -77,6 +78,7 @@ impl Each { scope, super_selector, false, + content, &mut stmts, )?; } diff --git a/src/atrule/for_rule.rs b/src/atrule/for_rule.rs index f9a69aa..8bf3af7 100644 --- a/src/atrule/for_rule.rs +++ b/src/atrule/for_rule.rs @@ -32,6 +32,7 @@ impl For { self, scope: &mut Scope, super_selector: &Selector, + content: Option<&[Spanned]>, ) -> SassResult>> { let mut stmts = Vec::new(); for i in self.iter { @@ -47,6 +48,7 @@ impl For { scope, super_selector, false, + content, &mut stmts, )?; } diff --git a/src/atrule/function.rs b/src/atrule/function.rs index f0a5ee5..fe57a9a 100644 --- a/src/atrule/function.rs +++ b/src/atrule/function.rs @@ -112,6 +112,7 @@ impl Function { &mut self.scope, super_selector, false, + None, ) } @@ -153,6 +154,7 @@ impl Function { &mut self.scope, super_selector, false, + None, )?; if let Some(v) = self.call(super_selector, for_stmts)? { return Ok(Some(v)); @@ -160,7 +162,7 @@ impl Function { } } Stmt::AtRule(AtRule::If(i)) => { - let if_stmts = i.eval(&mut self.scope, super_selector)?; + let if_stmts = i.eval(&mut self.scope, super_selector, None)?; if let Some(v) = self.call(super_selector, if_stmts)? { return Ok(Some(v)); } @@ -174,6 +176,7 @@ impl Function { scope, super_selector, false, + None, )?; if let Some(v) = self.call(super_selector, while_stmts)? { return Ok(Some(v)); diff --git a/src/atrule/if_rule.rs b/src/atrule/if_rule.rs index 92445f6..8bf36f8 100644 --- a/src/atrule/if_rule.rs +++ b/src/atrule/if_rule.rs @@ -105,6 +105,7 @@ impl If { self, scope: &mut Scope, super_selector: &Selector, + content: Option<&[Spanned]>, ) -> SassResult>> { let mut stmts = Vec::new(); let mut toks = Vec::new(); @@ -125,6 +126,7 @@ impl If { scope, super_selector, false, + content, &mut stmts, )?; Ok(stmts) diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 716a411..9fb14d8 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -22,23 +22,12 @@ pub(crate) struct Mixin { scope: Scope, args: FuncArgs, body: PeekMoreIterator>, - content: Vec>, } impl Mixin { - pub fn new( - scope: Scope, - args: FuncArgs, - body: Vec, - content: Vec>, - ) -> Self { + pub fn new(scope: Scope, args: FuncArgs, body: Vec) -> Self { let body = body.into_iter().peekmore(); - Mixin { - scope, - args, - body, - content, - } + Mixin { scope, args, body } } pub fn decl_from_tokens>( @@ -62,16 +51,11 @@ impl Mixin { body.push(toks.next().unwrap()); Ok(Spanned { - node: (name, Mixin::new(scope.clone(), args, body, Vec::new())), + node: (name, Mixin::new(scope.clone(), args, body)), span, }) } - pub fn content(mut self, content: Vec>) -> Mixin { - self.content = content; - self - } - pub fn args( mut self, mut args: CallArgs, @@ -113,28 +97,43 @@ impl Mixin { Ok(self) } - pub fn call(mut self, super_selector: &Selector) -> SassResult>> { - self.eval(super_selector) + pub fn call( + mut self, + super_selector: &Selector, + content: Option<&[Spanned]>, + ) -> SassResult>> { + self.eval(super_selector, content) } - fn eval(&mut self, super_selector: &Selector) -> SassResult>> { + fn eval( + &mut self, + super_selector: &Selector, + content: Option<&[Spanned]>, + ) -> SassResult>> { let mut stmts = Vec::new(); - while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector)? { + while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector, content)? { let span = expr.span; match expr.node { Expr::AtRule(a) => match a { AtRule::For(f) => { - stmts.extend(f.ruleset_eval(&mut self.scope, super_selector)?) + stmts.extend(f.ruleset_eval(&mut self.scope, super_selector, content)?) } AtRule::Each(e) => { - stmts.extend(e.ruleset_eval(&mut self.scope, super_selector)?) - } - AtRule::While(w) => { - stmts.extend(w.ruleset_eval(&mut self.scope, super_selector, false)?) + stmts.extend(e.ruleset_eval(&mut self.scope, super_selector, content)?) } + AtRule::While(w) => stmts.extend(w.ruleset_eval( + &mut self.scope, + super_selector, + false, + content, + )?), AtRule::Include(s) => stmts.extend(s), - AtRule::If(i) => stmts.extend(i.eval(&mut self.scope.clone(), super_selector)?), - AtRule::Content => stmts.extend(self.content.clone()), + AtRule::If(i) => { + stmts.extend(i.eval(&mut self.scope.clone(), super_selector, content)?) + } + AtRule::Content => { + stmts.extend(content.unwrap_or_default().into_iter().cloned()); + } AtRule::Return(..) => { return Err(("This at-rule is not allowed here.", span).into()) } @@ -161,7 +160,7 @@ impl Mixin { return Err(("Mixins may not contain mixin declarations.", span).into()) } Expr::Selector(selector) => { - let rules = self.eval(&super_selector.zip(&selector))?; + let rules = self.eval(&super_selector.zip(&selector), content)?; stmts.push(Spanned { node: Stmt::RuleSet(RuleSet { super_selector: super_selector.clone(), @@ -188,6 +187,7 @@ pub(crate) fn eat_include>( toks: &mut PeekMoreIterator, scope: &Scope, super_selector: &Selector, + content: Option<&[Spanned]>, ) -> SassResult>> { devour_whitespace_or_comment(toks)?; let name = eat_ident(toks, scope, super_selector)?; @@ -223,7 +223,7 @@ pub(crate) fn eat_include>( devour_whitespace(toks); - let mut content = Vec::new(); + let mut this_content = Vec::new(); if let Some(tok) = toks.peek() { if tok.kind == '{' { @@ -233,7 +233,8 @@ pub(crate) fn eat_include>( &mut scope.clone(), super_selector, false, - &mut content, + content, + &mut this_content, )?; } else if has_content { ruleset_eval( @@ -241,7 +242,8 @@ pub(crate) fn eat_include>( &mut scope.clone(), super_selector, false, - &mut content, + content, + &mut this_content, )?; } } @@ -250,7 +252,6 @@ pub(crate) fn eat_include>( let rules = mixin .args(args, scope, super_selector)? - .content(content) - .call(super_selector)?; + .call(super_selector, Some(&this_content))?; Ok(rules) } diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index ef22bea..999db3c 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -57,6 +57,7 @@ impl AtRule { toks: &mut PeekMoreIterator, scope: &mut Scope, super_selector: &Selector, + content: Option<&[Spanned]>, ) -> SassResult> { devour_whitespace(toks); Ok(match rule { @@ -170,6 +171,7 @@ impl AtRule { selector, 0, is_some, + content, )? .into_iter() .filter_map(|s| match s.node { @@ -225,6 +227,7 @@ impl AtRule { scope, super_selector, kind_span, + content, )?), span: kind_span, }, @@ -233,7 +236,7 @@ impl AtRule { span: kind_span, }, AtRuleKind::Include => Spanned { - node: AtRule::Include(eat_include(toks, scope, super_selector)?), + node: AtRule::Include(eat_include(toks, scope, super_selector, content)?), span: kind_span, }, AtRuleKind::Import => todo!("@import not yet implemented"), diff --git a/src/atrule/parse.rs b/src/atrule/parse.rs index c51db80..e93dc69 100644 --- a/src/atrule/parse.rs +++ b/src/atrule/parse.rs @@ -14,9 +14,10 @@ pub(crate) fn eat_stmts>( scope: &mut Scope, super_selector: &Selector, at_root: bool, + content: Option<&[Spanned]>, ) -> SassResult>> { let mut stmts = Vec::new(); - while let Some(expr) = eat_expr(toks, scope, super_selector)? { + while let Some(expr) = eat_expr(toks, scope, super_selector, content)? { let span = expr.span; match expr.node { Expr::AtRule(a) => stmts.push(Stmt::AtRule(a).span(span)), @@ -29,7 +30,13 @@ pub(crate) fn eat_stmts>( ), Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(), Expr::Selector(selector) => { - let rules = eat_stmts(toks, scope, &super_selector.zip(&selector), at_root)?; + let rules = eat_stmts( + toks, + scope, + &super_selector.zip(&selector), + at_root, + content, + )?; stmts.push( Stmt::RuleSet(RuleSet { super_selector: super_selector.clone(), @@ -58,9 +65,10 @@ pub(crate) fn eat_stmts_at_root>( super_selector: &Selector, mut nesting: usize, is_some: bool, + content: Option<&[Spanned]>, ) -> SassResult>> { let mut stmts = Vec::new(); - while let Some(expr) = eat_expr(toks, scope, super_selector)? { + while let Some(expr) = eat_expr(toks, scope, super_selector, content)? { let span = expr.span; match expr.node { Expr::AtRule(a) => stmts.push(Stmt::AtRule(a).span(span)), @@ -79,7 +87,7 @@ pub(crate) fn eat_stmts_at_root>( selector = Selector::replace(super_selector, selector); } nesting += 1; - let rules = eat_stmts_at_root(toks, scope, &selector, nesting, true)?; + let rules = eat_stmts_at_root(toks, scope, &selector, nesting, true, content)?; nesting -= 1; stmts.push( Stmt::RuleSet(RuleSet { @@ -108,18 +116,34 @@ pub(crate) fn ruleset_eval>( scope: &mut Scope, super_selector: &Selector, at_root: bool, + content: Option<&[Spanned]>, stmts: &mut Vec>, ) -> SassResult<()> { - for stmt in eat_stmts(toks, scope, super_selector, at_root)? { + for stmt in eat_stmts(toks, scope, super_selector, at_root, content)? { match stmt.node { - Stmt::AtRule(AtRule::For(f)) => stmts.extend(f.ruleset_eval(scope, super_selector)?), - Stmt::AtRule(AtRule::Each(e)) => stmts.extend(e.ruleset_eval(scope, super_selector)?), + Stmt::AtRule(AtRule::For(f)) => { + stmts.extend(f.ruleset_eval(scope, super_selector, content)?) + } + Stmt::AtRule(AtRule::Each(e)) => { + stmts.extend(e.ruleset_eval(scope, super_selector, content)?) + } Stmt::AtRule(AtRule::While(w)) => { // TODO: should at_root be false? scoping - stmts.extend(w.ruleset_eval(scope, super_selector, at_root)?) + stmts.extend(w.ruleset_eval(scope, super_selector, at_root, content)?) } Stmt::AtRule(AtRule::Include(s)) => stmts.extend(s), - Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?), + Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector, content)?), + Stmt::AtRule(AtRule::Content) => { + if let Some(c) = content { + stmts.extend(c.into_iter().cloned()); + } else { + return Err(( + "@content is only allowed within mixin declarations.", + stmt.span, + ) + .into()); + } + } _ => stmts.push(stmt), } } diff --git a/src/atrule/unknown.rs b/src/atrule/unknown.rs index 42e0b7e..12cea3c 100644 --- a/src/atrule/unknown.rs +++ b/src/atrule/unknown.rs @@ -2,7 +2,7 @@ use codemap::{Span, Spanned}; use peekmore::PeekMoreIterator; -use super::parse::eat_stmts; +use super::parse::ruleset_eval; use crate::error::SassResult; use crate::scope::Scope; use crate::selector::Selector; @@ -24,6 +24,7 @@ impl UnknownAtRule { scope: &mut Scope, super_selector: &Selector, kind_span: Span, + content: Option<&[Spanned]>, ) -> SassResult { let mut params = String::new(); while let Some(tok) = toks.next() { @@ -49,7 +50,8 @@ impl UnknownAtRule { params.push(tok.kind); } - let raw_body = eat_stmts(toks, scope, super_selector, false)?; + let mut raw_body = Vec::new(); + ruleset_eval(toks, scope, super_selector, false, content, &mut raw_body)?; let mut rules = Vec::with_capacity(raw_body.len()); let mut body = Vec::new(); diff --git a/src/atrule/while_rule.rs b/src/atrule/while_rule.rs index 104bfc0..16d6db6 100644 --- a/src/atrule/while_rule.rs +++ b/src/atrule/while_rule.rs @@ -25,6 +25,7 @@ impl While { scope: &mut Scope, super_selector: &Selector, at_root: bool, + content: Option<&[Spanned]>, ) -> SassResult>> { let mut stmts = Vec::new(); let mut val = Value::from_vec(self.cond.clone(), scope, super_selector)?; @@ -35,6 +36,7 @@ impl While { scope, super_selector, at_root, + content, &mut stmts, )?; val = Value::from_vec(self.cond.clone(), scope, super_selector)?; diff --git a/src/lib.rs b/src/lib.rs index 344d00a..7f8eb13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -359,6 +359,7 @@ impl<'a> StyleSheetParser<'a> { &mut self.lexer, &Scope::new(), &Selector::new(), + None, )?), AtRuleKind::Import => { devour_whitespace(&mut self.lexer); @@ -394,7 +395,7 @@ impl<'a> StyleSheetParser<'a> { }); } v => { - let rule = AtRule::from_tokens(&v, span, &mut self.lexer, &mut Scope::new(), &Selector::new())?; + let rule = AtRule::from_tokens(&v, span, &mut self.lexer, &mut Scope::new(), &Selector::new(), None)?; match rule.node { AtRule::Mixin(name, mixin) => { insert_global_mixin(&name, *mixin); @@ -410,17 +411,17 @@ impl<'a> StyleSheetParser<'a> { ("This at-rule is not allowed here.", rule.span).into() ) } - AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new())?), - AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true)?), + AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?), + AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true, None)?), AtRule::Each(e) => { - rules.extend(e.ruleset_eval(&mut Scope::new(), &Selector::new())?) + rules.extend(e.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?) } AtRule::Include(s) => rules.extend(s), AtRule::Content => return Err( ("@content is only allowed within mixin declarations.", rule.span ).into()), AtRule::If(i) => { - rules.extend(i.eval(&mut Scope::new(), &Selector::new())?); + rules.extend(i.eval(&mut Scope::new(), &Selector::new(), None)?); } AtRule::AtRoot(root_rules) => rules.extend(root_rules), AtRule::Unknown(..) => rules.push(rule.map_node(Stmt::AtRule)), @@ -448,7 +449,7 @@ impl<'a> StyleSheetParser<'a> { scope: &mut Scope, ) -> SassResult>> { let mut stmts = Vec::new(); - while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector)? { + while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector, None)? { let span = expr.span; match expr.node { Expr::Style(s) => stmts.push(Spanned { @@ -456,13 +457,13 @@ impl<'a> StyleSheetParser<'a> { span, }), Expr::AtRule(a) => match a { - AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector)?), + AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector, None)?), AtRule::While(w) => { - stmts.extend(w.ruleset_eval(scope, super_selector, false)?) + stmts.extend(w.ruleset_eval(scope, super_selector, false, None)?) } - AtRule::Each(e) => stmts.extend(e.ruleset_eval(scope, super_selector)?), + AtRule::Each(e) => stmts.extend(e.ruleset_eval(scope, super_selector, None)?), AtRule::Include(s) => stmts.extend(s), - AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?), + AtRule::If(i) => stmts.extend(i.eval(scope, super_selector, None)?), AtRule::Content => { return Err(( "@content is only allowed within mixin declarations.", @@ -533,6 +534,7 @@ pub(crate) fn eat_expr>( toks: &mut PeekMoreIterator, scope: &mut Scope, super_selector: &Selector, + content: Option<&[Spanned]>, ) -> SassResult>> { let mut values = Vec::with_capacity(5); let mut span = if let Some(tok) = toks.peek() { @@ -689,6 +691,7 @@ pub(crate) fn eat_expr>( toks, scope, super_selector, + content, )?; return Ok(Some(Spanned { node: match rule.node { diff --git a/tests/mixins.rs b/tests/mixins.rs index 09c9b31..bea25ad 100644 --- a/tests/mixins.rs +++ b/tests/mixins.rs @@ -204,3 +204,28 @@ test!( "@mixin foo {\n @content;\n}\n\na {\n @include foo {@if true {color: red;}}\n}\n", "a {\n color: red;\n}\n" ); +test!( + content_in_control_flow, + "@mixin foo() {\n @if true {\n @content;\n }\n}\n\na {\n @include foo {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n" +); +test!( + content_inside_unknown_at_rule, + "@mixin foo() {\n @foo (max-width: max) {\n @content;\n }\n}\n\na {\n @include foo {\n color: red;\n }\n}\n", + "@foo (max-width: max) {\n a {\n color: red;\n }\n}\n" +); +test!( + content_inside_media, + "@mixin foo() {\n @media (max-width: max) {\n @content;\n }\n}\n\na {\n @include foo {\n color: red;\n }\n}\n", + "@media (max-width: max) {\n a {\n color: red;\n }\n}\n" +); +error!( + function_inside_mixin, + "@mixin foo() {\n @function bar() {\n @return foo;\n }\n}\n\na {\n @include foo {\n color: red;\n }\n}\n", + "Error: Mixins may not contain function declarations." +); +error!( + content_inside_control_flow_outside_mixin, + "a {\n @if true {\n @content;\n }\n}\n", + "Error: @content is only allowed within mixin declarations." +);