diff --git a/src/atrule.rs b/src/atrule.rs index 12beaac..effdd2e 100644 --- a/src/atrule.rs +++ b/src/atrule.rs @@ -37,6 +37,7 @@ impl AtRule { pos: Pos, toks: &mut Peekable, scope: &Scope, + super_selector: &Selector, ) -> SassResult { devour_whitespace(toks); Ok(match rule { @@ -116,7 +117,22 @@ impl AtRule { params.push_str(&tok.kind.to_string()); } - let body = eat_unknown_atrule_body(toks, scope, &Selector::new())?; + let raw_body = eat_unknown_atrule_body(toks, scope, super_selector)?; + let mut body = Vec::with_capacity(raw_body.len()); + body.push(Stmt::RuleSet(RuleSet::new())); + let mut rules = Vec::new(); + for stmt in raw_body { + match stmt { + s @ Stmt::Style(..) => rules.push(s), + s => body.push(s), + } + } + + body[0] = Stmt::RuleSet(RuleSet { + selector: super_selector.clone(), + rules, + super_selector: Selector::new(), + }); let u = UnknownAtRule { name: name.clone(), @@ -140,6 +156,7 @@ fn eat_unknown_atrule_body>( let mut stmts = Vec::new(); while let Some(expr) = eat_expr(toks, scope, super_selector)? { match expr { + Expr::AtRule(a) => stmts.push(Stmt::AtRule(a)), Expr::Style(s) => stmts.push(Stmt::Style(s)), Expr::Styles(s) => stmts.extend(s.into_iter().map(Stmt::Style)), Expr::Include(s) => stmts.extend(s), diff --git a/src/css.rs b/src/css.rs index 766b4b4..7022ac9 100644 --- a/src/css.rs +++ b/src/css.rs @@ -82,7 +82,7 @@ impl Css { .get_mut(0) .expect("expected block to exist") .push_comment(s), - Stmt::AtRule(_) => todo!("at rule inside css block"), + Stmt::AtRule(r) => vals.push(Toplevel::AtRule(r)), }; } vals diff --git a/src/function.rs b/src/function.rs index 580994f..9c624ed 100644 --- a/src/function.rs +++ b/src/function.rs @@ -4,6 +4,7 @@ use crate::args::{eat_func_args, CallArgs, FuncArgs}; use crate::atrule::AtRule; use crate::common::{Scope, Symbol}; use crate::error::SassResult; +use crate::selector::Selector; use crate::utils::devour_whitespace; use crate::value::Value; use crate::{Token, TokenKind}; @@ -47,9 +48,13 @@ impl Function { while nesting > 0 { if let Some(tok) = toks.next() { match &tok.kind { - TokenKind::AtRule(rule) => { - body.push(AtRule::from_tokens(rule, tok.pos, toks, scope)?) - } + TokenKind::AtRule(rule) => body.push(AtRule::from_tokens( + rule, + tok.pos, + toks, + scope, + &Selector::new(), + )?), TokenKind::Symbol(Symbol::CloseCurlyBrace) => nesting -= 1, _ => {} } diff --git a/src/lib.rs b/src/lib.rs index 3af2cd9..c4b8324 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -213,6 +213,16 @@ pub(crate) struct RuleSet { super_selector: Selector, } +impl RuleSet { + pub(crate) fn new() -> RuleSet { + RuleSet { + selector: Selector::new(), + rules: Vec::new(), + super_selector: Selector::new(), + } + } +} + /// An intermediate representation of what are essentially single lines /// todo! rename this #[derive(Clone, Debug)] @@ -236,6 +246,7 @@ enum Expr { MultilineComment(String), Debug(Pos, String), Warn(Pos, String), + AtRule(AtRule), // /// Function call: `calc(10vw - 1px)` // FuncCall(String, Vec), } @@ -437,7 +448,7 @@ impl<'a> StyleSheetParser<'a> { pos, }) = self.lexer.next() { - match AtRule::from_tokens(rule, pos, &mut self.lexer, &self.global_scope)? { + match AtRule::from_tokens(rule, pos, &mut self.lexer, &self.global_scope, &Selector::new())? { AtRule::Mixin(name, mixin) => { self.global_scope.insert_mixin(&name, *mixin); } @@ -476,7 +487,7 @@ impl<'a> StyleSheetParser<'a> { while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector)? { match expr { Expr::Style(s) => stmts.push(Stmt::Style(s)), - #[allow(clippy::redundant_closure)] + Expr::AtRule(s) => stmts.push(Stmt::AtRule(s)), Expr::Styles(s) => stmts.extend(s.into_iter().map(Stmt::Style)), Expr::MixinDecl(name, mixin) => { scope.insert_mixin(&name, *mixin); @@ -623,7 +634,7 @@ pub(crate) fn eat_expr>( pos, }) = toks.next() { - return match AtRule::from_tokens(rule, pos, toks, scope)? { + return match AtRule::from_tokens(rule, pos, toks, scope, &super_selector)? { AtRule::Mixin(name, mixin) => Ok(Some(Expr::MixinDecl(name, mixin))), AtRule::Function(name, func) => Ok(Some(Expr::FunctionDecl(name, func))), AtRule::Charset(_) => todo!("@charset as expr"), @@ -631,7 +642,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(_) => todo!("@return in unexpected location!"), - AtRule::Unknown(..) => todo!("nested media queries not yet implemented"), + u @ AtRule::Unknown(..) => Ok(Some(Expr::AtRule(u))), }; } } diff --git a/src/mixin.rs b/src/mixin.rs index f1ee5e6..44abdc4 100644 --- a/src/mixin.rs +++ b/src/mixin.rs @@ -94,6 +94,7 @@ impl Mixin { let mut stmts = Vec::new(); while let Some(expr) = eat_expr(&mut self.body, &self.scope, super_selector)? { match expr { + Expr::AtRule(a) => stmts.push(Stmt::AtRule(a)), Expr::Style(s) => stmts.push(Stmt::Style(s)), Expr::Styles(s) => stmts.extend(s.into_iter().map(Stmt::Style)), Expr::Include(s) => stmts.extend(s), diff --git a/src/selector.rs b/src/selector.rs index a2eb235..d1f6edb 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -335,6 +335,8 @@ impl Selector { pub fn zip(&self, other: &Selector) -> Selector { if self.0.is_empty() { return Selector(other.0.clone()); + } else if other.0.is_empty() { + return self.clone(); } let mut rules: Vec = Vec::with_capacity(self.0.len() + other.0.len()); let sel1_split: Vec<&[SelectorKind]> = diff --git a/tests/media.rs b/tests/media.rs index 62cc723..b6f32aa 100644 --- a/tests/media.rs +++ b/tests/media.rs @@ -7,3 +7,8 @@ test!( basic_toplevel, "@media foo {\n a {\n color: red;\n }\n}\n" ); +test!( + basic_nested, + "a {\n @media foo {\n color: red;\n }\n}\n", + "@media foo {\n a {\n color: red;\n }\n}\n" +);