implement module mixins and meta.load-css
This commit is contained in:
parent
d043167015
commit
d029fd2001
@ -1,21 +1,67 @@
|
|||||||
use crate::{args::FuncArgs, Token};
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
args::{CallArgs, FuncArgs},
|
||||||
|
error::SassResult,
|
||||||
|
parse::{Parser, Stmt},
|
||||||
|
Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) type BuiltinMixin = fn(CallArgs, &mut Parser<'_>) -> SassResult<Vec<Stmt>>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) enum Mixin {
|
||||||
|
UserDefined(UserDefinedMixin),
|
||||||
|
Builtin(BuiltinMixin),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Mixin {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::UserDefined(u) => f
|
||||||
|
.debug_struct("UserDefinedMixin")
|
||||||
|
.field("args", &u.args)
|
||||||
|
.field("body", &u.body)
|
||||||
|
.field("accepts_content_block", &u.accepts_content_block)
|
||||||
|
.field("declared_at_root", &u.declared_at_root)
|
||||||
|
.finish(),
|
||||||
|
Self::Builtin(..) => f.debug_struct("BuiltinMixin").finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mixin {
|
||||||
|
pub fn new_user_defined(
|
||||||
|
args: FuncArgs,
|
||||||
|
body: Vec<Token>,
|
||||||
|
accepts_content_block: bool,
|
||||||
|
declared_at_root: bool,
|
||||||
|
) -> Self {
|
||||||
|
Mixin::UserDefined(UserDefinedMixin::new(
|
||||||
|
args,
|
||||||
|
body,
|
||||||
|
accepts_content_block,
|
||||||
|
declared_at_root,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct Mixin {
|
pub(crate) struct UserDefinedMixin {
|
||||||
pub args: FuncArgs,
|
pub args: FuncArgs,
|
||||||
pub body: Vec<Token>,
|
pub body: Vec<Token>,
|
||||||
pub accepts_content_block: bool,
|
pub accepts_content_block: bool,
|
||||||
pub declared_at_root: bool,
|
pub declared_at_root: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mixin {
|
impl UserDefinedMixin {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
args: FuncArgs,
|
args: FuncArgs,
|
||||||
body: Vec<Token>,
|
body: Vec<Token>,
|
||||||
accepts_content_block: bool,
|
accepts_content_block: bool,
|
||||||
declared_at_root: bool,
|
declared_at_root: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Mixin {
|
Self {
|
||||||
args,
|
args,
|
||||||
body,
|
body,
|
||||||
accepts_content_block,
|
accepts_content_block,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
pub(crate) use function::Function;
|
pub(crate) use function::Function;
|
||||||
pub(crate) use kind::AtRuleKind;
|
pub(crate) use kind::AtRuleKind;
|
||||||
pub(crate) use mixin::{Content, Mixin};
|
|
||||||
pub(crate) use supports::SupportsRule;
|
pub(crate) use supports::SupportsRule;
|
||||||
pub(crate) use unknown::UnknownAtRule;
|
pub(crate) use unknown::UnknownAtRule;
|
||||||
|
|
||||||
@ -8,6 +7,6 @@ mod function;
|
|||||||
pub mod keyframes;
|
pub mod keyframes;
|
||||||
mod kind;
|
mod kind;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
mod mixin;
|
pub mod mixin;
|
||||||
mod supports;
|
mod supports;
|
||||||
mod unknown;
|
mod unknown;
|
||||||
|
@ -8,13 +8,42 @@ use crate::{
|
|||||||
modules::Module,
|
modules::Module,
|
||||||
},
|
},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
parse::Parser,
|
parse::{Parser, Stmt},
|
||||||
value::Value,
|
value::Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn load_css(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn load_css(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Vec<Stmt>> {
|
||||||
args.max_args(2)?;
|
args.max_args(2)?;
|
||||||
todo!()
|
|
||||||
|
// todo: https://github.com/sass/dart-sass/issues/1054
|
||||||
|
let url = match args.get_err(0, "module")? {
|
||||||
|
Value::String(s, ..) => s,
|
||||||
|
v => {
|
||||||
|
return Err((
|
||||||
|
format!("$module: {} is not a string.", v.inspect(args.span())?),
|
||||||
|
args.span(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let with = match args.default_arg(1, "with", Value::Null)? {
|
||||||
|
Value::Map(map) => Some(map),
|
||||||
|
Value::Null => None,
|
||||||
|
v => {
|
||||||
|
return Err((
|
||||||
|
format!("$with: {} is not a map.", v.inspect(args.span())?),
|
||||||
|
args.span(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(with) = with {
|
||||||
|
todo!("`$with` to `load-css` not yet implemented")
|
||||||
|
} else {
|
||||||
|
parser.parse_single_import(&url, args.span())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn module_functions(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn module_functions(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
@ -69,4 +98,6 @@ pub(crate) fn declare(f: &mut Module) {
|
|||||||
f.insert_builtin("module-functions", module_functions);
|
f.insert_builtin("module-functions", module_functions);
|
||||||
f.insert_builtin("get-function", get_function);
|
f.insert_builtin("get-function", get_function);
|
||||||
f.insert_builtin("call", call);
|
f.insert_builtin("call", call);
|
||||||
|
|
||||||
|
f.insert_builtin_mixin("load-css", load_css);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use codemap::{Span, Spanned};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
args::CallArgs,
|
args::CallArgs,
|
||||||
atrule::Mixin,
|
atrule::mixin::{BuiltinMixin, Mixin},
|
||||||
builtin::Builtin,
|
builtin::Builtin,
|
||||||
common::{Identifier, QuoteKind},
|
common::{Identifier, QuoteKind},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
@ -65,6 +65,25 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_mixin(&self, name: Spanned<Identifier>) -> SassResult<Mixin> {
|
||||||
|
if name.node.as_str().starts_with('-') {
|
||||||
|
return Err((
|
||||||
|
"Private members can't be accessed from outside their modules.",
|
||||||
|
name.span,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.0.mixins.get(&name.node) {
|
||||||
|
Some(v) => Ok(v.clone()),
|
||||||
|
None => Err(("Undefined mixin.", name.span).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_builtin_mixin(&mut self, name: &'static str, mixin: BuiltinMixin) {
|
||||||
|
self.0.mixins.insert(name.into(), Mixin::Builtin(mixin));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn insert_builtin_var(&mut self, name: &'static str, value: Value) {
|
pub fn insert_builtin_var(&mut self, name: &'static str, value: Value) {
|
||||||
self.0.vars.insert(name.into(), value);
|
self.0.vars.insert(name.into(), value);
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,11 @@ impl<'a> Parser<'a> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_single_import(&mut self, file_name: &str, span: Span) -> SassResult<Vec<Stmt>> {
|
pub(crate) fn parse_single_import(
|
||||||
|
&mut self,
|
||||||
|
file_name: &str,
|
||||||
|
span: Span,
|
||||||
|
) -> SassResult<Vec<Stmt>> {
|
||||||
let path: &Path = file_name.as_ref();
|
let path: &Path = file_name.as_ref();
|
||||||
|
|
||||||
if let Some(name) = self.find_import(path) {
|
if let Some(name) = self.find_import(path) {
|
||||||
|
@ -6,7 +6,7 @@ use peekmore::PeekMore;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
args::{CallArgs, FuncArgs},
|
args::{CallArgs, FuncArgs},
|
||||||
atrule::{Content, Mixin},
|
atrule::mixin::{Content, Mixin, UserDefinedMixin},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
scope::Scopes,
|
scope::Scopes,
|
||||||
utils::read_until_closing_curly_brace,
|
utils::read_until_closing_curly_brace,
|
||||||
@ -55,7 +55,7 @@ impl<'a> Parser<'a> {
|
|||||||
// this is blocked on figuring out just how to check for this. presumably we could have a check
|
// this is blocked on figuring out just how to check for this. presumably we could have a check
|
||||||
// not when parsing initially, but rather when `@include`ing to see if an `@content` was found.
|
// not when parsing initially, but rather when `@include`ing to see if an `@content` was found.
|
||||||
|
|
||||||
let mixin = Mixin::new(args, body, false, self.at_root);
|
let mixin = Mixin::new_user_defined(args, body, false, self.at_root);
|
||||||
|
|
||||||
if self.at_root {
|
if self.at_root {
|
||||||
self.global_scope.insert_mixin(name, mixin);
|
self.global_scope.insert_mixin(name, mixin);
|
||||||
@ -73,6 +73,19 @@ impl<'a> Parser<'a> {
|
|||||||
self.whitespace_or_comment();
|
self.whitespace_or_comment();
|
||||||
let name = self.parse_identifier()?.map_node(Into::into);
|
let name = self.parse_identifier()?.map_node(Into::into);
|
||||||
|
|
||||||
|
let mixin = if let Some(Token { kind: '.', .. }) = self.toks.peek() {
|
||||||
|
self.toks.next();
|
||||||
|
|
||||||
|
let module = name;
|
||||||
|
let name = self.parse_identifier()?.map_node(Into::into);
|
||||||
|
|
||||||
|
self.modules
|
||||||
|
.get(module.node, module.span)?
|
||||||
|
.get_mixin(name)?
|
||||||
|
} else {
|
||||||
|
self.scopes.get_mixin(name, self.global_scope)?
|
||||||
|
};
|
||||||
|
|
||||||
self.whitespace_or_comment();
|
self.whitespace_or_comment();
|
||||||
|
|
||||||
let args = if let Some(Token { kind: '(', .. }) = self.toks.peek() {
|
let args = if let Some(Token { kind: '(', .. }) = self.toks.peek() {
|
||||||
@ -125,12 +138,17 @@ impl<'a> Parser<'a> {
|
|||||||
self.toks.next();
|
self.toks.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
let Mixin {
|
let UserDefinedMixin {
|
||||||
body,
|
body,
|
||||||
args: fn_args,
|
args: fn_args,
|
||||||
declared_at_root,
|
declared_at_root,
|
||||||
..
|
..
|
||||||
} = self.scopes.get_mixin(name, self.global_scope)?;
|
} = match mixin {
|
||||||
|
Mixin::UserDefined(u) => u,
|
||||||
|
Mixin::Builtin(b) => {
|
||||||
|
return b(args, self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let scope = self.eval_args(fn_args, args)?;
|
let scope = self.eval_args(fn_args, args)?;
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ use crate::{
|
|||||||
atrule::{
|
atrule::{
|
||||||
keyframes::{Keyframes, KeyframesRuleSet},
|
keyframes::{Keyframes, KeyframesRuleSet},
|
||||||
media::MediaRule,
|
media::MediaRule,
|
||||||
AtRuleKind, Content, SupportsRule, UnknownAtRule,
|
mixin::Content,
|
||||||
|
AtRuleKind, SupportsRule, UnknownAtRule,
|
||||||
},
|
},
|
||||||
builtin::modules::{
|
builtin::modules::{
|
||||||
declare_module_color, declare_module_list, declare_module_map, declare_module_math,
|
declare_module_color, declare_module_list, declare_module_map, declare_module_math,
|
||||||
|
@ -3,7 +3,7 @@ use std::collections::BTreeMap;
|
|||||||
use codemap::Spanned;
|
use codemap::Spanned;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
atrule::Mixin,
|
atrule::mixin::Mixin,
|
||||||
builtin::{modules::Module, GLOBAL_FUNCTIONS},
|
builtin::{modules::Module, GLOBAL_FUNCTIONS},
|
||||||
common::Identifier,
|
common::Identifier,
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
|
@ -30,3 +30,36 @@ fn mixin_exists_module() {
|
|||||||
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
|
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_css_simple() {
|
||||||
|
let input = "@use \"sass:meta\";\na {\n @include meta.load-css(load_css_simple);\n}";
|
||||||
|
tempfile!("load_css_simple.scss", "a { color: red; }");
|
||||||
|
assert_eq!(
|
||||||
|
"a a {\n color: red;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_css_explicit_args() {
|
||||||
|
let input = "@use \"sass:meta\";\na {\n @include meta.load-css($module: load_css_explicit_args, $with: null);\n}";
|
||||||
|
tempfile!("load_css_explicit_args.scss", "a { color: red; }");
|
||||||
|
assert_eq!(
|
||||||
|
"a a {\n color: red;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_css_non_string_url() {
|
||||||
|
let input = "@use \"sass:meta\";\na {\n @include meta.load-css(2);\n}";
|
||||||
|
tempfile!("load_css_non_string_url.scss", "a { color: red; }");
|
||||||
|
assert_err!("Error: $module: 2 is not a string.", input);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_css_non_map_with() {
|
||||||
|
let input = "@use \"sass:meta\";\na {\n @include meta.load-css(foo, 2);\n}";
|
||||||
|
assert_err!("Error: $with: 2 is not a map.", input);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user