mixins and functions can use their module's scope
This commit is contained in:
parent
c86b5f82e9
commit
1f672c4c49
@ -283,7 +283,7 @@ pub(crate) fn call(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>
|
||||
.into())
|
||||
}
|
||||
};
|
||||
func.call(args.decrement(), parser)
|
||||
func.call(args.decrement(), None, parser)
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
|
@ -14,8 +14,16 @@ use crate::{
|
||||
use super::{common::ContextFlags, Parser, Stmt};
|
||||
|
||||
/// Names that functions are not allowed to have
|
||||
const RESERVED_IDENTIFIERS: [&str; 7] =
|
||||
["calc", "element", "expression", "url", "and", "or", "not"];
|
||||
const RESERVED_IDENTIFIERS: [&str; 8] = [
|
||||
"calc",
|
||||
"element",
|
||||
"expression",
|
||||
"url",
|
||||
"and",
|
||||
"or",
|
||||
"not",
|
||||
"clamp",
|
||||
];
|
||||
|
||||
impl<'a, 'b> Parser<'a, 'b> {
|
||||
pub(super) fn parse_function(&mut self) -> SassResult<()> {
|
||||
@ -56,16 +64,15 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
|
||||
let name_as_ident = Identifier::from(name);
|
||||
|
||||
let sass_function = SassFunction::UserDefined {
|
||||
function: Box::new(function),
|
||||
name: name_as_ident,
|
||||
};
|
||||
|
||||
if self.at_root {
|
||||
self.global_scope.insert_fn(
|
||||
name_as_ident,
|
||||
SassFunction::UserDefined(Box::new(function), name_as_ident),
|
||||
);
|
||||
self.global_scope.insert_fn(name_as_ident, sass_function);
|
||||
} else {
|
||||
self.scopes.insert_fn(
|
||||
name_as_ident,
|
||||
SassFunction::UserDefined(Box::new(function), name_as_ident),
|
||||
);
|
||||
self.scopes.insert_fn(name_as_ident, sass_function);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -78,7 +85,12 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
Ok(Box::new(v.node))
|
||||
}
|
||||
|
||||
pub fn eval_function(&mut self, function: Function, args: CallArgs) -> SassResult<Value> {
|
||||
pub fn eval_function(
|
||||
&mut self,
|
||||
function: Function,
|
||||
args: CallArgs,
|
||||
module: Option<Spanned<Identifier>>,
|
||||
) -> SassResult<Value> {
|
||||
let Function {
|
||||
body,
|
||||
args: fn_args,
|
||||
@ -97,6 +109,16 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
self.scopes.enter_scope(scope);
|
||||
};
|
||||
|
||||
if let Some(module) = module {
|
||||
let module = self.modules.get(module.node, module.span)?;
|
||||
|
||||
if declared_at_root {
|
||||
new_scope.enter_scope(module.scope.clone());
|
||||
} else {
|
||||
self.scopes.enter_scope(module.scope.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut return_value = Parser {
|
||||
toks: &mut Lexer::new(body),
|
||||
map: self.map,
|
||||
@ -125,6 +147,10 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
self.scopes.exit_scope();
|
||||
}
|
||||
|
||||
if module.is_some() {
|
||||
self.scopes.exit_scope();
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
return_value.len() <= 1,
|
||||
"we expect there to be only one return value"
|
||||
|
@ -73,15 +73,18 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
self.whitespace_or_comment();
|
||||
let name = self.parse_identifier()?.map_node(Into::into);
|
||||
|
||||
let mixin = if self.consume_char_if_exists('.') {
|
||||
let (mixin, module) = if self.consume_char_if_exists('.') {
|
||||
let module = name;
|
||||
let name = self.parse_identifier()?.map_node(Into::into);
|
||||
|
||||
self.modules
|
||||
.get(module.node, module.span)?
|
||||
.get_mixin(name)?
|
||||
(
|
||||
self.modules
|
||||
.get(module.node, module.span)?
|
||||
.get_mixin(name)?,
|
||||
Some(module),
|
||||
)
|
||||
} else {
|
||||
self.scopes.get_mixin(name, self.global_scope)?
|
||||
(self.scopes.get_mixin(name, self.global_scope)?, None)
|
||||
};
|
||||
|
||||
self.whitespace_or_comment();
|
||||
@ -152,6 +155,11 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
|
||||
self.scopes.enter_scope(scope);
|
||||
|
||||
if let Some(module) = module {
|
||||
let module = self.modules.get(module.node, module.span)?;
|
||||
self.scopes.enter_scope(module.scope.clone());
|
||||
}
|
||||
|
||||
self.content.push(Content {
|
||||
content,
|
||||
content_args,
|
||||
@ -180,6 +188,11 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
.parse_stmt()?;
|
||||
|
||||
self.content.pop();
|
||||
|
||||
if module.is_some() {
|
||||
self.scopes.exit_scope();
|
||||
}
|
||||
|
||||
self.scopes.exit_scope();
|
||||
|
||||
if declared_at_root {
|
||||
|
@ -7,7 +7,7 @@ use num_traits::Zero;
|
||||
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
common::{Op, QuoteKind},
|
||||
common::{Identifier, Op, QuoteKind},
|
||||
error::SassResult,
|
||||
unit::Unit,
|
||||
value::{SassFunction, Value},
|
||||
@ -19,7 +19,7 @@ use super::super::Parser;
|
||||
pub(crate) enum HigherIntermediateValue {
|
||||
Literal(Value),
|
||||
/// A function that hasn't yet been evaluated
|
||||
Function(SassFunction, CallArgs),
|
||||
Function(SassFunction, CallArgs, Option<Spanned<Identifier>>),
|
||||
BinaryOp(Box<Self>, Op, Box<Self>),
|
||||
UnaryOp(Op, Box<Self>),
|
||||
}
|
||||
@ -31,8 +31,13 @@ impl HigherIntermediateValue {
|
||||
}
|
||||
|
||||
impl<'a, 'b> Parser<'a, 'b> {
|
||||
fn call_function(&mut self, function: SassFunction, args: CallArgs) -> SassResult<Value> {
|
||||
function.call(args, self)
|
||||
fn call_function(
|
||||
&mut self,
|
||||
function: SassFunction,
|
||||
args: CallArgs,
|
||||
module: Option<Spanned<Identifier>>,
|
||||
) -> SassResult<Value> {
|
||||
function.call(args, module, self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,8 +59,8 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> {
|
||||
HigherIntermediateValue::Literal(v) => Ok(v),
|
||||
HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2, in_parens),
|
||||
HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val, in_parens),
|
||||
HigherIntermediateValue::Function(function, args) => {
|
||||
self.parser.call_function(function, args)
|
||||
HigherIntermediateValue::Function(function, args, module) => {
|
||||
self.parser.call_function(function, args, module)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,8 +221,8 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> {
|
||||
HigherIntermediateValue::UnaryOp(op, val) => {
|
||||
HigherIntermediateValue::Literal(self.unary_op(op, *val, in_parens)?)
|
||||
}
|
||||
HigherIntermediateValue::Function(function, args) => {
|
||||
HigherIntermediateValue::Literal(self.parser.call_function(function, args)?)
|
||||
HigherIntermediateValue::Function(function, args, module) => {
|
||||
HigherIntermediateValue::Literal(self.parser.call_function(function, args, module)?)
|
||||
}
|
||||
val => val,
|
||||
})
|
||||
@ -301,7 +306,6 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> {
|
||||
.into());
|
||||
}
|
||||
if unit == unit2 {
|
||||
// dbg!(&num, &num2, num.clone() + num2.clone(), unit.clone(), unit2.clone());
|
||||
Value::Dimension(Some(num + num2), unit, true)
|
||||
} else if unit == Unit::None {
|
||||
Value::Dimension(Some(num + num2), unit2, true)
|
||||
|
@ -198,8 +198,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
#[allow(clippy::eval_order_dependence)]
|
||||
fn parse_module_item(
|
||||
&mut self,
|
||||
module: &str,
|
||||
mut module_span: Span,
|
||||
mut module: Spanned<Identifier>,
|
||||
) -> SassResult<Spanned<IntermediateValue>> {
|
||||
Ok(
|
||||
IntermediateValue::Value(if self.consume_char_if_exists('$') {
|
||||
@ -207,9 +206,9 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
.parse_identifier_no_interpolation(false)?
|
||||
.map_node(|i| i.into());
|
||||
|
||||
module_span = module_span.merge(var.span);
|
||||
module.span = module.span.merge(var.span);
|
||||
|
||||
let value = self.modules.get(module.into(), module_span)?.get_var(var)?;
|
||||
let value = self.modules.get(module.node, module.span)?.get_var(var)?;
|
||||
HigherIntermediateValue::Literal(value.clone())
|
||||
} else {
|
||||
let fn_name = self
|
||||
@ -218,7 +217,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
|
||||
let function = self
|
||||
.modules
|
||||
.get(module.into(), module_span)?
|
||||
.get(module.node, module.span)?
|
||||
.get_fn(fn_name)?
|
||||
.ok_or(("Undefined function.", fn_name.span))?;
|
||||
|
||||
@ -226,9 +225,9 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
|
||||
let call_args = self.parse_call_args()?;
|
||||
|
||||
HigherIntermediateValue::Function(function, call_args)
|
||||
HigherIntermediateValue::Function(function, call_args, Some(module))
|
||||
})
|
||||
.span(module_span),
|
||||
.span(module.span),
|
||||
)
|
||||
}
|
||||
|
||||
@ -260,6 +259,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
return Ok(IntermediateValue::Value(HigherIntermediateValue::Function(
|
||||
SassFunction::Builtin(f.clone(), as_ident),
|
||||
self.parse_call_args()?,
|
||||
None,
|
||||
))
|
||||
.span(self.span_before));
|
||||
}
|
||||
@ -297,7 +297,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
|
||||
let call_args = self.parse_call_args()?;
|
||||
Ok(
|
||||
IntermediateValue::Value(HigherIntermediateValue::Function(func, call_args))
|
||||
IntermediateValue::Value(HigherIntermediateValue::Function(func, call_args, None))
|
||||
.span(self.span_before),
|
||||
)
|
||||
}
|
||||
@ -336,7 +336,10 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
Some(Token { kind: '.', .. }) => {
|
||||
if !predicate(self) {
|
||||
self.toks.next();
|
||||
return self.parse_module_item(&s, span);
|
||||
return self.parse_module_item(Spanned {
|
||||
node: s.into(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||
/// A singular scope
|
||||
///
|
||||
/// Contains variables, functions, and mixins
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub(crate) struct Scope {
|
||||
pub vars: BTreeMap<Identifier, Value>,
|
||||
pub mixins: BTreeMap<Identifier, Mixin>,
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use codemap::Spanned;
|
||||
|
||||
use crate::{
|
||||
args::CallArgs, atrule::Function, builtin::Builtin, common::Identifier, error::SassResult,
|
||||
parse::Parser, value::Value,
|
||||
@ -25,7 +27,10 @@ use crate::{
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub(crate) enum SassFunction {
|
||||
Builtin(Builtin, Identifier),
|
||||
UserDefined(Box<Function>, Identifier),
|
||||
UserDefined {
|
||||
function: Box<Function>,
|
||||
name: Identifier,
|
||||
},
|
||||
}
|
||||
|
||||
impl SassFunction {
|
||||
@ -34,7 +39,7 @@ impl SassFunction {
|
||||
/// Used mainly in debugging and `inspect()`
|
||||
pub fn name(&self) -> &Identifier {
|
||||
match self {
|
||||
Self::Builtin(_, name) | Self::UserDefined(_, name) => name,
|
||||
Self::Builtin(_, name) | Self::UserDefined { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,14 +49,19 @@ impl SassFunction {
|
||||
fn kind(&self) -> &'static str {
|
||||
match &self {
|
||||
Self::Builtin(..) => "Builtin",
|
||||
Self::UserDefined(..) => "UserDefined",
|
||||
Self::UserDefined { .. } => "UserDefined",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(self, args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
|
||||
pub fn call(
|
||||
self,
|
||||
args: CallArgs,
|
||||
module: Option<Spanned<Identifier>>,
|
||||
parser: &mut Parser,
|
||||
) -> SassResult<Value> {
|
||||
match self {
|
||||
Self::Builtin(f, ..) => f.0(args, parser),
|
||||
Self::UserDefined(f, ..) => parser.eval_function(*f, args),
|
||||
Self::UserDefined { function, .. } => parser.eval_function(*function, args, module),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
49
tests/use.rs
49
tests/use.rs
@ -369,6 +369,55 @@ fn use_modules_imported_by_other_modules_does_not_cause_conflict() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_mixin_can_use_scope_from_own_module() {
|
||||
let input = r#"
|
||||
@use "use_mixin_can_use_scope_from_own_module__a" as a;
|
||||
@include a.foo();
|
||||
"#;
|
||||
|
||||
tempfile!(
|
||||
"use_mixin_can_use_scope_from_own_module__a.scss",
|
||||
"$a: red;
|
||||
|
||||
@mixin foo() {
|
||||
a {
|
||||
color: $a;
|
||||
}
|
||||
}"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"a {\n color: red;\n}\n",
|
||||
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_function_can_use_scope_from_own_module() {
|
||||
let input = r#"
|
||||
@use "use_function_can_use_scope_from_own_module__a" as a;
|
||||
|
||||
a {
|
||||
color: a.foo();
|
||||
}
|
||||
"#;
|
||||
|
||||
tempfile!(
|
||||
"use_function_can_use_scope_from_own_module__a.scss",
|
||||
"$a: red;
|
||||
|
||||
@function foo() {
|
||||
@return $a;
|
||||
}"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"a {\n color: red;\n}\n",
|
||||
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_variable_redeclaration_builtin() {
|
||||
let input = "@use \"sass:math\";\nmath.$e: red;";
|
||||
|
Loading…
x
Reference in New Issue
Block a user