diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 6b0ffcd..02fe77f 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -1,6 +1,8 @@ use std::iter::Peekable; use std::vec::IntoIter; +use super::eat_stmts; + use crate::args::{eat_call_args, eat_func_args, CallArgs, FuncArgs}; use crate::atrule::AtRule; use crate::common::Symbol; @@ -15,12 +17,18 @@ pub(crate) struct Mixin { scope: Scope, args: FuncArgs, body: Peekable>, + content: Vec, } impl Mixin { - pub fn new(scope: Scope, args: FuncArgs, body: Vec) -> Self { + pub fn new(scope: Scope, args: FuncArgs, body: Vec, content: Vec) -> Self { let body = body.into_iter().peekable(); - Mixin { scope, args, body } + Mixin { + scope, + args, + body, + content, + } } pub fn decl_from_tokens>( @@ -69,7 +77,12 @@ impl Mixin { } } - Ok((name, Mixin::new(scope.clone(), args, body))) + Ok((name, Mixin::new(scope.clone(), args, body, Vec::new()))) + } + + pub fn content(mut self, content: Vec) -> Mixin { + self.content = content; + self } pub fn args(mut self, args: &mut CallArgs) -> SassResult { @@ -98,7 +111,7 @@ impl Mixin { while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector)? { match expr { Expr::AtRule(a) => match a { - AtRule::Content => todo!("@content in mixin"), + AtRule::Content => stmts.extend(self.content.clone()), _ => stmts.push(Stmt::AtRule(a)), }, Expr::Style(s) => stmts.push(Stmt::Style(s)), @@ -146,6 +159,8 @@ pub(crate) fn eat_include>( devour_whitespace(toks); + let mut has_include = false; + let mut args = if let Some(tok) = toks.next() { match tok.kind { TokenKind::Symbol(Symbol::SemiColon) => CallArgs::new(), @@ -153,10 +168,18 @@ pub(crate) fn eat_include>( let tmp = eat_call_args(toks, scope, super_selector)?; devour_whitespace(toks); if let Some(tok) = toks.next() { - assert_eq!(tok.kind, TokenKind::Symbol(Symbol::SemiColon)); + match tok.kind { + TokenKind::Symbol(Symbol::SemiColon) => {} + TokenKind::Symbol(Symbol::OpenCurlyBrace) => has_include = true, + _ => todo!(), + } } tmp } + TokenKind::Symbol(Symbol::OpenCurlyBrace) => { + has_include = true; + CallArgs::new() + } _ => return Err("expected \"{\".".into()), } } else { @@ -165,8 +188,24 @@ pub(crate) fn eat_include>( devour_whitespace(toks); + let content = if let Some(tok) = toks.peek() { + if tok.is_symbol(Symbol::OpenCurlyBrace) { + toks.next(); + eat_stmts(toks, &mut scope.clone(), super_selector)? + } else if has_include { + eat_stmts(toks, &mut scope.clone(), super_selector)? + } else { + Vec::new() + } + } else { + Vec::new() + }; + let mixin = scope.get_mixin(&name)?.clone(); - let rules = mixin.args(&mut args)?.call(super_selector)?; + let rules = mixin + .args(&mut args)? + .content(content) + .call(super_selector)?; Ok(rules) } diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index 339424f..0c6468a 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -3,8 +3,6 @@ use std::iter::Peekable; use num_traits::cast::ToPrimitive; -pub(crate) use function::Function; -pub(crate) use mixin::{eat_include, Mixin}; use crate::common::{Keyword, Pos, Symbol}; use crate::error::SassResult; use crate::scope::Scope; @@ -13,13 +11,15 @@ use crate::unit::Unit; use crate::utils::{devour_whitespace, devour_whitespace_or_comment}; use crate::value::{Number, Value}; use crate::{Stmt, Token, TokenKind}; +pub(crate) use function::Function; +pub(crate) use mixin::{eat_include, Mixin}; use parse::eat_stmts; use unknown::UnknownAtRule; mod function; -mod parse; mod mixin; +mod parse; mod unknown; #[derive(Debug, Clone)] diff --git a/src/builtin/list.rs b/src/builtin/list.rs index fe855e4..a0d76d6 100644 --- a/src/builtin/list.rs +++ b/src/builtin/list.rs @@ -164,7 +164,7 @@ pub(crate) fn register(f: &mut HashMap) { } else { sep1 } - }, + } "comma" => ListSeparator::Comma, "space" => ListSeparator::Space, _ => { diff --git a/src/lib.rs b/src/lib.rs index 65e5e62..6587834 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,7 @@ use std::io::Write; use std::iter::{Iterator, Peekable}; use std::path::Path; -use crate::atrule::{AtRule, AtRuleKind, eat_include, Mixin, Function}; +use crate::atrule::{eat_include, AtRule, AtRuleKind, Function, Mixin}; use crate::common::{Pos, Symbol, Whitespace}; use crate::css::Css; use crate::error::SassError; @@ -637,9 +637,7 @@ pub(crate) fn eat_expr>( AtRule::Warn(a, b) => Ok(Some(Expr::Warn(a, b))), AtRule::Error(pos, err) => Err(SassError::new(err, pos)), AtRule::Return(_) => Err("This at-rule is not allowed here.".into()), - AtRule::Content => { - return Err("@content is only allowed within mixin declarations.".into()) - } + c @ AtRule::Content => Ok(Some(Expr::AtRule(c))), f @ AtRule::If(..) => Ok(Some(Expr::AtRule(f))), f @ AtRule::For(..) => Ok(Some(Expr::AtRule(f))), u @ AtRule::Unknown(..) => Ok(Some(Expr::AtRule(u))), diff --git a/src/scope.rs b/src/scope.rs index ac7d0e8..a063b88 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,8 +1,8 @@ use std::cell::RefCell; use std::collections::HashMap; -use crate::error::SassResult; use crate::atrule::{Function, Mixin}; +use crate::error::SassResult; use crate::value::Value; thread_local!(pub(crate) static GLOBAL_SCOPE: RefCell = RefCell::new(Scope::new())); diff --git a/tests/mixins.rs b/tests/mixins.rs index 7dec5d8..b2dbb1a 100644 --- a/tests/mixins.rs +++ b/tests/mixins.rs @@ -174,3 +174,13 @@ test!( "@mixin foo($x) {\n color: $x;\n}\na {\n @include foo(0px 0px 0px 0px #ef8086 inset !important);\n}\n", "a {\n color: 0px 0px 0px 0px #ef8086 inset !important;\n}\n" ); +test!( + content_without_variable, + "@mixin foo {\n @content;\n}\n\na {\n @include foo {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n" +); +test!( + content_with_variable, + "@mixin foo($a) {\n @content;\n}\n\na {\n @include foo(red) {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n" +);