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)]
|
||||
pub(crate) struct Mixin {
|
||||
pub(crate) struct UserDefinedMixin {
|
||||
pub args: FuncArgs,
|
||||
pub body: Vec<Token>,
|
||||
pub accepts_content_block: bool,
|
||||
pub declared_at_root: bool,
|
||||
}
|
||||
|
||||
impl Mixin {
|
||||
impl UserDefinedMixin {
|
||||
pub fn new(
|
||||
args: FuncArgs,
|
||||
body: Vec<Token>,
|
||||
accepts_content_block: bool,
|
||||
declared_at_root: bool,
|
||||
) -> Self {
|
||||
Mixin {
|
||||
Self {
|
||||
args,
|
||||
body,
|
||||
accepts_content_block,
|
||||
|
@ -1,6 +1,5 @@
|
||||
pub(crate) use function::Function;
|
||||
pub(crate) use kind::AtRuleKind;
|
||||
pub(crate) use mixin::{Content, Mixin};
|
||||
pub(crate) use supports::SupportsRule;
|
||||
pub(crate) use unknown::UnknownAtRule;
|
||||
|
||||
@ -8,6 +7,6 @@ mod function;
|
||||
pub mod keyframes;
|
||||
mod kind;
|
||||
pub mod media;
|
||||
mod mixin;
|
||||
pub mod mixin;
|
||||
mod supports;
|
||||
mod unknown;
|
||||
|
@ -8,13 +8,42 @@ use crate::{
|
||||
modules::Module,
|
||||
},
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
parse::{Parser, Stmt},
|
||||
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)?;
|
||||
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> {
|
||||
@ -69,4 +98,6 @@ pub(crate) fn declare(f: &mut Module) {
|
||||
f.insert_builtin("module-functions", module_functions);
|
||||
f.insert_builtin("get-function", get_function);
|
||||
f.insert_builtin("call", call);
|
||||
|
||||
f.insert_builtin_mixin("load-css", load_css);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use codemap::{Span, Spanned};
|
||||
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
atrule::Mixin,
|
||||
atrule::mixin::{BuiltinMixin, Mixin},
|
||||
builtin::Builtin,
|
||||
common::{Identifier, QuoteKind},
|
||||
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) {
|
||||
self.0.vars.insert(name.into(), value);
|
||||
}
|
||||
|
@ -77,7 +77,11 @@ impl<'a> Parser<'a> {
|
||||
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();
|
||||
|
||||
if let Some(name) = self.find_import(path) {
|
||||
|
@ -6,7 +6,7 @@ use peekmore::PeekMore;
|
||||
|
||||
use crate::{
|
||||
args::{CallArgs, FuncArgs},
|
||||
atrule::{Content, Mixin},
|
||||
atrule::mixin::{Content, Mixin, UserDefinedMixin},
|
||||
error::SassResult,
|
||||
scope::Scopes,
|
||||
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
|
||||
// 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 {
|
||||
self.global_scope.insert_mixin(name, mixin);
|
||||
@ -73,6 +73,19 @@ impl<'a> Parser<'a> {
|
||||
self.whitespace_or_comment();
|
||||
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();
|
||||
|
||||
let args = if let Some(Token { kind: '(', .. }) = self.toks.peek() {
|
||||
@ -125,12 +138,17 @@ impl<'a> Parser<'a> {
|
||||
self.toks.next();
|
||||
}
|
||||
|
||||
let Mixin {
|
||||
let UserDefinedMixin {
|
||||
body,
|
||||
args: fn_args,
|
||||
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)?;
|
||||
|
||||
|
@ -7,7 +7,8 @@ use crate::{
|
||||
atrule::{
|
||||
keyframes::{Keyframes, KeyframesRuleSet},
|
||||
media::MediaRule,
|
||||
AtRuleKind, Content, SupportsRule, UnknownAtRule,
|
||||
mixin::Content,
|
||||
AtRuleKind, SupportsRule, UnknownAtRule,
|
||||
},
|
||||
builtin::modules::{
|
||||
declare_module_color, declare_module_list, declare_module_map, declare_module_math,
|
||||
|
@ -3,7 +3,7 @@ use std::collections::BTreeMap;
|
||||
use codemap::Spanned;
|
||||
|
||||
use crate::{
|
||||
atrule::Mixin,
|
||||
atrule::mixin::Mixin,
|
||||
builtin::{modules::Module, GLOBAL_FUNCTIONS},
|
||||
common::Identifier,
|
||||
error::SassResult,
|
||||
|
@ -30,3 +30,36 @@ fn mixin_exists_module() {
|
||||
&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