grass_compiler/builtin/functions/
meta.rs

1use crate::builtin::builtin_imports::*;
2
3// todo: this should be a constant of some sort. we shouldn't be allocating this
4// every time
5pub(crate) fn if_arguments() -> ArgumentDeclaration {
6    ArgumentDeclaration {
7        args: vec![
8            Argument {
9                name: Identifier::from("condition"),
10                default: None,
11            },
12            Argument {
13                name: Identifier::from("if-true"),
14                default: None,
15            },
16            Argument {
17                name: Identifier::from("if-false"),
18                default: None,
19            },
20        ],
21        rest: None,
22    }
23}
24
25fn if_(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
26    args.max_args(3)?;
27    if args.get_err(0, "condition")?.is_truthy() {
28        Ok(args.get_err(1, "if-true")?)
29    } else {
30        Ok(args.get_err(2, "if-false")?)
31    }
32}
33
34pub(crate) fn feature_exists(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
35    args.max_args(1)?;
36    let feature = args
37        .get_err(0, "feature")?
38        .assert_string_with_name("feature", args.span())?
39        .0;
40
41    #[allow(clippy::match_same_arms)]
42    Ok(match feature.as_str() {
43        // A local variable will shadow a global variable unless
44        // `!global` is used.
45        "global-variable-shadowing" => Value::True,
46        // the @extend rule will affect selectors nested in pseudo-classes
47        // like :not()
48        "extend-selector-pseudoclass" => Value::True,
49        // Full support for unit arithmetic using units defined in the
50        // [Values and Units Level 3][] spec.
51        "units-level-3" => Value::True,
52        // The Sass `@error` directive is supported.
53        "at-error" => Value::True,
54        // The "Custom Properties Level 1" spec is supported. This means
55        // that custom properties are parsed statically, with only
56        // interpolation treated as SassScript.
57        "custom-property" => Value::True,
58        _ => Value::False,
59    })
60}
61
62pub(crate) fn unit(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
63    args.max_args(1)?;
64
65    let number = args
66        .get_err(0, "number")?
67        .assert_number_with_name("number", args.span())?;
68
69    Ok(Value::String(number.unit.to_string(), QuoteKind::Quoted))
70}
71
72pub(crate) fn type_of(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
73    args.max_args(1)?;
74    let value = args.get_err(0, "value")?;
75    Ok(Value::String(value.kind().to_owned(), QuoteKind::None))
76}
77
78pub(crate) fn unitless(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
79    args.max_args(1)?;
80    let number = args
81        .get_err(0, "number")?
82        .assert_number_with_name("number", args.span())?;
83
84    Ok(Value::bool(number.unit == Unit::None))
85}
86
87pub(crate) fn inspect(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
88    args.max_args(1)?;
89    Ok(Value::String(
90        args.get_err(0, "value")?.inspect(args.span())?,
91        QuoteKind::None,
92    ))
93}
94
95pub(crate) fn variable_exists(
96    mut args: ArgumentResult,
97    visitor: &mut Visitor,
98) -> SassResult<Value> {
99    args.max_args(1)?;
100
101    let name = Identifier::from(
102        args.get_err(0, "name")?
103            .assert_string_with_name("name", args.span())?
104            .0,
105    );
106
107    Ok(Value::bool(visitor.env.var_exists(name, None)?))
108}
109
110pub(crate) fn global_variable_exists(
111    mut args: ArgumentResult,
112    visitor: &mut Visitor,
113) -> SassResult<Value> {
114    args.max_args(2)?;
115
116    let name = Identifier::from(
117        args.get_err(0, "name")?
118            .assert_string_with_name("name", args.span())?
119            .0,
120    );
121
122    let module = match args.default_arg(1, "module", Value::Null) {
123        Value::String(s, _) => Some(s),
124        Value::Null => None,
125        v => {
126            return Err((
127                format!("$module: {} is not a string.", v.inspect(args.span())?),
128                args.span(),
129            )
130                .into())
131        }
132    };
133
134    Ok(Value::bool(if let Some(module_name) = module {
135        (*(*visitor.env.modules)
136            .borrow()
137            .get(module_name.into(), args.span())?)
138        .borrow()
139        .var_exists(name)
140    } else {
141        (*visitor.env.global_vars()).borrow().contains_key(&name)
142    }))
143}
144
145pub(crate) fn mixin_exists(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
146    args.max_args(2)?;
147    let name = Identifier::from(
148        args.get_err(0, "name")?
149            .assert_string_with_name("name", args.span())?
150            .0,
151    );
152
153    let module = match args.default_arg(1, "module", Value::Null) {
154        Value::String(s, _) => Some(s),
155        Value::Null => None,
156        v => {
157            return Err((
158                format!("$module: {} is not a string.", v.inspect(args.span())?),
159                args.span(),
160            )
161                .into())
162        }
163    };
164
165    Ok(Value::bool(if let Some(module_name) = module {
166        (*(*visitor.env.modules)
167            .borrow()
168            .get(module_name.into(), args.span())?)
169        .borrow()
170        .mixin_exists(name)
171    } else {
172        visitor.env.mixin_exists(name)
173    }))
174}
175
176pub(crate) fn function_exists(
177    mut args: ArgumentResult,
178    visitor: &mut Visitor,
179) -> SassResult<Value> {
180    args.max_args(2)?;
181
182    let name = Identifier::from(
183        args.get_err(0, "name")?
184            .assert_string_with_name("name", args.span())?
185            .0,
186    );
187
188    let module = match args.default_arg(1, "module", Value::Null) {
189        Value::String(s, _) => Some(s),
190        Value::Null => None,
191        v => {
192            return Err((
193                format!("$module: {} is not a string.", v.inspect(args.span())?),
194                args.span(),
195            )
196                .into())
197        }
198    };
199
200    Ok(Value::bool(if let Some(module_name) = module {
201        (*(*visitor.env.modules)
202            .borrow()
203            .get(module_name.into(), args.span())?)
204        .borrow()
205        .fn_exists(name)
206    } else {
207        visitor.env.fn_exists(name)
208    }))
209}
210
211pub(crate) fn get_function(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
212    args.max_args(3)?;
213    let name: Identifier = match args.get_err(0, "name")? {
214        Value::String(s, _) => s.into(),
215        v => {
216            return Err((
217                format!("$name: {} is not a string.", v.inspect(args.span())?),
218                args.span(),
219            )
220                .into())
221        }
222    };
223    let css = args.default_arg(1, "css", Value::False).is_truthy();
224    let module = match args.default_arg(2, "module", Value::Null) {
225        Value::String(s, ..) => Some(s),
226        Value::Null => None,
227        v => {
228            return Err((
229                format!("$module: {} is not a string.", v.inspect(args.span())?),
230                args.span(),
231            )
232                .into())
233        }
234    };
235
236    if css && module.is_some() {
237        return Err((
238            "$css and $module may not both be passed at once.",
239            args.span(),
240        )
241            .into());
242    }
243
244    let func = if css {
245        Some(SassFunction::Plain { name })
246    } else if let Some(module_name) = module {
247        visitor.env.get_fn(
248            name,
249            Some(Spanned {
250                node: module_name.into(),
251                span: args.span(),
252            }),
253        )?
254    } else {
255        match visitor.env.get_fn(name, None)? {
256            Some(f) => Some(f),
257            None => GLOBAL_FUNCTIONS
258                .get(name.as_str())
259                .map(|f| SassFunction::Builtin(f.clone(), name)),
260        }
261    };
262
263    match func {
264        Some(func) => Ok(Value::FunctionRef(Box::new(func))),
265        None => Err((format!("Function not found: {}", name), args.span()).into()),
266    }
267}
268
269pub(crate) fn call(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
270    let span = args.span();
271    let func = match args.get_err(0, "function")? {
272        Value::FunctionRef(f) => *f,
273        Value::String(name, ..) => {
274            let name = Identifier::from(name);
275
276            match visitor.env.get_fn(name, None)? {
277                Some(f) => f,
278                None => match GLOBAL_FUNCTIONS.get(name.as_str()) {
279                    Some(f) => SassFunction::Builtin(f.clone(), name),
280                    None => SassFunction::Plain { name },
281                },
282            }
283        }
284        v => {
285            return Err((
286                format!(
287                    "$function: {} is not a function reference.",
288                    v.inspect(span)?
289                ),
290                span,
291            )
292                .into())
293        }
294    };
295
296    args.remove_positional(0);
297
298    visitor.run_function_callable_with_maybe_evaled(func, MaybeEvaledArguments::Evaled(args), span)
299}
300
301#[allow(clippy::needless_pass_by_value)]
302pub(crate) fn content_exists(args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
303    args.max_args(0)?;
304    if !visitor.flags.in_mixin() {
305        return Err((
306            "content-exists() may only be called within a mixin.",
307            args.span(),
308        )
309            .into());
310    }
311    Ok(Value::bool(visitor.env.content.is_some()))
312}
313
314pub(crate) fn keywords(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
315    args.max_args(1)?;
316
317    let span = args.span();
318
319    let args = match args.get_err(0, "args")? {
320        Value::ArgList(args) => args,
321        v => {
322            return Err((
323                format!("$args: {} is not an argument list.", v.inspect(span)?),
324                span,
325            )
326                .into())
327        }
328    };
329
330    Ok(Value::Map(SassMap::new_with(
331        args.into_keywords()
332            .into_iter()
333            .map(|(name, val)| {
334                (
335                    Value::String(name.to_string(), QuoteKind::None).span(span),
336                    val,
337                )
338            })
339            .collect(),
340    )))
341}
342
343pub(crate) fn declare(f: &mut GlobalFunctionMap) {
344    f.insert("if", Builtin::new(if_));
345    f.insert("feature-exists", Builtin::new(feature_exists));
346    f.insert("unit", Builtin::new(unit));
347    f.insert("type-of", Builtin::new(type_of));
348    f.insert("unitless", Builtin::new(unitless));
349    f.insert("inspect", Builtin::new(inspect));
350    f.insert("variable-exists", Builtin::new(variable_exists));
351    f.insert(
352        "global-variable-exists",
353        Builtin::new(global_variable_exists),
354    );
355    f.insert("mixin-exists", Builtin::new(mixin_exists));
356    f.insert("function-exists", Builtin::new(function_exists));
357    f.insert("get-function", Builtin::new(get_function));
358    f.insert("call", Builtin::new(call));
359    f.insert("content-exists", Builtin::new(content_exists));
360    f.insert("keywords", Builtin::new(keywords));
361}