refactor parsing of unknown at rules

This commit is contained in:
Connor Skees 2021-07-24 22:21:11 -04:00
parent 3ab2aa961a
commit 86a1ffec42
6 changed files with 173 additions and 27 deletions

View File

@ -6,4 +6,8 @@ pub(crate) struct UnknownAtRule {
pub super_selector: Selector, pub super_selector: Selector,
pub params: String, pub params: String,
pub body: Vec<Stmt>, pub body: Vec<Stmt>,
/// Whether or not this @-rule was declared with curly
/// braces. A body may not necessarily have contents
pub has_body: bool,
} }

View File

@ -21,6 +21,15 @@ struct ToplevelUnknownAtRule {
name: String, name: String,
params: String, params: String,
body: Vec<Stmt>, body: Vec<Stmt>,
has_body: bool,
is_group_end: bool,
inside_rule: bool,
}
#[derive(Debug, Clone)]
struct BlockEntryUnknownAtRule {
name: String,
params: String,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -65,6 +74,7 @@ impl Toplevel {
pub fn is_group_end(&self) -> bool { pub fn is_group_end(&self) -> bool {
match self { match self {
Toplevel::RuleSet { is_group_end, .. } => *is_group_end, Toplevel::RuleSet { is_group_end, .. } => *is_group_end,
Toplevel::UnknownAtRule(t) => t.is_group_end && t.inside_rule,
Toplevel::Media { Toplevel::Media {
inside_rule, inside_rule,
is_group_end, is_group_end,
@ -87,6 +97,7 @@ fn set_group_end(group: &mut [Toplevel]) {
| Some(Toplevel::Media { is_group_end, .. }) => { | Some(Toplevel::Media { is_group_end, .. }) => {
*is_group_end = true; *is_group_end = true;
} }
Some(Toplevel::UnknownAtRule(t)) => t.is_group_end = true,
_ => {} _ => {}
} }
} }
@ -95,6 +106,7 @@ fn set_group_end(group: &mut [Toplevel]) {
enum BlockEntry { enum BlockEntry {
Style(Style), Style(Style),
MultilineComment(String), MultilineComment(String),
UnknownAtRule(BlockEntryUnknownAtRule),
} }
impl BlockEntry { impl BlockEntry {
@ -102,6 +114,13 @@ impl BlockEntry {
match self { match self {
BlockEntry::Style(s) => s.to_string(), BlockEntry::Style(s) => s.to_string(),
BlockEntry::MultilineComment(s) => Ok(format!("/*{}*/", s)), BlockEntry::MultilineComment(s) => Ok(format!("/*{}*/", s)),
BlockEntry::UnknownAtRule(BlockEntryUnknownAtRule { name, params }) => {
Ok(if params.is_empty() {
format!("@{};", name)
} else {
format!("@{} {};", name, params)
})
}
} }
} }
} }
@ -138,6 +157,17 @@ impl Toplevel {
panic!(); panic!();
} }
} }
fn push_unknown_at_rule(&mut self, at_rule: ToplevelUnknownAtRule) {
if let Toplevel::RuleSet { body, .. } = self {
body.push(BlockEntry::UnknownAtRule(BlockEntryUnknownAtRule {
name: at_rule.name,
params: at_rule.params,
}));
} else {
panic!();
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -206,13 +236,27 @@ impl Css {
} }
Stmt::UnknownAtRule(u) => { Stmt::UnknownAtRule(u) => {
let UnknownAtRule { let UnknownAtRule {
params, body, name, .. params,
body,
name,
has_body,
..
} = *u; } = *u;
vals.push(Toplevel::UnknownAtRule(Box::new(ToplevelUnknownAtRule {
let at_rule = ToplevelUnknownAtRule {
name, name,
params, params,
body, body,
}))); has_body,
inside_rule: true,
is_group_end: false,
};
if has_body {
vals.push(Toplevel::UnknownAtRule(Box::new(at_rule)));
} else {
vals.first_mut().unwrap().push_unknown_at_rule(at_rule);
}
} }
Stmt::Return(..) => unreachable!(), Stmt::Return(..) => unreachable!(),
Stmt::AtRoot { body } => { Stmt::AtRoot { body } => {
@ -267,12 +311,19 @@ impl Css {
} }
Stmt::UnknownAtRule(u) => { Stmt::UnknownAtRule(u) => {
let UnknownAtRule { let UnknownAtRule {
params, body, name, .. params,
body,
name,
has_body,
..
} = *u; } = *u;
vec![Toplevel::UnknownAtRule(Box::new(ToplevelUnknownAtRule { vec![Toplevel::UnknownAtRule(Box::new(ToplevelUnknownAtRule {
name, name,
params, params,
body, body,
has_body,
inside_rule: false,
is_group_end: false,
}))] }))]
} }
Stmt::Return(..) => unreachable!("@return: {:?}", stmt), Stmt::Return(..) => unreachable!("@return: {:?}", stmt),
@ -393,7 +444,9 @@ impl Formatter for CompressedFormatter {
write!(buf, "@import {};", s)?; write!(buf, "@import {};", s)?;
} }
Toplevel::UnknownAtRule(u) => { Toplevel::UnknownAtRule(u) => {
let ToplevelUnknownAtRule { params, name, body } = *u; let ToplevelUnknownAtRule {
params, name, body, ..
} = *u;
if params.is_empty() { if params.is_empty() {
write!(buf, "@{}", name)?; write!(buf, "@{}", name)?;
@ -494,6 +547,7 @@ impl CompressedFormatter {
break; break;
} }
BlockEntry::MultilineComment(..) => continue, BlockEntry::MultilineComment(..) => continue,
b @ BlockEntry::UnknownAtRule(_) => write!(buf, "{}", b.to_string()?)?,
} }
} }
@ -505,6 +559,7 @@ impl CompressedFormatter {
write!(buf, ";{}:{}", s.property, value)?; write!(buf, ";{}:{}", s.property, value)?;
} }
BlockEntry::MultilineComment(..) => continue, BlockEntry::MultilineComment(..) => continue,
b @ BlockEntry::UnknownAtRule(_) => write!(buf, "{}", b.to_string()?)?,
} }
} }
Ok(()) Ok(())
@ -593,7 +648,14 @@ impl Formatter for ExpandedFormatter {
write!(buf, "{}@import {};", padding, s)?; write!(buf, "{}@import {};", padding, s)?;
} }
Toplevel::UnknownAtRule(u) => { Toplevel::UnknownAtRule(u) => {
let ToplevelUnknownAtRule { params, name, body } = *u; let ToplevelUnknownAtRule {
params,
name,
body,
has_body,
inside_rule,
..
} = *u;
if params.is_empty() { if params.is_empty() {
write!(buf, "{}@{}", padding, name)?; write!(buf, "{}@{}", padding, name)?;
@ -601,14 +663,29 @@ impl Formatter for ExpandedFormatter {
write!(buf, "{}@{} {}", padding, name, params)?; write!(buf, "{}@{} {}", padding, name, params)?;
} }
if body.is_empty() { let css = Css::from_stmts(
body,
if inside_rule {
AtRuleContext::Unknown
} else {
AtRuleContext::None
},
css.allows_charset,
)?;
if !has_body {
write!(buf, ";")?; write!(buf, ";")?;
prev = Some(Previous { is_group_end }); prev = Some(Previous { is_group_end });
continue; continue;
} }
if css.blocks.iter().all(Toplevel::is_invisible) {
write!(buf, " {{}}")?;
prev = Some(Previous { is_group_end });
continue;
}
writeln!(buf, " {{")?; writeln!(buf, " {{")?;
let css = Css::from_stmts(body, AtRuleContext::Unknown, css.allows_charset)?;
self.write_css(buf, css, map)?; self.write_css(buf, css, map)?;
write!(buf, "\n{}}}", padding)?; write!(buf, "\n{}}}", padding)?;
} }

View File

@ -218,7 +218,7 @@ impl<'a, 'b> Parser<'a, 'b> {
if let Some(ref content_args) = content.content_args { if let Some(ref content_args) = content.content_args {
call_args.max_args(content_args.len())?; call_args.max_args(content_args.len())?;
let scope = self.eval_args(&content_args, call_args)?; let scope = self.eval_args(content_args, call_args)?;
scope_at_decl.enter_scope(scope); scope_at_decl.enter_scope(scope);
entered_scope = true; entered_scope = true;
} else { } else {

View File

@ -618,36 +618,47 @@ impl<'a, 'b> Parser<'a, 'b> {
let mut params = String::new(); let mut params = String::new();
self.whitespace_or_comment(); self.whitespace_or_comment();
if let Some(Token { kind: ';', .. }) | None = self.toks.peek() {
self.toks.next(); loop {
return Ok(Stmt::UnknownAtRule(Box::new(UnknownAtRule { match self.toks.peek() {
name, Some(Token { kind: '{', .. }) => {
super_selector: Selector::new(self.span_before), self.toks.next();
params: String::new(), break;
body: Vec::new(), }
}))); Some(Token { kind: ';', .. }) | Some(Token { kind: '}', .. }) | None => {
} self.consume_char_if_exists(';');
while let Some(tok) = self.toks.next() { return Ok(Stmt::UnknownAtRule(Box::new(UnknownAtRule {
match tok.kind { name,
'{' => break, super_selector: Selector::new(self.span_before),
'#' => { has_body: false,
params: params.trim().to_owned(),
body: Vec::new(),
})));
}
Some(Token { kind: '#', .. }) => {
self.toks.next();
if let Some(Token { kind: '{', pos }) = self.toks.peek() { if let Some(Token { kind: '{', pos }) = self.toks.peek() {
self.span_before = self.span_before.merge(pos); self.span_before = self.span_before.merge(pos);
self.toks.next(); self.toks.next();
params.push_str(&self.parse_interpolation_as_string()?); params.push_str(&self.parse_interpolation_as_string()?);
} else { } else {
params.push(tok.kind); params.push('#');
} }
continue; continue;
} }
'\n' | ' ' | '\t' => { Some(Token { kind: '\n', .. })
| Some(Token { kind: ' ', .. })
| Some(Token { kind: '\t', .. }) => {
self.whitespace(); self.whitespace();
params.push(' '); params.push(' ');
continue; continue;
} }
_ => {} Some(Token { kind, .. }) => {
self.toks.next();
params.push(kind);
}
} }
params.push(tok.kind);
} }
let raw_body = self.parse_stmt()?; let raw_body = self.parse_stmt()?;
@ -680,6 +691,7 @@ impl<'a, 'b> Parser<'a, 'b> {
name, name,
super_selector: Selector::new(self.span_before), super_selector: Selector::new(self.span_before),
params: params.trim().to_owned(), params: params.trim().to_owned(),
has_body: true,
body, body,
}))) })))
} }

View File

@ -12,6 +12,7 @@ test!(
"@foo {\n color: red;\n}\n" "@foo {\n color: red;\n}\n"
); );
test!(unknown_at_rule_no_body, "@foo;\n", "@foo;\n"); test!(unknown_at_rule_no_body, "@foo;\n", "@foo;\n");
test!(unknown_at_rule_empty_body, "@foo {}\n", "@foo {}\n");
test!(unknown_at_rule_no_body_eof, "@foo", "@foo;\n"); test!(unknown_at_rule_no_body_eof, "@foo", "@foo;\n");
test!( test!(
unknown_at_rule_interpolated_eof_no_body, unknown_at_rule_interpolated_eof_no_body,
@ -32,4 +33,56 @@ test!(
}", }",
"@foo (a: b) {\n a {\n color: red;\n }\n}\na {\n color: green;\n}\n" "@foo (a: b) {\n a {\n color: red;\n }\n}\na {\n color: green;\n}\n"
); );
test!(
no_semicolon_no_params_no_body,
"a {
@b
}
a {
color: red;
}",
"a {\n @b;\n}\n\na {\n color: red;\n}\n"
);
test!(
no_semicolon_has_params_no_body,
"a {
@foo bar
}
a {
color: red;
}",
"a {\n @foo bar;\n}\n\na {\n color: red;\n}\n"
);
test!(
no_body_remains_inside_style_rule,
"a {
@box-shadow: $btn-focus-box-shadow, $btn-active-box-shadow;
}
a {
color: red;
}",
"a {\n @box-shadow : $btn-focus-box-shadow, $btn-active-box-shadow;\n}\n\na {\n color: red;\n}\n"
);
test!(
empty_body_moves_outside_style_rule,
"a {
@b {}
}
a {
color: red;
}",
"@b {}\n\na {\n color: red;\n}\n"
);
test!(
#[ignore = "not sure how dart-sass is parsing this to include the semicolon in the params"]
params_contain_silent_comment_and_semicolon,
"a {
@box-shadow: $btn-focus-box-shadow, // $btn-active-box-shadow;
}",
"a {\n @box-shadow : $btn-focus-box-shadow, / $btn-active-box-shadow;\n}\n"
);
test!(contains_multiline_comment, "@foo /**/;\n", "@foo;\n"); test!(contains_multiline_comment, "@foo /**/;\n", "@foo;\n");