allow @content in more contexts

This commit is contained in:
ConnorSkees 2020-04-26 21:29:09 -04:00
parent 6f57797c29
commit 3615835e03
11 changed files with 128 additions and 59 deletions

View File

@ -26,6 +26,7 @@ impl Each {
self,
scope: &mut Scope,
super_selector: &Selector,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
for row in self.iter {
@ -77,6 +78,7 @@ impl Each {
scope,
super_selector,
false,
content,
&mut stmts,
)?;
}

View File

@ -32,6 +32,7 @@ impl For {
self,
scope: &mut Scope,
super_selector: &Selector,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
for i in self.iter {
@ -47,6 +48,7 @@ impl For {
scope,
super_selector,
false,
content,
&mut stmts,
)?;
}

View File

@ -112,6 +112,7 @@ impl Function {
&mut self.scope,
super_selector,
false,
None,
)
}
@ -153,6 +154,7 @@ impl Function {
&mut self.scope,
super_selector,
false,
None,
)?;
if let Some(v) = self.call(super_selector, for_stmts)? {
return Ok(Some(v));
@ -160,7 +162,7 @@ impl Function {
}
}
Stmt::AtRule(AtRule::If(i)) => {
let if_stmts = i.eval(&mut self.scope, super_selector)?;
let if_stmts = i.eval(&mut self.scope, super_selector, None)?;
if let Some(v) = self.call(super_selector, if_stmts)? {
return Ok(Some(v));
}
@ -174,6 +176,7 @@ impl Function {
scope,
super_selector,
false,
None,
)?;
if let Some(v) = self.call(super_selector, while_stmts)? {
return Ok(Some(v));

View File

@ -105,6 +105,7 @@ impl If {
self,
scope: &mut Scope,
super_selector: &Selector,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
let mut toks = Vec::new();
@ -125,6 +126,7 @@ impl If {
scope,
super_selector,
false,
content,
&mut stmts,
)?;
Ok(stmts)

View File

@ -22,23 +22,12 @@ pub(crate) struct Mixin {
scope: Scope,
args: FuncArgs,
body: PeekMoreIterator<IntoIter<Token>>,
content: Vec<Spanned<Stmt>>,
}
impl Mixin {
pub fn new(
scope: Scope,
args: FuncArgs,
body: Vec<Token>,
content: Vec<Spanned<Stmt>>,
) -> Self {
pub fn new(scope: Scope, args: FuncArgs, body: Vec<Token>) -> Self {
let body = body.into_iter().peekmore();
Mixin {
scope,
args,
body,
content,
}
Mixin { scope, args, body }
}
pub fn decl_from_tokens<I: Iterator<Item = Token>>(
@ -62,16 +51,11 @@ impl Mixin {
body.push(toks.next().unwrap());
Ok(Spanned {
node: (name, Mixin::new(scope.clone(), args, body, Vec::new())),
node: (name, Mixin::new(scope.clone(), args, body)),
span,
})
}
pub fn content(mut self, content: Vec<Spanned<Stmt>>) -> Mixin {
self.content = content;
self
}
pub fn args(
mut self,
mut args: CallArgs,
@ -113,28 +97,43 @@ impl Mixin {
Ok(self)
}
pub fn call(mut self, super_selector: &Selector) -> SassResult<Vec<Spanned<Stmt>>> {
self.eval(super_selector)
pub fn call(
mut self,
super_selector: &Selector,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> {
self.eval(super_selector, content)
}
fn eval(&mut self, super_selector: &Selector) -> SassResult<Vec<Spanned<Stmt>>> {
fn eval(
&mut self,
super_selector: &Selector,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector)? {
while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector, content)? {
let span = expr.span;
match expr.node {
Expr::AtRule(a) => match a {
AtRule::For(f) => {
stmts.extend(f.ruleset_eval(&mut self.scope, super_selector)?)
stmts.extend(f.ruleset_eval(&mut self.scope, super_selector, content)?)
}
AtRule::Each(e) => {
stmts.extend(e.ruleset_eval(&mut self.scope, super_selector)?)
}
AtRule::While(w) => {
stmts.extend(w.ruleset_eval(&mut self.scope, super_selector, false)?)
stmts.extend(e.ruleset_eval(&mut self.scope, super_selector, content)?)
}
AtRule::While(w) => stmts.extend(w.ruleset_eval(
&mut self.scope,
super_selector,
false,
content,
)?),
AtRule::Include(s) => stmts.extend(s),
AtRule::If(i) => stmts.extend(i.eval(&mut self.scope.clone(), super_selector)?),
AtRule::Content => stmts.extend(self.content.clone()),
AtRule::If(i) => {
stmts.extend(i.eval(&mut self.scope.clone(), super_selector, content)?)
}
AtRule::Content => {
stmts.extend(content.unwrap_or_default().into_iter().cloned());
}
AtRule::Return(..) => {
return Err(("This at-rule is not allowed here.", span).into())
}
@ -161,7 +160,7 @@ impl Mixin {
return Err(("Mixins may not contain mixin declarations.", span).into())
}
Expr::Selector(selector) => {
let rules = self.eval(&super_selector.zip(&selector))?;
let rules = self.eval(&super_selector.zip(&selector), content)?;
stmts.push(Spanned {
node: Stmt::RuleSet(RuleSet {
super_selector: super_selector.clone(),
@ -188,6 +187,7 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>,
scope: &Scope,
super_selector: &Selector,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> {
devour_whitespace_or_comment(toks)?;
let name = eat_ident(toks, scope, super_selector)?;
@ -223,7 +223,7 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
devour_whitespace(toks);
let mut content = Vec::new();
let mut this_content = Vec::new();
if let Some(tok) = toks.peek() {
if tok.kind == '{' {
@ -233,7 +233,8 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
&mut scope.clone(),
super_selector,
false,
&mut content,
content,
&mut this_content,
)?;
} else if has_content {
ruleset_eval(
@ -241,7 +242,8 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
&mut scope.clone(),
super_selector,
false,
&mut content,
content,
&mut this_content,
)?;
}
}
@ -250,7 +252,6 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
let rules = mixin
.args(args, scope, super_selector)?
.content(content)
.call(super_selector)?;
.call(super_selector, Some(&this_content))?;
Ok(rules)
}

View File

@ -57,6 +57,7 @@ impl AtRule {
toks: &mut PeekMoreIterator<I>,
scope: &mut Scope,
super_selector: &Selector,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Spanned<AtRule>> {
devour_whitespace(toks);
Ok(match rule {
@ -170,6 +171,7 @@ impl AtRule {
selector,
0,
is_some,
content,
)?
.into_iter()
.filter_map(|s| match s.node {
@ -225,6 +227,7 @@ impl AtRule {
scope,
super_selector,
kind_span,
content,
)?),
span: kind_span,
},
@ -233,7 +236,7 @@ impl AtRule {
span: kind_span,
},
AtRuleKind::Include => Spanned {
node: AtRule::Include(eat_include(toks, scope, super_selector)?),
node: AtRule::Include(eat_include(toks, scope, super_selector, content)?),
span: kind_span,
},
AtRuleKind::Import => todo!("@import not yet implemented"),

View File

@ -14,9 +14,10 @@ pub(crate) fn eat_stmts<I: Iterator<Item = Token>>(
scope: &mut Scope,
super_selector: &Selector,
at_root: bool,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
while let Some(expr) = eat_expr(toks, scope, super_selector)? {
while let Some(expr) = eat_expr(toks, scope, super_selector, content)? {
let span = expr.span;
match expr.node {
Expr::AtRule(a) => stmts.push(Stmt::AtRule(a).span(span)),
@ -29,7 +30,13 @@ pub(crate) fn eat_stmts<I: Iterator<Item = Token>>(
),
Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(),
Expr::Selector(selector) => {
let rules = eat_stmts(toks, scope, &super_selector.zip(&selector), at_root)?;
let rules = eat_stmts(
toks,
scope,
&super_selector.zip(&selector),
at_root,
content,
)?;
stmts.push(
Stmt::RuleSet(RuleSet {
super_selector: super_selector.clone(),
@ -58,9 +65,10 @@ pub(crate) fn eat_stmts_at_root<I: Iterator<Item = Token>>(
super_selector: &Selector,
mut nesting: usize,
is_some: bool,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
while let Some(expr) = eat_expr(toks, scope, super_selector)? {
while let Some(expr) = eat_expr(toks, scope, super_selector, content)? {
let span = expr.span;
match expr.node {
Expr::AtRule(a) => stmts.push(Stmt::AtRule(a).span(span)),
@ -79,7 +87,7 @@ pub(crate) fn eat_stmts_at_root<I: Iterator<Item = Token>>(
selector = Selector::replace(super_selector, selector);
}
nesting += 1;
let rules = eat_stmts_at_root(toks, scope, &selector, nesting, true)?;
let rules = eat_stmts_at_root(toks, scope, &selector, nesting, true, content)?;
nesting -= 1;
stmts.push(
Stmt::RuleSet(RuleSet {
@ -108,18 +116,34 @@ pub(crate) fn ruleset_eval<I: Iterator<Item = Token>>(
scope: &mut Scope,
super_selector: &Selector,
at_root: bool,
content: Option<&[Spanned<Stmt>]>,
stmts: &mut Vec<Spanned<Stmt>>,
) -> SassResult<()> {
for stmt in eat_stmts(toks, scope, super_selector, at_root)? {
for stmt in eat_stmts(toks, scope, super_selector, at_root, content)? {
match stmt.node {
Stmt::AtRule(AtRule::For(f)) => stmts.extend(f.ruleset_eval(scope, super_selector)?),
Stmt::AtRule(AtRule::Each(e)) => stmts.extend(e.ruleset_eval(scope, super_selector)?),
Stmt::AtRule(AtRule::For(f)) => {
stmts.extend(f.ruleset_eval(scope, super_selector, content)?)
}
Stmt::AtRule(AtRule::Each(e)) => {
stmts.extend(e.ruleset_eval(scope, super_selector, content)?)
}
Stmt::AtRule(AtRule::While(w)) => {
// TODO: should at_root be false? scoping
stmts.extend(w.ruleset_eval(scope, super_selector, at_root)?)
stmts.extend(w.ruleset_eval(scope, super_selector, at_root, content)?)
}
Stmt::AtRule(AtRule::Include(s)) => stmts.extend(s),
Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?),
Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector, content)?),
Stmt::AtRule(AtRule::Content) => {
if let Some(c) = content {
stmts.extend(c.into_iter().cloned());
} else {
return Err((
"@content is only allowed within mixin declarations.",
stmt.span,
)
.into());
}
}
_ => stmts.push(stmt),
}
}

View File

@ -2,7 +2,7 @@ use codemap::{Span, Spanned};
use peekmore::PeekMoreIterator;
use super::parse::eat_stmts;
use super::parse::ruleset_eval;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
@ -24,6 +24,7 @@ impl UnknownAtRule {
scope: &mut Scope,
super_selector: &Selector,
kind_span: Span,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<UnknownAtRule> {
let mut params = String::new();
while let Some(tok) = toks.next() {
@ -49,7 +50,8 @@ impl UnknownAtRule {
params.push(tok.kind);
}
let raw_body = eat_stmts(toks, scope, super_selector, false)?;
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();

View File

@ -25,6 +25,7 @@ impl While {
scope: &mut Scope,
super_selector: &Selector,
at_root: bool,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
let mut val = Value::from_vec(self.cond.clone(), scope, super_selector)?;
@ -35,6 +36,7 @@ impl While {
scope,
super_selector,
at_root,
content,
&mut stmts,
)?;
val = Value::from_vec(self.cond.clone(), scope, super_selector)?;

View File

@ -359,6 +359,7 @@ impl<'a> StyleSheetParser<'a> {
&mut self.lexer,
&Scope::new(),
&Selector::new(),
None,
)?),
AtRuleKind::Import => {
devour_whitespace(&mut self.lexer);
@ -394,7 +395,7 @@ impl<'a> StyleSheetParser<'a> {
});
}
v => {
let rule = AtRule::from_tokens(&v, span, &mut self.lexer, &mut Scope::new(), &Selector::new())?;
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);
@ -410,17 +411,17 @@ impl<'a> StyleSheetParser<'a> {
("This at-rule is not allowed here.", rule.span).into()
)
}
AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new())?),
AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true)?),
AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?),
AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true, None)?),
AtRule::Each(e) => {
rules.extend(e.ruleset_eval(&mut Scope::new(), &Selector::new())?)
rules.extend(e.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?)
}
AtRule::Include(s) => rules.extend(s),
AtRule::Content => return Err(
("@content is only allowed within mixin declarations.", rule.span
).into()),
AtRule::If(i) => {
rules.extend(i.eval(&mut Scope::new(), &Selector::new())?);
rules.extend(i.eval(&mut Scope::new(), &Selector::new(), None)?);
}
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
AtRule::Unknown(..) => rules.push(rule.map_node(Stmt::AtRule)),
@ -448,7 +449,7 @@ impl<'a> StyleSheetParser<'a> {
scope: &mut Scope,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector)? {
while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector, None)? {
let span = expr.span;
match expr.node {
Expr::Style(s) => stmts.push(Spanned {
@ -456,13 +457,13 @@ impl<'a> StyleSheetParser<'a> {
span,
}),
Expr::AtRule(a) => match a {
AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector)?),
AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector, None)?),
AtRule::While(w) => {
stmts.extend(w.ruleset_eval(scope, super_selector, false)?)
stmts.extend(w.ruleset_eval(scope, super_selector, false, None)?)
}
AtRule::Each(e) => stmts.extend(e.ruleset_eval(scope, super_selector)?),
AtRule::Each(e) => stmts.extend(e.ruleset_eval(scope, super_selector, None)?),
AtRule::Include(s) => stmts.extend(s),
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?),
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector, None)?),
AtRule::Content => {
return Err((
"@content is only allowed within mixin declarations.",
@ -533,6 +534,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>,
scope: &mut Scope,
super_selector: &Selector,
content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Option<Spanned<Expr>>> {
let mut values = Vec::with_capacity(5);
let mut span = if let Some(tok) = toks.peek() {
@ -689,6 +691,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
toks,
scope,
super_selector,
content,
)?;
return Ok(Some(Spanned {
node: match rule.node {

View File

@ -204,3 +204,28 @@ test!(
"@mixin foo {\n @content;\n}\n\na {\n @include foo {@if true {color: red;}}\n}\n",
"a {\n color: red;\n}\n"
);
test!(
content_in_control_flow,
"@mixin foo() {\n @if true {\n @content;\n }\n}\n\na {\n @include foo {\n color: red;\n }\n}\n",
"a {\n color: red;\n}\n"
);
test!(
content_inside_unknown_at_rule,
"@mixin foo() {\n @foo (max-width: max) {\n @content;\n }\n}\n\na {\n @include foo {\n color: red;\n }\n}\n",
"@foo (max-width: max) {\n a {\n color: red;\n }\n}\n"
);
test!(
content_inside_media,
"@mixin foo() {\n @media (max-width: max) {\n @content;\n }\n}\n\na {\n @include foo {\n color: red;\n }\n}\n",
"@media (max-width: max) {\n a {\n color: red;\n }\n}\n"
);
error!(
function_inside_mixin,
"@mixin foo() {\n @function bar() {\n @return foo;\n }\n}\n\na {\n @include foo {\n color: red;\n }\n}\n",
"Error: Mixins may not contain function declarations."
);
error!(
content_inside_control_flow_outside_mixin,
"a {\n @if true {\n @content;\n }\n}\n",
"Error: @content is only allowed within mixin declarations."
);