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