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

@ -1,3 +1,3 @@
body {
background: red;
}
}

View File

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

View File

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

View File

@ -12,6 +12,7 @@ test!(
"@foo {\n color: red;\n}\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_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"
);
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");