separate media and unknown at rules

This commit is contained in:
ConnorSkees 2020-05-20 20:13:53 -04:00
parent d61f8abd41
commit d39a45090a
9 changed files with 165 additions and 20 deletions

View File

@ -41,6 +41,8 @@ pub enum AtRuleKind {
For,
While,
Media,
// CSS @rules
/// Defines the character set used by the style sheet
Charset,
@ -78,6 +80,7 @@ impl From<&str> for AtRuleKind {
"supports" => Self::Supports,
"keyframes" => Self::Keyframes,
"content" => Self::Content,
"media" => Self::Media,
s => Self::Unknown(s.to_owned()),
}
}

87
src/atrule/media.rs Normal file
View File

@ -0,0 +1,87 @@
use codemap::{Span, Spanned};
use peekmore::PeekMoreIterator;
use super::parse::ruleset_eval;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::utils::{devour_whitespace, parse_interpolation};
use crate::{RuleSet, Stmt, Token};
#[derive(Debug, Clone)]
pub(crate) struct Media {
pub super_selector: Selector,
pub params: String,
pub body: Vec<Spanned<Stmt>>,
}
impl Media {
pub fn from_tokens<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>,
scope: &mut Scope,
super_selector: &Selector,
kind_span: Span,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Media> {
let mut params = String::new();
while let Some(tok) = toks.next() {
match tok.kind {
'{' => break,
'#' => {
if toks.peek().unwrap().kind == '{' {
toks.next();
let interpolation = parse_interpolation(toks, scope, super_selector)?;
params.push_str(&interpolation.node.to_css_string(interpolation.span)?);
continue;
} else {
params.push(tok.kind);
}
}
'\n' | ' ' | '\t' => {
devour_whitespace(toks);
params.push(' ');
continue;
}
_ => {}
}
params.push(tok.kind);
}
if params.is_empty() {
return Err(("Expected identifier.", kind_span).into());
}
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();
for stmt in raw_body {
match stmt.node {
Stmt::Style(..) => body.push(stmt),
_ => rules.push(stmt),
}
}
if super_selector.is_empty() {
body.append(&mut rules);
} else {
body = vec![Spanned {
node: Stmt::RuleSet(RuleSet {
selector: super_selector.clone(),
rules: body,
super_selector: Selector::new(),
}),
span: kind_span,
}];
body.append(&mut rules);
}
Ok(Media {
super_selector: Selector::new(),
params: params.trim().to_owned(),
body,
})
}
}

View File

@ -17,6 +17,7 @@ use for_rule::For;
pub(crate) use function::Function;
pub(crate) use if_rule::If;
pub(crate) use kind::AtRuleKind;
use media::Media;
pub(crate) use mixin::{eat_include, Mixin};
use parse::{eat_stmts, eat_stmts_at_root, ruleset_eval};
use unknown::UnknownAtRule;
@ -27,6 +28,7 @@ mod for_rule;
mod function;
mod if_rule;
mod kind;
mod media;
mod mixin;
mod parse;
mod unknown;
@ -47,12 +49,13 @@ pub(crate) enum AtRule {
While(While),
Include(Vec<Spanned<Stmt>>),
If(If),
Media(Media),
AtRoot(Vec<Spanned<Stmt>>),
}
impl AtRule {
pub fn from_tokens<I: Iterator<Item = Token>>(
rule: &AtRuleKind,
rule: AtRuleKind,
kind_span: Span,
toks: &mut PeekMoreIterator<I>,
scope: &mut Scope,
@ -246,6 +249,16 @@ impl AtRule {
)?),
span: kind_span,
},
AtRuleKind::Media => Spanned {
node: AtRule::Media(Media::from_tokens(
toks,
scope,
super_selector,
kind_span,
content,
)?),
span: kind_span,
},
AtRuleKind::Import => todo!("@import not yet implemented"),
AtRuleKind::Forward => todo!("@forward not yet implemented"),
AtRuleKind::Supports => todo!("@supports not yet implemented"),

View File

@ -20,13 +20,23 @@ pub(crate) struct UnknownAtRule {
impl UnknownAtRule {
pub fn from_tokens<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>,
name: &str,
name: String,
scope: &mut Scope,
super_selector: &Selector,
kind_span: Span,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<UnknownAtRule> {
let mut params = String::new();
devour_whitespace(toks);
if let Some(Token { kind: ';', .. }) | None = toks.peek() {
toks.next();
return Ok(UnknownAtRule {
name,
super_selector: Selector::new(),
params: String::new(),
body: Vec::new(),
});
}
while let Some(tok) = toks.next() {
match tok.kind {
'{' => break,
@ -77,7 +87,7 @@ impl UnknownAtRule {
}
Ok(UnknownAtRule {
name: name.to_owned(),
name,
super_selector: Selector::new(),
params: params.trim().to_owned(),
body,

View File

@ -341,7 +341,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
let Spanned { node: ident, span } = eat_ident(toks, scope, super_selector, span)?;
devour_whitespace(toks);
let rule = AtRule::from_tokens(
&AtRuleKind::from(ident.as_str()),
AtRuleKind::from(ident.as_str()),
span,
toks,
scope,
@ -364,6 +364,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
u @ AtRule::Unknown(..) => Expr::AtRule(u),
u @ AtRule::AtRoot(..) => Expr::AtRule(u),
u @ AtRule::Include(..) => Expr::AtRule(u),
u @ AtRule::Media(..) => Expr::AtRule(u),
},
span,
}));

View File

@ -185,15 +185,29 @@ impl Css {
Toplevel::AtRule(r) => {
match r {
AtRule::Unknown(u) => {
if u.params.is_empty() {
write!(buf, "{}@{}", padding, u.name)?;
} else {
write!(buf, "{}@{} {}", padding, u.name, u.params)?;
}
if u.body.is_empty() {
writeln!(buf, ";")?;
continue;
} else {
writeln!(buf, " {{")?;
}
Css::from_stylesheet(StyleSheet::from_stmts(u.body))?
._inner_pretty_print(buf, map, nesting + 1)?;
writeln!(buf, "{}}}", padding)?;
}
AtRule::Media(m) => {
if m.body.is_empty() {
continue;
}
if u.params.is_empty() {
writeln!(buf, "{}@{} {{", padding, u.name)?;
} else {
writeln!(buf, "{}@{} {} {{", padding, u.name, u.params)?;
}
Css::from_stylesheet(StyleSheet::from_stmts(u.body))?
writeln!(buf, "{}@media {} {{", padding, m.params)?;
Css::from_stylesheet(StyleSheet::from_stmts(m.body))?
._inner_pretty_print(buf, map, nesting + 1)?;
writeln!(buf, "{}}}", padding)?;
}

View File

@ -251,7 +251,7 @@ impl<'a> StyleSheetParser<'a> {
});
}
v => {
let rule = AtRule::from_tokens(&v, span, &mut self.lexer, &mut Scope::new(), &Selector::new(), None)?;
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);
@ -281,6 +281,7 @@ impl<'a> StyleSheetParser<'a> {
}
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
AtRule::Unknown(..) => rules.push(rule.map_node(Stmt::AtRule)),
AtRule::Media(..) => rules.push(rule.map_node(Stmt::AtRule)),
}
}
}
@ -339,6 +340,10 @@ impl<'a> StyleSheetParser<'a> {
node: Stmt::AtRule(r),
span,
}),
r @ AtRule::Media(..) => stmts.push(Spanned {
node: Stmt::AtRule(r),
span,
}),
},
Expr::Styles(s) => stmts.extend(
s.into_iter()

View File

@ -7,18 +7,13 @@ test!(
basic_toplevel,
"@media foo {\n a {\n color: red;\n }\n}\n"
);
test!(
toplevel_no_params,
"@media {\n a {\n color: red;\n }\n}\n"
error!(
no_params,
"@media {\n a {\n color: red;\n }\n}\n", "Error: Expected identifier."
);
test!(
basic_nested,
"a {\n @media foo {\n color: red;\n }\n}\n",
"@media foo {\n a {\n color: red;\n }\n}\n"
);
test!(
basic_unknown_at_rule,
"@foo {\n a {\n color: red;\n }\n}\n"
);
test!(unknown_at_rule_no_selector, "@foo {\n color: red;\n}\n");
test!(empty, "@media (min-width: 2px) {}", "");
test!(empty_body, "@media (min-width: 2px) {}", "");

17
tests/unknown-at-rule.rs Normal file
View File

@ -0,0 +1,17 @@
#![cfg(test)]
#[macro_use]
mod macros;
test!(
basic_unknown_at_rule,
"@foo {\n a {\n color: red;\n }\n}\n"
);
test!(unknown_at_rule_no_selector, "@foo {\n color: red;\n}\n");
test!(unknown_at_rule_no_body, "@foo;\n");
test!(unknown_at_rule_no_body_eof, "@foo", "@foo;\n");
test!(
unknown_at_rule_interpolated_eof_no_body,
"@#{()if(0,0<0,0)}",
"@false;\n"
);