allow @content
to take arguments
This commit is contained in:
parent
4b98ec198d
commit
a88f07da54
@ -23,6 +23,15 @@ impl FuncArgs {
|
||||
pub const fn new() -> Self {
|
||||
FuncArgs(Vec::new())
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -23,3 +23,17 @@ impl Mixin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Content {
|
||||
pub content: Option<Vec<Token>>,
|
||||
pub content_args: Option<FuncArgs>,
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
content: None,
|
||||
content_args: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
pub(crate) use function::Function;
|
||||
pub(crate) use kind::AtRuleKind;
|
||||
pub(crate) use mixin::Mixin;
|
||||
pub(crate) use mixin::{Content, Mixin};
|
||||
pub(crate) use supports::SupportsRule;
|
||||
pub(crate) use unknown::UnknownAtRule;
|
||||
|
||||
|
@ -245,7 +245,7 @@ fn content_exists(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
|
||||
)
|
||||
.into());
|
||||
}
|
||||
Ok(Value::bool(parser.content.is_some()))
|
||||
Ok(Value::bool(parser.content.content.is_some()))
|
||||
}
|
||||
|
||||
pub(crate) fn declare(f: &mut GlobalFunctionMap) {
|
||||
|
@ -102,6 +102,7 @@ use peekmore::PeekMore;
|
||||
pub use crate::error::{SassError as Error, SassResult as Result};
|
||||
pub(crate) use crate::token::Token;
|
||||
use crate::{
|
||||
atrule::Content,
|
||||
lexer::Lexer,
|
||||
output::Css,
|
||||
parse::{common::NeverEmptyVec, Parser},
|
||||
@ -159,7 +160,7 @@ pub fn from_path(p: &str) -> Result<String> {
|
||||
global_scope: &mut Scope::new(),
|
||||
super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)),
|
||||
span_before: empty_span,
|
||||
content: None,
|
||||
content: &Content::new(),
|
||||
in_mixin: false,
|
||||
in_function: false,
|
||||
in_control_flow: false,
|
||||
@ -205,7 +206,7 @@ pub fn from_string(p: String) -> Result<String> {
|
||||
global_scope: &mut Scope::new(),
|
||||
super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)),
|
||||
span_before: empty_span,
|
||||
content: None,
|
||||
content: &Content::new(),
|
||||
in_mixin: false,
|
||||
in_function: false,
|
||||
in_control_flow: false,
|
||||
@ -241,7 +242,7 @@ pub fn from_string(p: String) -> std::result::Result<String, JsValue> {
|
||||
global_scope: &mut Scope::new(),
|
||||
super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)),
|
||||
span_before: empty_span,
|
||||
content: None,
|
||||
content: &Content::new(),
|
||||
in_mixin: false,
|
||||
in_function: false,
|
||||
in_control_flow: false,
|
||||
|
@ -115,6 +115,7 @@ impl<'a> Parser<'a> {
|
||||
self.whitespace();
|
||||
}
|
||||
self.whitespace();
|
||||
// TODO: this should NOT eat the opening curly brace
|
||||
match self.toks.next() {
|
||||
Some(v) if v.kind == '{' => {}
|
||||
Some(..) | None => return Err(("expected \"{\".", close_paren_span).into()),
|
||||
|
@ -4,6 +4,7 @@ use codemap::Spanned;
|
||||
|
||||
use crate::{value::Value, Token};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct NeverEmptyVec<T> {
|
||||
first: T,
|
||||
rest: Vec<T>,
|
||||
|
@ -6,8 +6,9 @@ use peekmore::PeekMore;
|
||||
|
||||
use crate::{
|
||||
args::{CallArgs, FuncArgs},
|
||||
atrule::Mixin,
|
||||
atrule::{Content, Mixin},
|
||||
error::SassResult,
|
||||
scope::Scope,
|
||||
utils::read_until_closing_curly_brace,
|
||||
value::Value,
|
||||
Token,
|
||||
@ -58,58 +59,79 @@ impl<'a> Parser<'a> {
|
||||
|
||||
self.whitespace_or_comment();
|
||||
|
||||
let mut has_content = false;
|
||||
|
||||
let args = match self.toks.next() {
|
||||
Some(Token { kind: ';', .. }) => CallArgs::new(name.span),
|
||||
Some(Token { kind: '(', .. }) => {
|
||||
let tmp = self.parse_call_args()?;
|
||||
self.whitespace_or_comment();
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
';' => {
|
||||
let args = if let Some(Token { kind: '(', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
}
|
||||
'{' => {
|
||||
self.toks.next();
|
||||
has_content = true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
tmp
|
||||
}
|
||||
Some(Token { kind: '{', .. }) => {
|
||||
has_content = true;
|
||||
self.parse_call_args()?
|
||||
} else {
|
||||
CallArgs::new(name.span)
|
||||
}
|
||||
Some(Token { pos, .. }) => return Err(("expected \"{\".", pos).into()),
|
||||
None => return Err(("expected \"{\".", name.span).into()),
|
||||
};
|
||||
|
||||
self.whitespace();
|
||||
self.whitespace_or_comment();
|
||||
|
||||
let content = if has_content {
|
||||
Some(self.parse_content()?)
|
||||
let content_args = if let Some(Token { kind: 'u', .. }) | Some(Token { kind: 'U', .. }) =
|
||||
self.toks.peek()
|
||||
{
|
||||
let mut ident = self.parse_identifier_no_interpolation(false)?;
|
||||
ident.node.make_ascii_lowercase();
|
||||
if ident.node == "using" {
|
||||
self.whitespace_or_comment();
|
||||
if !matches!(self.toks.next(), Some(Token { kind: '(', .. })) {
|
||||
return Err(("expected \"(\".", ident.span).into());
|
||||
}
|
||||
|
||||
Some(self.parse_func_args()?)
|
||||
} else {
|
||||
return Err(("expected keyword \"using\".", ident.span).into());
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut mixin = self.scopes.last().get_mixin(name, self.global_scope)?;
|
||||
self.eval_mixin_args(&mut mixin, args)?;
|
||||
self.whitespace_or_comment();
|
||||
|
||||
let content = if content_args.is_some()
|
||||
|| matches!(self.toks.peek(), Some(Token { kind: '{', .. }))
|
||||
{
|
||||
if matches!(self.toks.peek(), Some(Token { kind: '{', .. })) {
|
||||
self.toks.next();
|
||||
}
|
||||
let mut toks = read_until_closing_curly_brace(self.toks)?;
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
toks.push(*tok);
|
||||
self.toks.next();
|
||||
}
|
||||
Some(toks)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(Token { kind: ';', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
}
|
||||
|
||||
let Mixin {
|
||||
mut scope,
|
||||
body,
|
||||
args: fn_args,
|
||||
..
|
||||
} = self.scopes.last().get_mixin(name, self.global_scope)?;
|
||||
self.eval_args(fn_args, args, &mut scope)?;
|
||||
|
||||
let body = Parser {
|
||||
toks: &mut mixin.body.into_iter().peekmore(),
|
||||
toks: &mut body.into_iter().peekmore(),
|
||||
map: self.map,
|
||||
path: self.path,
|
||||
scopes: &mut NeverEmptyVec::new(mixin.scope),
|
||||
scopes: &mut NeverEmptyVec::new(scope),
|
||||
global_scope: self.global_scope,
|
||||
super_selectors: self.super_selectors,
|
||||
span_before: self.span_before,
|
||||
in_mixin: true,
|
||||
in_function: self.in_function,
|
||||
in_control_flow: self.in_control_flow,
|
||||
content: content.as_deref(),
|
||||
content: &Content {
|
||||
content,
|
||||
content_args,
|
||||
},
|
||||
at_root: false,
|
||||
at_root_has_selector: self.at_root_has_selector,
|
||||
extender: self.extender,
|
||||
@ -119,18 +141,64 @@ impl<'a> Parser<'a> {
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
pub(super) fn parse_content(&mut self) -> SassResult<Vec<Stmt>> {
|
||||
self.parse_stmt()
|
||||
pub(super) fn parse_content_rule(&mut self) -> SassResult<Vec<Stmt>> {
|
||||
if self.in_mixin {
|
||||
let mut scope = self.scopes.last().clone();
|
||||
if let Some(Token { kind: '(', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
let args = self.parse_call_args()?;
|
||||
if let Some(content_args) = self.content.content_args.clone() {
|
||||
args.max_args(content_args.len())?;
|
||||
|
||||
self.eval_args(content_args, args, &mut scope)?;
|
||||
} else {
|
||||
args.max_args(0)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_mixin_args(&mut self, mixin: &mut Mixin, mut args: CallArgs) -> SassResult<()> {
|
||||
Ok(if let Some(content) = &self.content.content {
|
||||
Parser {
|
||||
toks: &mut content.to_vec().into_iter().peekmore(),
|
||||
map: self.map,
|
||||
path: self.path,
|
||||
scopes: &mut NeverEmptyVec::new(scope),
|
||||
global_scope: self.global_scope,
|
||||
super_selectors: self.super_selectors,
|
||||
span_before: self.span_before,
|
||||
in_mixin: false,
|
||||
in_function: self.in_function,
|
||||
in_control_flow: self.in_control_flow,
|
||||
content: self.content,
|
||||
at_root: false,
|
||||
at_root_has_selector: self.at_root_has_selector,
|
||||
extender: self.extender,
|
||||
}
|
||||
.parse()?
|
||||
} else {
|
||||
Vec::new()
|
||||
})
|
||||
} else {
|
||||
Err((
|
||||
"@content is only allowed within mixin declarations.",
|
||||
self.span_before,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_args(
|
||||
&mut self,
|
||||
mut fn_args: FuncArgs,
|
||||
mut args: CallArgs,
|
||||
scope: &mut Scope,
|
||||
) -> SassResult<()> {
|
||||
self.scopes.push(self.scopes.last().clone());
|
||||
for (idx, arg) in mixin.args.0.iter_mut().enumerate() {
|
||||
for (idx, arg) in fn_args.0.iter_mut().enumerate() {
|
||||
if arg.is_variadic {
|
||||
let span = args.span();
|
||||
// todo: does this get the most recent scope?
|
||||
let arg_list = Value::ArgList(self.variadic_args(args)?);
|
||||
mixin.scope.insert_var(
|
||||
scope.insert_var(
|
||||
arg.name.clone(),
|
||||
Spanned {
|
||||
node: arg_list,
|
||||
@ -153,7 +221,7 @@ impl<'a> Parser<'a> {
|
||||
self.scopes
|
||||
.last_mut()
|
||||
.insert_var(arg.name.clone(), val.clone())?;
|
||||
mixin.scope.insert_var(mem::take(&mut arg.name), val)?;
|
||||
scope.insert_var(mem::take(&mut arg.name), val)?;
|
||||
}
|
||||
self.scopes.pop();
|
||||
Ok(())
|
||||
|
@ -5,7 +5,7 @@ use num_traits::cast::ToPrimitive;
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use crate::{
|
||||
atrule::{media::MediaRule, AtRuleKind, SupportsRule, UnknownAtRule},
|
||||
atrule::{media::MediaRule, AtRuleKind, Content, SupportsRule, UnknownAtRule},
|
||||
common::{Brackets, ListSeparator},
|
||||
error::SassResult,
|
||||
scope::Scope,
|
||||
@ -70,7 +70,7 @@ pub(crate) struct Parser<'a> {
|
||||
pub scopes: &'a mut NeverEmptyVec<Scope>,
|
||||
pub super_selectors: &'a mut NeverEmptyVec<Selector>,
|
||||
pub span_before: Span,
|
||||
pub content: Option<&'a [Stmt]>,
|
||||
pub content: &'a Content,
|
||||
pub in_mixin: bool,
|
||||
pub in_function: bool,
|
||||
pub in_control_flow: bool,
|
||||
@ -111,19 +111,7 @@ impl<'a> Parser<'a> {
|
||||
match AtRuleKind::try_from(&kind_string)? {
|
||||
AtRuleKind::Import => stmts.append(&mut self.import()?),
|
||||
AtRuleKind::Mixin => self.parse_mixin()?,
|
||||
AtRuleKind::Content => {
|
||||
if self.in_mixin {
|
||||
if let Some(content) = &self.content {
|
||||
stmts.extend_from_slice(content);
|
||||
}
|
||||
} else {
|
||||
return Err((
|
||||
"@content is only allowed within mixin declarations.",
|
||||
kind_string.span,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
AtRuleKind::Content => stmts.append(&mut self.parse_content_rule()?),
|
||||
AtRuleKind::Include => stmts.append(&mut self.parse_include()?),
|
||||
AtRuleKind::Function => self.parse_function()?,
|
||||
AtRuleKind::Return => {
|
||||
|
@ -247,3 +247,68 @@ test!(
|
||||
"@mixin foo {}\n\na {\n $a: red;\n @include foo;\n color: $a;\n}\n",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
empty_content_args,
|
||||
"@mixin foo {
|
||||
@content()
|
||||
}
|
||||
|
||||
a {
|
||||
@include foo {
|
||||
color: red;
|
||||
};
|
||||
}",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
empty_content_args_using_empty_args,
|
||||
"@mixin foo {
|
||||
@content()
|
||||
}
|
||||
|
||||
a {
|
||||
@include foo using () {
|
||||
color: red;
|
||||
};
|
||||
}",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
content_using_one_arg,
|
||||
"@mixin foo {
|
||||
@content(red)
|
||||
}
|
||||
|
||||
a {
|
||||
@include foo using ($a) {
|
||||
color: $a;
|
||||
}
|
||||
}",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
error!(
|
||||
content_using_too_many_args,
|
||||
"@mixin foo {
|
||||
@content(red, blue)
|
||||
}
|
||||
|
||||
a {
|
||||
@include foo using ($a) {
|
||||
color: $a;
|
||||
}
|
||||
}",
|
||||
"Error: Only 1 argument allowed, but 2 were passed."
|
||||
);
|
||||
error!(
|
||||
content_using_too_few_args,
|
||||
"@mixin foo {
|
||||
@content()
|
||||
}
|
||||
|
||||
a {
|
||||
@include foo using ($a) {
|
||||
color: $a;
|
||||
}
|
||||
}",
|
||||
"Error: Missing argument $a."
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user