grass_compiler/evaluate/
visitor.rs

1use std::{
2    cell::{Cell, RefCell},
3    collections::{BTreeMap, BTreeSet, HashSet},
4    ffi::OsStr,
5    fmt,
6    iter::FromIterator,
7    mem,
8    path::{Path, PathBuf},
9    rc::Rc,
10    sync::Arc,
11};
12
13use codemap::{CodeMap, Span, Spanned};
14use indexmap::IndexSet;
15
16use crate::{
17    ast::*,
18    builtin::{
19        meta::if_arguments,
20        modules::{
21            declare_module_color, declare_module_list, declare_module_map, declare_module_math,
22            declare_module_meta, declare_module_selector, declare_module_string, Module,
23        },
24        GLOBAL_FUNCTIONS,
25    },
26    common::{unvendor, BinaryOp, Identifier, ListSeparator, QuoteKind, UnaryOp},
27    error::{SassError, SassResult},
28    interner::InternedString,
29    lexer::Lexer,
30    parse::{
31        AtRootQueryParser, CssParser, KeyframesSelectorParser, SassParser, ScssParser,
32        StylesheetParser,
33    },
34    selector::{
35        ComplexSelectorComponent, ExtendRule, ExtendedSelector, ExtensionStore, SelectorList,
36        SelectorParser,
37    },
38    utils::{to_sentence, trim_ascii},
39    value::{
40        ArgList, CalculationArg, CalculationName, Number, SassCalculation, SassFunction, SassMap,
41        SassNumber, UserDefinedFunction, Value,
42    },
43    ContextFlags, InputSyntax, Options,
44};
45
46use super::{
47    bin_op::{add, cmp, div, mul, rem, single_eq, sub},
48    css_tree::{CssTree, CssTreeIdx},
49    env::Environment,
50};
51
52trait UserDefinedCallable {
53    fn name(&self) -> Identifier;
54    fn arguments(&self) -> &ArgumentDeclaration;
55}
56
57impl UserDefinedCallable for AstFunctionDecl {
58    fn name(&self) -> Identifier {
59        self.name.node
60    }
61
62    fn arguments(&self) -> &ArgumentDeclaration {
63        &self.arguments
64    }
65}
66
67impl UserDefinedCallable for Arc<AstFunctionDecl> {
68    fn name(&self) -> Identifier {
69        self.name.node
70    }
71
72    fn arguments(&self) -> &ArgumentDeclaration {
73        &self.arguments
74    }
75}
76
77impl UserDefinedCallable for AstMixin {
78    fn name(&self) -> Identifier {
79        self.name
80    }
81
82    fn arguments(&self) -> &ArgumentDeclaration {
83        &self.args
84    }
85}
86
87impl UserDefinedCallable for Arc<CallableContentBlock> {
88    fn name(&self) -> Identifier {
89        Identifier::from("@content")
90    }
91
92    fn arguments(&self) -> &ArgumentDeclaration {
93        &self.content.args
94    }
95}
96
97#[derive(Debug, Clone)]
98pub(crate) struct CallableContentBlock {
99    content: AstContentBlock,
100    env: Environment,
101}
102
103/// Evaluation context of the current execution
104#[derive(Debug)]
105pub struct Visitor<'a> {
106    pub(crate) declaration_name: Option<String>,
107    pub(crate) flags: ContextFlags,
108    pub(crate) env: Environment,
109    pub(crate) style_rule_ignoring_at_root: Option<ExtendedSelector>,
110    // avoid emitting duplicate warnings for the same span
111    pub(crate) warnings_emitted: HashSet<Span>,
112    pub(crate) media_queries: Option<Vec<MediaQuery>>,
113    pub(crate) media_query_sources: Option<IndexSet<MediaQuery>>,
114    pub(crate) extender: ExtensionStore,
115
116    /// The complete file path of the current file being visited. Imports are
117    /// resolved relative to this path
118    pub current_import_path: PathBuf,
119    pub(crate) is_plain_css: bool,
120    pub(crate) modules: BTreeMap<PathBuf, Arc<RefCell<Module>>>,
121    pub(crate) active_modules: BTreeSet<PathBuf>,
122    css_tree: CssTree,
123    parent: Option<CssTreeIdx>,
124    configuration: Rc<RefCell<Configuration>>,
125    import_nodes: Vec<CssStmt>,
126    pub options: &'a Options<'a>,
127    pub(crate) map: &'a mut CodeMap,
128    // todo: remove
129    empty_span: Span,
130    import_cache: BTreeMap<PathBuf, StyleSheet>,
131    /// As a simple heuristic, we don't cache the results of an import unless it
132    /// has been seen in the past. In the majority of cases, files are imported
133    /// at most once.
134    files_seen: BTreeSet<PathBuf>,
135}
136
137impl<'a> Visitor<'a> {
138    pub fn new(
139        path: &Path,
140        options: &'a Options<'a>,
141        map: &'a mut CodeMap,
142        empty_span: Span,
143    ) -> Self {
144        let mut flags = ContextFlags::empty();
145        flags.set(ContextFlags::IN_SEMI_GLOBAL_SCOPE, true);
146
147        let extender = ExtensionStore::new(empty_span);
148
149        let current_import_path = path.to_path_buf();
150
151        Self {
152            declaration_name: None,
153            style_rule_ignoring_at_root: None,
154            flags,
155            warnings_emitted: HashSet::new(),
156            media_queries: None,
157            media_query_sources: None,
158            env: Environment::new(),
159            extender,
160            css_tree: CssTree::new(),
161            parent: None,
162            current_import_path,
163            configuration: Rc::new(RefCell::new(Configuration::empty())),
164            is_plain_css: false,
165            import_nodes: Vec::new(),
166            modules: BTreeMap::new(),
167            active_modules: BTreeSet::new(),
168            options,
169            empty_span,
170            map,
171            import_cache: BTreeMap::new(),
172            files_seen: BTreeSet::new(),
173        }
174    }
175
176    pub(crate) fn visit_stylesheet(&mut self, mut style_sheet: StyleSheet) -> SassResult<()> {
177        self.active_modules.insert(style_sheet.url.clone());
178        let was_in_plain_css = self.is_plain_css;
179        self.is_plain_css = style_sheet.is_plain_css;
180        mem::swap(&mut self.current_import_path, &mut style_sheet.url);
181
182        for stmt in style_sheet.body {
183            let result = self.visit_stmt(stmt)?;
184            debug_assert!(result.is_none());
185        }
186
187        mem::swap(&mut self.current_import_path, &mut style_sheet.url);
188        self.is_plain_css = was_in_plain_css;
189
190        self.active_modules.remove(&style_sheet.url);
191
192        Ok(())
193    }
194
195    pub(crate) fn finish(mut self) -> Vec<CssStmt> {
196        let mut finished_tree = self.css_tree.finish();
197        if self.import_nodes.is_empty() {
198            finished_tree
199        } else {
200            self.import_nodes.append(&mut finished_tree);
201            self.import_nodes
202        }
203    }
204
205    fn visit_return_rule(&mut self, ret: AstReturn) -> SassResult<Option<Value>> {
206        let val = self.visit_expr(ret.val)?;
207
208        Ok(Some(self.without_slash(val)))
209    }
210
211    // todo: we really don't have to return Option<Value> from all of these children
212    pub(crate) fn visit_stmt(&mut self, stmt: AstStmt) -> SassResult<Option<Value>> {
213        match stmt {
214            AstStmt::RuleSet(ruleset) => self.visit_ruleset(ruleset),
215            AstStmt::Style(style) => self.visit_style(style),
216            AstStmt::SilentComment(..) => Ok(None),
217            AstStmt::If(if_stmt) => self.visit_if_stmt(if_stmt),
218            AstStmt::For(for_stmt) => self.visit_for_stmt(for_stmt),
219            AstStmt::Return(ret) => self.visit_return_rule(ret),
220            AstStmt::Each(each_stmt) => self.visit_each_stmt(each_stmt),
221            AstStmt::Media(media_rule) => self.visit_media_rule(media_rule),
222            AstStmt::Include(include_stmt) => self.visit_include_stmt(include_stmt),
223            AstStmt::While(while_stmt) => self.visit_while_stmt(&while_stmt),
224            AstStmt::VariableDecl(decl) => self.visit_variable_decl(decl),
225            AstStmt::LoudComment(comment) => self.visit_loud_comment(comment),
226            AstStmt::ImportRule(import_rule) => self.visit_import_rule(import_rule),
227            AstStmt::FunctionDecl(func) => {
228                self.visit_function_decl(func);
229                Ok(None)
230            }
231            AstStmt::Mixin(mixin) => {
232                self.visit_mixin_decl(mixin);
233                Ok(None)
234            }
235            AstStmt::ContentRule(content_rule) => self.visit_content_rule(content_rule),
236            AstStmt::Warn(warn_rule) => {
237                self.visit_warn_rule(warn_rule)?;
238                Ok(None)
239            }
240            AstStmt::UnknownAtRule(unknown_at_rule) => self.visit_unknown_at_rule(unknown_at_rule),
241            AstStmt::ErrorRule(error_rule) => Err(self.visit_error_rule(error_rule)?),
242            AstStmt::Extend(extend_rule) => self.visit_extend_rule(extend_rule),
243            AstStmt::AtRootRule(at_root_rule) => self.visit_at_root_rule(at_root_rule),
244            AstStmt::Debug(debug_rule) => self.visit_debug_rule(debug_rule),
245            AstStmt::Use(use_rule) => {
246                self.visit_use_rule(use_rule)?;
247                Ok(None)
248            }
249            AstStmt::Forward(forward_rule) => {
250                self.visit_forward_rule(forward_rule)?;
251                Ok(None)
252            }
253            AstStmt::Supports(supports_rule) => {
254                self.visit_supports_rule(supports_rule)?;
255                Ok(None)
256            }
257        }
258    }
259
260    fn visit_forward_rule(&mut self, forward_rule: AstForwardRule) -> SassResult<()> {
261        let old_config = Rc::clone(&self.configuration);
262        let adjusted_config = Configuration::through_forward(Rc::clone(&old_config), &forward_rule);
263
264        if !forward_rule.configuration.is_empty() {
265            let new_configuration =
266                self.add_forward_configuration(Rc::clone(&adjusted_config), &forward_rule)?;
267
268            self.load_module(
269                forward_rule.url.as_path(),
270                Some(Rc::clone(&new_configuration)),
271                false,
272                forward_rule.span,
273                |visitor, module, _| {
274                    visitor.env.forward_module(module, forward_rule.clone());
275
276                    Ok(())
277                },
278            )?;
279
280            Self::remove_used_configuration(
281                &adjusted_config,
282                &new_configuration,
283                &forward_rule
284                    .configuration
285                    .iter()
286                    .filter(|var| !var.is_guarded)
287                    .map(|var| var.name.node)
288                    .collect(),
289            );
290
291            // Remove all the variables that weren't configured by this particular
292            // `@forward` before checking that the configuration is empty. Errors for
293            // outer `with` clauses will be thrown once those clauses finish
294            // executing.
295            let configured_variables: HashSet<Identifier> = forward_rule
296                .configuration
297                .iter()
298                .map(|var| var.name.node)
299                .collect();
300
301            let mut to_remove = Vec::new();
302
303            for name in (*new_configuration).borrow().values.keys() {
304                if !configured_variables.contains(&name) {
305                    to_remove.push(name);
306                }
307            }
308
309            for name in to_remove {
310                (*new_configuration).borrow_mut().remove(name);
311            }
312
313            Self::assert_configuration_is_empty(&new_configuration, false)?;
314        } else {
315            self.configuration = adjusted_config;
316            let url = forward_rule.url.clone();
317            self.load_module(
318                url.as_path(),
319                None,
320                false,
321                forward_rule.span,
322                move |visitor, module, _| {
323                    visitor.env.forward_module(module, forward_rule.clone());
324
325                    Ok(())
326                },
327            )?;
328            self.configuration = old_config;
329        }
330
331        Ok(())
332    }
333
334    #[allow(clippy::unnecessary_unwrap)]
335    fn add_forward_configuration(
336        &mut self,
337        config: Rc<RefCell<Configuration>>,
338        forward_rule: &AstForwardRule,
339    ) -> SassResult<Rc<RefCell<Configuration>>> {
340        let mut new_values = BTreeMap::from_iter((*config).borrow().values.iter());
341
342        for variable in &forward_rule.configuration {
343            if variable.is_guarded {
344                let old_value = (*config).borrow_mut().remove(variable.name.node);
345
346                if old_value.is_some()
347                    && !matches!(
348                        old_value,
349                        Some(ConfiguredValue {
350                            value: Value::Null,
351                            ..
352                        })
353                    )
354                {
355                    new_values.insert(variable.name.node, old_value.unwrap());
356                    continue;
357                }
358            }
359
360            // todo: superfluous clone?
361            let value = self.visit_expr(variable.expr.node.clone())?;
362            let value = self.without_slash(value);
363
364            new_values.insert(
365                variable.name.node,
366                ConfiguredValue::explicit(value, variable.expr.span),
367            );
368        }
369
370        Ok(Rc::new(RefCell::new(
371            if !(*config).borrow().is_implicit() || (*config).borrow().is_empty() {
372                Configuration::explicit(new_values, forward_rule.span)
373            } else {
374                Configuration::implicit(new_values)
375            },
376        )))
377    }
378
379    /// Remove configured values from [upstream] that have been removed from
380    /// [downstream], unless they match a name in [except].
381    fn remove_used_configuration(
382        upstream: &Rc<RefCell<Configuration>>,
383        downstream: &Rc<RefCell<Configuration>>,
384        except: &HashSet<Identifier>,
385    ) {
386        let mut names_to_remove = Vec::new();
387        let downstream_keys = (*downstream).borrow().values.keys();
388        for name in (*upstream).borrow().values.keys() {
389            if except.contains(&name) {
390                continue;
391            }
392
393            if !downstream_keys.contains(&name) {
394                names_to_remove.push(name);
395            }
396        }
397
398        for name in names_to_remove {
399            (*upstream).borrow_mut().remove(name);
400        }
401    }
402
403    fn parenthesize_supports_condition(
404        &mut self,
405        condition: AstSupportsCondition,
406        operator: Option<&str>,
407    ) -> SassResult<String> {
408        match &condition {
409            AstSupportsCondition::Negation(..) => {
410                Ok(format!("({})", self.visit_supports_condition(condition)?))
411            }
412            AstSupportsCondition::Operation {
413                operator: operator2,
414                ..
415            } if operator2.is_none() || operator2.as_deref() != operator => {
416                Ok(format!("({})", self.visit_supports_condition(condition)?))
417            }
418            _ => self.visit_supports_condition(condition),
419        }
420    }
421
422    fn visit_supports_condition(&mut self, condition: AstSupportsCondition) -> SassResult<String> {
423        match condition {
424            AstSupportsCondition::Operation {
425                left,
426                operator,
427                right,
428            } => Ok(format!(
429                "{} {} {}",
430                self.parenthesize_supports_condition(*left, operator.as_deref())?,
431                operator.as_ref().unwrap(),
432                self.parenthesize_supports_condition(*right, operator.as_deref())?
433            )),
434            AstSupportsCondition::Negation(condition) => Ok(format!(
435                "not {}",
436                self.parenthesize_supports_condition(*condition, None)?
437            )),
438            AstSupportsCondition::Interpolation(expr) => {
439                self.evaluate_to_css(expr, QuoteKind::None, self.empty_span)
440            }
441            AstSupportsCondition::Declaration { name, value } => {
442                let old_in_supports_decl = self.flags.in_supports_declaration();
443                self.flags.set(ContextFlags::IN_SUPPORTS_DECLARATION, true);
444
445                let is_custom_property = match &name {
446                    AstExpr::String(StringExpr(text, QuoteKind::None), ..) => {
447                        text.initial_plain().starts_with("--")
448                    }
449                    _ => false,
450                };
451
452                let result = format!(
453                    "({}:{}{})",
454                    self.evaluate_to_css(name, QuoteKind::Quoted, self.empty_span)?,
455                    if is_custom_property { "" } else { " " },
456                    self.evaluate_to_css(value, QuoteKind::Quoted, self.empty_span)?,
457                );
458
459                self.flags
460                    .set(ContextFlags::IN_SUPPORTS_DECLARATION, old_in_supports_decl);
461
462                Ok(result)
463            }
464            AstSupportsCondition::Function { name, args } => Ok(format!(
465                "{}({})",
466                self.perform_interpolation(name, false)?,
467                self.perform_interpolation(args, false)?
468            )),
469            AstSupportsCondition::Anything { contents } => Ok(format!(
470                "({})",
471                self.perform_interpolation(contents, false)?,
472            )),
473        }
474    }
475
476    fn visit_supports_rule(&mut self, supports_rule: AstSupportsRule) -> SassResult<()> {
477        if self.declaration_name.is_some() {
478            return Err((
479                "Supports rules may not be used within nested declarations.",
480                supports_rule.span,
481            )
482                .into());
483        }
484
485        let condition = self.visit_supports_condition(supports_rule.condition)?;
486
487        let css_supports_rule = CssStmt::Supports(
488            SupportsRule {
489                params: condition,
490                body: Vec::new(),
491            },
492            false,
493        );
494
495        let children = supports_rule.body;
496
497        self.with_parent(
498            css_supports_rule,
499            true,
500            |visitor| {
501                if !visitor.style_rule_exists() {
502                    for stmt in children {
503                        let result = visitor.visit_stmt(stmt)?;
504                        debug_assert!(result.is_none());
505                    }
506                } else {
507                    // If we're in a style rule, copy it into the supports rule so that
508                    // declarations immediately inside @supports have somewhere to go.
509                    //
510                    // For example, "a {@supports (a: b) {b: c}}" should produce "@supports
511                    // (a: b) {a {b: c}}".
512                    let selector = visitor.style_rule_ignoring_at_root.clone().unwrap();
513                    let ruleset = CssStmt::RuleSet {
514                        selector,
515                        body: Vec::new(),
516                        is_group_end: false,
517                    };
518
519                    visitor.with_parent(
520                        ruleset,
521                        false,
522                        |visitor| {
523                            for stmt in children {
524                                let result = visitor.visit_stmt(stmt)?;
525                                debug_assert!(result.is_none());
526                            }
527
528                            Ok(())
529                        },
530                        |_| false,
531                    )?;
532                }
533
534                Ok(())
535            },
536            CssStmt::is_style_rule,
537        )?;
538
539        Ok(())
540    }
541
542    fn execute(
543        &mut self,
544        stylesheet: StyleSheet,
545        configuration: Option<Rc<RefCell<Configuration>>>,
546        // todo: different errors based on this
547        _names_in_errors: bool,
548    ) -> SassResult<Arc<RefCell<Module>>> {
549        let url = stylesheet.url.clone();
550
551        // todo: use canonical url for modules
552        if let Some(already_loaded) = self.modules.get(&stylesheet.url) {
553            let current_configuration =
554                configuration.unwrap_or_else(|| Rc::clone(&self.configuration));
555
556            if !current_configuration.borrow().is_implicit() {
557                //   if (!_moduleConfigurations[url]!.sameOriginal(currentConfiguration) &&
558                //       currentConfiguration is ExplicitConfiguration) {
559                //     var message = namesInErrors
560                //         ? "${p.prettyUri(url)} was already loaded, so it can't be "
561                //             "configured using \"with\"."
562                //         : "This module was already loaded, so it can't be configured using "
563                //             "\"with\".";
564
565                //     var existingSpan = _moduleNodes[url]?.span;
566                //     var configurationSpan = configuration == null
567                //         ? currentConfiguration.nodeWithSpan.span
568                //         : null;
569                //     var secondarySpans = {
570                //       if (existingSpan != null) existingSpan: "original load",
571                //       if (configurationSpan != null) configurationSpan: "configuration"
572                //     };
573
574                //     throw secondarySpans.isEmpty
575                //         ? _exception(message)
576                //         : _multiSpanException(message, "new load", secondarySpans);
577                //   }
578            }
579
580            return Ok(Arc::clone(already_loaded));
581        }
582
583        let env = Environment::new();
584        let mut extension_store = ExtensionStore::new(self.empty_span);
585
586        self.with_environment::<SassResult<()>, _>(env.new_closure(), |visitor| {
587            let old_parent = visitor.parent;
588            mem::swap(&mut visitor.extender, &mut extension_store);
589            let old_style_rule = visitor.style_rule_ignoring_at_root.take();
590            let old_media_queries = visitor.media_queries.take();
591            let old_declaration_name = visitor.declaration_name.take();
592            let old_in_unknown_at_rule = visitor.flags.in_unknown_at_rule();
593            let old_at_root_excluding_style_rule = visitor.flags.at_root_excluding_style_rule();
594            let old_in_keyframes = visitor.flags.in_keyframes();
595            let old_configuration = if let Some(new_config) = configuration {
596                Some(mem::replace(&mut visitor.configuration, new_config))
597            } else {
598                None
599            };
600            visitor.parent = None;
601            visitor.flags.set(ContextFlags::IN_UNKNOWN_AT_RULE, false);
602            visitor
603                .flags
604                .set(ContextFlags::AT_ROOT_EXCLUDING_STYLE_RULE, false);
605            visitor.flags.set(ContextFlags::IN_KEYFRAMES, false);
606
607            visitor.visit_stylesheet(stylesheet)?;
608
609            // visitor.importer = old_importer;
610            // visitor.stylesheet = old_stylesheet;
611            // visitor.root = old_root;
612            visitor.parent = old_parent;
613            // visitor.end_of_imports = old_end_of_imports;
614            // visitor.out_of_order_imports = old_out_of_order_imports;
615            mem::swap(&mut visitor.extender, &mut extension_store);
616            visitor.style_rule_ignoring_at_root = old_style_rule;
617            visitor.media_queries = old_media_queries;
618            visitor.declaration_name = old_declaration_name;
619            visitor
620                .flags
621                .set(ContextFlags::IN_UNKNOWN_AT_RULE, old_in_unknown_at_rule);
622            visitor.flags.set(
623                ContextFlags::AT_ROOT_EXCLUDING_STYLE_RULE,
624                old_at_root_excluding_style_rule,
625            );
626            visitor
627                .flags
628                .set(ContextFlags::IN_KEYFRAMES, old_in_keyframes);
629            if let Some(old_config) = old_configuration {
630                visitor.configuration = old_config;
631            }
632
633            Ok(())
634        })?;
635
636        let module = env.to_module(extension_store);
637
638        self.modules.insert(url, Arc::clone(&module));
639
640        Ok(module)
641    }
642
643    pub(crate) fn load_module(
644        &mut self,
645        url: &Path,
646        configuration: Option<Rc<RefCell<Configuration>>>,
647        names_in_errors: bool,
648        span: Span,
649        callback: impl Fn(&mut Self, Arc<RefCell<Module>>, StyleSheet) -> SassResult<()>,
650    ) -> SassResult<()> {
651        let builtin = match url.to_string_lossy().as_ref() {
652            "sass:color" => Some(declare_module_color()),
653            "sass:list" => Some(declare_module_list()),
654            "sass:map" => Some(declare_module_map()),
655            "sass:math" => Some(declare_module_math()),
656            "sass:meta" => Some(declare_module_meta()),
657            "sass:selector" => Some(declare_module_selector()),
658            "sass:string" => Some(declare_module_string()),
659            _ => None,
660        };
661
662        if let Some(builtin) = builtin {
663            // todo: lots of ugly unwraps here
664            if configuration.is_some()
665                && !(**configuration.as_ref().unwrap()).borrow().is_implicit()
666            {
667                let msg = if names_in_errors {
668                    format!(
669                        "Built-in module {} can't be configured.",
670                        url.to_string_lossy()
671                    )
672                } else {
673                    "Built-in modules can't be configured.".to_owned()
674                };
675
676                return Err((
677                    msg,
678                    (**configuration.as_ref().unwrap()).borrow().span.unwrap(),
679                )
680                    .into());
681            }
682
683            callback(
684                self,
685                Arc::new(RefCell::new(builtin)),
686                StyleSheet::new(false, url.to_path_buf()),
687            )?;
688            return Ok(());
689        }
690
691        // todo: decide on naming convention for style_sheet vs stylesheet
692        let stylesheet = self.load_style_sheet(url.to_string_lossy().as_ref(), false, span)?;
693
694        let canonical_url = self
695            .options
696            .fs
697            .canonicalize(&stylesheet.url)
698            .unwrap_or_else(|_| stylesheet.url.clone());
699
700        if self.active_modules.contains(&canonical_url) {
701            return Err(("Module loop: this module is already being loaded.", span).into());
702        }
703
704        self.active_modules.insert(canonical_url.clone());
705
706        let module = self.execute(stylesheet.clone(), configuration, names_in_errors)?;
707
708        self.active_modules.remove(&canonical_url);
709
710        callback(self, module, stylesheet)?;
711
712        Ok(())
713    }
714
715    fn visit_use_rule(&mut self, use_rule: AstUseRule) -> SassResult<()> {
716        let configuration = if use_rule.configuration.is_empty() {
717            Rc::new(RefCell::new(Configuration::empty()))
718        } else {
719            let mut values = BTreeMap::new();
720
721            for var in use_rule.configuration {
722                let value = self.visit_expr(var.expr.node)?;
723                let value = self.without_slash(value);
724                values.insert(
725                    var.name.node,
726                    ConfiguredValue::explicit(value, var.name.span.merge(var.expr.span)),
727                );
728            }
729
730            Rc::new(RefCell::new(Configuration::explicit(values, use_rule.span)))
731        };
732
733        let span = use_rule.span;
734
735        let namespace = use_rule
736            .namespace
737            .as_ref()
738            .map(|s| Identifier::from(s.trim_start_matches("sass:")));
739
740        self.load_module(
741            &use_rule.url,
742            Some(Rc::clone(&configuration)),
743            false,
744            span,
745            |visitor, module, _| {
746                visitor.env.add_module(namespace, module, span)?;
747
748                Ok(())
749            },
750        )?;
751
752        Self::assert_configuration_is_empty(&configuration, false)?;
753
754        Ok(())
755    }
756
757    pub(crate) fn assert_configuration_is_empty(
758        config: &Rc<RefCell<Configuration>>,
759        name_in_error: bool,
760    ) -> SassResult<()> {
761        let config = (**config).borrow();
762        // By definition, implicit configurations are allowed to only use a subset
763        // of their values.
764        if config.is_empty() || config.is_implicit() {
765            return Ok(());
766        }
767
768        let Spanned { node: name, span } = config.first().unwrap();
769
770        let msg = if name_in_error {
771            format!(
772                "${name} was not declared with !default in the @used module.",
773                name = name
774            )
775        } else {
776            "This variable was not declared with !default in the @used module.".to_owned()
777        };
778
779        Err((msg, span).into())
780    }
781
782    fn visit_import_rule(&mut self, import_rule: AstImportRule) -> SassResult<Option<Value>> {
783        for import in import_rule.imports {
784            match import {
785                AstImport::Sass(dynamic_import) => {
786                    self.visit_dynamic_import_rule(&dynamic_import)?;
787                }
788                AstImport::Plain(static_import) => self.visit_static_import_rule(static_import)?,
789            }
790        }
791
792        Ok(None)
793    }
794
795    /// Searches the current directory of the file then searches in `load_paths` directories
796    /// if the import has not yet been found.
797    ///
798    /// <https://sass-lang.com/documentation/at-rules/import#finding-the-file>
799    /// <https://sass-lang.com/documentation/at-rules/import#load-paths>
800    #[allow(clippy::cognitive_complexity, clippy::redundant_clone)]
801    pub fn find_import(&self, path: &Path) -> Option<PathBuf> {
802        let path_buf = if path.is_absolute() {
803            path.into()
804        } else {
805            self.current_import_path
806                .parent()
807                .unwrap_or_else(|| Path::new(""))
808                .join(path)
809        };
810
811        macro_rules! try_path {
812            ($path:expr) => {
813                let path = $path;
814                let dirname = path.parent().unwrap_or_else(|| Path::new(""));
815                let basename = path.file_name().unwrap_or_else(|| OsStr::new(".."));
816
817                let partial = dirname.join(format!("_{}", basename.to_str().unwrap()));
818
819                if self.options.fs.is_file(&path) {
820                    return Some(path.to_path_buf());
821                }
822
823                if self.options.fs.is_file(&partial) {
824                    return Some(partial);
825                }
826            };
827        }
828
829        if path_buf.extension() == Some(OsStr::new("scss"))
830            || path_buf.extension() == Some(OsStr::new("sass"))
831            || path_buf.extension() == Some(OsStr::new("css"))
832        {
833            let extension = path_buf.extension().unwrap();
834            try_path!(path_buf.with_extension(format!(".import{}", extension.to_str().unwrap())));
835            try_path!(path_buf);
836            // todo: consider load paths
837            return None;
838        }
839
840        macro_rules! try_path_with_extensions {
841            ($path:expr) => {
842                let path = $path;
843                try_path!(path.with_extension("import.sass"));
844                try_path!(path.with_extension("import.scss"));
845                try_path!(path.with_extension("import.css"));
846                try_path!(path.with_extension("sass"));
847                try_path!(path.with_extension("scss"));
848                try_path!(path.with_extension("css"));
849            };
850        }
851
852        try_path_with_extensions!(path_buf.clone());
853
854        if self.options.fs.is_dir(&path_buf) {
855            try_path_with_extensions!(path_buf.join("index"));
856        }
857
858        for load_path in &self.options.load_paths {
859            let path_buf = load_path.join(path);
860
861            try_path_with_extensions!(&path_buf);
862
863            if self.options.fs.is_dir(&path_buf) {
864                try_path_with_extensions!(path_buf.join("index"));
865            }
866        }
867
868        None
869    }
870
871    fn parse_file(
872        &mut self,
873        lexer: Lexer,
874        path: &Path,
875        empty_span: Span,
876    ) -> SassResult<StyleSheet> {
877        match InputSyntax::for_path(path) {
878            InputSyntax::Scss => ScssParser::new(lexer, self.options, empty_span, path).__parse(),
879            InputSyntax::Sass => SassParser::new(lexer, self.options, empty_span, path).__parse(),
880            InputSyntax::Css => CssParser::new(lexer, self.options, empty_span, path).__parse(),
881        }
882    }
883
884    fn import_like_node(
885        &mut self,
886        url: &str,
887        _for_import: bool,
888        span: Span,
889    ) -> SassResult<StyleSheet> {
890        if let Some(name) = self.find_import(url.as_ref()) {
891            let name = self.options.fs.canonicalize(&name).unwrap_or(name);
892            if let Some(style_sheet) = self.import_cache.get(&name) {
893                return Ok(style_sheet.clone());
894            }
895
896            let file = self.map.add_file(
897                name.to_string_lossy().into(),
898                String::from_utf8(self.options.fs.read(&name)?)?,
899            );
900
901            let old_is_use_allowed = self.flags.is_use_allowed();
902            self.flags.set(ContextFlags::IS_USE_ALLOWED, true);
903
904            let style_sheet =
905                self.parse_file(Lexer::new_from_file(&file), &name, file.span.subspan(0, 0))?;
906
907            self.flags
908                .set(ContextFlags::IS_USE_ALLOWED, old_is_use_allowed);
909
910            if self.files_seen.contains(&name) {
911                self.import_cache.insert(name, style_sheet.clone());
912            } else {
913                self.files_seen.insert(name);
914            }
915
916            return Ok(style_sheet);
917        }
918
919        Err(("Can't find stylesheet to import.", span).into())
920    }
921
922    pub(crate) fn load_style_sheet(
923        &mut self,
924        url: &str,
925        // default=false
926        for_import: bool,
927        span: Span,
928    ) -> SassResult<StyleSheet> {
929        // todo: import cache
930        self.import_like_node(url, for_import, span)
931    }
932
933    fn visit_dynamic_import_rule(&mut self, dynamic_import: &AstSassImport) -> SassResult<()> {
934        let stylesheet = self.load_style_sheet(&dynamic_import.url, true, dynamic_import.span)?;
935
936        let url = stylesheet.url.clone();
937
938        if self.active_modules.contains(&url) {
939            return Err(("This file is already being loaded.", dynamic_import.span).into());
940        }
941
942        self.active_modules.insert(url.clone());
943
944        // If the imported stylesheet doesn't use any modules, we can inject its
945        // CSS directly into the current stylesheet. If it does use modules, we
946        // need to put its CSS into an intermediate [ModifiableCssStylesheet] so
947        // that we can hermetically resolve `@extend`s before injecting it.
948        if stylesheet.uses.is_empty() && stylesheet.forwards.is_empty() {
949            self.visit_stylesheet(stylesheet)?;
950            return Ok(());
951        }
952
953        // todo:
954        let loads_user_defined_modules = true;
955
956        // this todo should be unreachable, as we currently do not push
957        // to stylesheet.uses or stylesheet.forwards
958        // let mut children = Vec::new();
959        let env = self.env.for_import();
960
961        self.with_environment::<SassResult<()>, _>(env.clone(), |visitor| {
962            let old_parent = visitor.parent;
963            let old_configuration = Rc::clone(&visitor.configuration);
964
965            if loads_user_defined_modules {
966                visitor.parent = Some(CssTree::ROOT);
967            }
968
969            // This configuration is only used if it passes through a `@forward`
970            // rule, so we avoid creating unnecessary ones for performance reasons.
971            if !stylesheet.forwards.is_empty() {
972                visitor.configuration = Rc::new(RefCell::new(env.to_implicit_configuration()));
973            }
974
975            visitor.visit_stylesheet(stylesheet)?;
976
977            if loads_user_defined_modules {
978                visitor.parent = old_parent;
979            }
980            visitor.configuration = old_configuration;
981
982            Ok(())
983        })?;
984
985        // Create a dummy module with empty CSS and no extensions to make forwarded
986        // members available in the current import context and to combine all the
987        // CSS from modules used by [stylesheet].
988        let module = env.to_dummy_module(self.empty_span);
989        self.env.import_forwards(module);
990
991        if loads_user_defined_modules {
992            // todo:
993            //     if (module.transitivelyContainsCss) {
994            //       // If any transitively used module contains extensions, we need to
995            //       // clone all modules' CSS. Otherwise, it's possible that they'll be
996            //       // used or imported from another location that shouldn't have the same
997            //       // extensions applied.
998            //       await _combineCss(module,
999            //               clone: module.transitivelyContainsExtensions)
1000            //           .accept(this);
1001            //     }
1002
1003            //     var visitor = _ImportedCssVisitor(this);
1004            //     for (var child in children) {
1005            //       child.accept(visitor);
1006            //     }
1007        }
1008
1009        self.active_modules.remove(&url);
1010
1011        Ok(())
1012    }
1013
1014    fn visit_static_import_rule(&mut self, static_import: AstPlainCssImport) -> SassResult<()> {
1015        let import = self.interpolation_to_value(static_import.url, false, false)?;
1016
1017        let modifiers = static_import
1018            .modifiers
1019            .map(|modifiers| self.interpolation_to_value(modifiers, false, false))
1020            .transpose()?;
1021
1022        let node = CssStmt::Import(import, modifiers);
1023
1024        if self.parent.is_some() && self.parent != Some(CssTree::ROOT) {
1025            self.css_tree.add_stmt(node, self.parent);
1026        } else {
1027            self.import_nodes.push(node);
1028        }
1029
1030        Ok(())
1031    }
1032
1033    fn visit_debug_rule(&mut self, debug_rule: AstDebugRule) -> SassResult<Option<Value>> {
1034        if self.options.quiet {
1035            return Ok(None);
1036        }
1037
1038        let message = self.visit_expr(debug_rule.value)?;
1039        let message = message.inspect(debug_rule.span)?;
1040
1041        let loc = self.map.look_up_span(debug_rule.span);
1042        self.options.logger.debug(loc, message.as_str());
1043
1044        Ok(None)
1045    }
1046
1047    fn visit_content_rule(&mut self, content_rule: AstContentRule) -> SassResult<Option<Value>> {
1048        let span = content_rule.args.span;
1049        if let Some(content) = &self.env.content {
1050            #[allow(mutable_borrow_reservation_conflict)]
1051            self.run_user_defined_callable(
1052                MaybeEvaledArguments::Invocation(content_rule.args),
1053                Arc::clone(content),
1054                &content.env.clone(),
1055                span,
1056                |content, visitor| {
1057                    for stmt in content.content.body.clone() {
1058                        let result = visitor.visit_stmt(stmt)?;
1059                        debug_assert!(result.is_none());
1060                    }
1061
1062                    Ok(())
1063                },
1064            )?;
1065        }
1066
1067        Ok(None)
1068    }
1069
1070    fn trim_included(&self, nodes: &[CssTreeIdx]) -> CssTreeIdx {
1071        if nodes.is_empty() {
1072            return CssTree::ROOT;
1073        }
1074
1075        let mut parent = self.parent;
1076
1077        let mut innermost_contiguous: Option<usize> = None;
1078
1079        for i in 0..nodes.len() {
1080            while parent != nodes.get(i).copied() {
1081                innermost_contiguous = None;
1082
1083                let grandparent = self.css_tree.child_to_parent.get(&parent.unwrap()).copied();
1084                if grandparent.is_none() {
1085                    unreachable!(
1086                        "Expected {:?} to be an ancestor of {:?}.",
1087                        nodes[i], grandparent
1088                    )
1089                }
1090                parent = grandparent;
1091            }
1092            innermost_contiguous = innermost_contiguous.or(Some(i));
1093
1094            let grandparent = self.css_tree.child_to_parent.get(&parent.unwrap()).copied();
1095            if grandparent.is_none() {
1096                unreachable!(
1097                    "Expected {:?} to be an ancestor of {:?}.",
1098                    nodes[i], grandparent
1099                )
1100            }
1101            parent = grandparent;
1102        }
1103
1104        if parent != Some(CssTree::ROOT) {
1105            return CssTree::ROOT;
1106        }
1107
1108        nodes[innermost_contiguous.unwrap()]
1109    }
1110
1111    fn visit_at_root_rule(&mut self, mut at_root_rule: AstAtRootRule) -> SassResult<Option<Value>> {
1112        let query = match at_root_rule.query.clone() {
1113            Some(query) => {
1114                let resolved = self.perform_interpolation(query.node, true)?;
1115
1116                let span = query.span;
1117
1118                let query_toks = Lexer::new_from_string(&resolved, span);
1119
1120                AtRootQueryParser::new(query_toks).parse()?
1121            }
1122            None => AtRootQuery::default(),
1123        };
1124
1125        let mut current_parent_idx = self.parent;
1126
1127        let mut included = Vec::new();
1128
1129        while let Some(parent_idx) = current_parent_idx {
1130            let parent = self.css_tree.get(parent_idx);
1131            let grandparent_idx = match &*parent {
1132                Some(parent) => {
1133                    if !query.excludes(parent) {
1134                        included.push(parent_idx);
1135                    }
1136                    self.css_tree.child_to_parent.get(&parent_idx).copied()
1137                }
1138                None => break,
1139            };
1140
1141            current_parent_idx = grandparent_idx;
1142        }
1143
1144        let root = self.trim_included(&included);
1145
1146        // If we didn't exclude any rules, we don't need to use the copies we might
1147        // have created.
1148        if Some(root) == self.parent {
1149            self.with_scope::<SassResult<()>, _>(false, true, |visitor| {
1150                for stmt in at_root_rule.body {
1151                    let result = visitor.visit_stmt(stmt)?;
1152                    debug_assert!(result.is_none());
1153                }
1154
1155                Ok(())
1156            })?;
1157            return Ok(None);
1158        }
1159
1160        let inner_copy = if !included.is_empty() {
1161            let inner_copy = self
1162                .css_tree
1163                .get(*included.first().unwrap())
1164                .as_ref()
1165                .map(CssStmt::copy_without_children);
1166            let mut outer_copy = self.css_tree.add_stmt(inner_copy.unwrap(), None);
1167
1168            for node in &included[1..] {
1169                let copy = self
1170                    .css_tree
1171                    .get(*node)
1172                    .as_ref()
1173                    .map(CssStmt::copy_without_children)
1174                    .unwrap();
1175
1176                let copy_idx = self.css_tree.add_stmt(copy, None);
1177                self.css_tree.link_child_to_parent(outer_copy, copy_idx);
1178
1179                outer_copy = copy_idx;
1180            }
1181
1182            Some(outer_copy)
1183        } else {
1184            let inner_copy = self
1185                .css_tree
1186                .get(root)
1187                .as_ref()
1188                .map(CssStmt::copy_without_children);
1189            inner_copy.map(|p| self.css_tree.add_stmt(p, None))
1190        };
1191
1192        let body = mem::take(&mut at_root_rule.body);
1193
1194        self.with_scope_for_at_root::<SassResult<()>, _>(inner_copy, &query, |visitor| {
1195            for stmt in body {
1196                let result = visitor.visit_stmt(stmt)?;
1197                debug_assert!(result.is_none());
1198            }
1199
1200            Ok(())
1201        })?;
1202
1203        Ok(None)
1204    }
1205
1206    fn with_scope_for_at_root<T, F: FnOnce(&mut Self) -> T>(
1207        &mut self,
1208        new_parent_idx: Option<CssTreeIdx>,
1209        query: &AtRootQuery,
1210        callback: F,
1211    ) -> T {
1212        let old_parent = self.parent;
1213        self.parent = new_parent_idx;
1214
1215        let old_at_root_excluding_style_rule = self.flags.at_root_excluding_style_rule();
1216
1217        if query.excludes_style_rules() {
1218            self.flags
1219                .set(ContextFlags::AT_ROOT_EXCLUDING_STYLE_RULE, true);
1220        }
1221
1222        let old_media_query_info = if self.media_queries.is_some() && query.excludes_name("media") {
1223            Some((self.media_queries.take(), self.media_query_sources.take()))
1224        } else {
1225            None
1226        };
1227
1228        let was_in_keyframes = if self.flags.in_keyframes() && query.excludes_name("keyframes") {
1229            let was = self.flags.in_keyframes();
1230            self.flags.set(ContextFlags::IN_KEYFRAMES, false);
1231            was
1232        } else {
1233            self.flags.in_keyframes()
1234        };
1235
1236        // todo:
1237        // if self.flags.in_unknown_at_rule() && !included.iter().any(|parent| parent is CssAtRule)
1238
1239        let res = self.with_scope(false, true, callback);
1240
1241        self.parent = old_parent;
1242
1243        self.flags.set(
1244            ContextFlags::AT_ROOT_EXCLUDING_STYLE_RULE,
1245            old_at_root_excluding_style_rule,
1246        );
1247
1248        if let Some((old_media_queries, old_media_query_sources)) = old_media_query_info {
1249            self.media_queries = old_media_queries;
1250            self.media_query_sources = old_media_query_sources;
1251        }
1252
1253        self.flags.set(ContextFlags::IN_KEYFRAMES, was_in_keyframes);
1254
1255        res
1256    }
1257
1258    fn visit_function_decl(&mut self, fn_decl: AstFunctionDecl) {
1259        let name = fn_decl.name.node;
1260        // todo: independency
1261
1262        let func = SassFunction::UserDefined(UserDefinedFunction {
1263            function: Arc::new(fn_decl),
1264            name,
1265            env: self.env.new_closure(),
1266        });
1267
1268        self.env.insert_fn(func);
1269    }
1270
1271    pub(crate) fn parse_selector_from_string(
1272        &mut self,
1273        selector_text: &str,
1274        allows_parent: bool,
1275        allows_placeholder: bool,
1276        span: Span,
1277    ) -> SassResult<SelectorList> {
1278        let sel_toks = Lexer::new_from_string(selector_text, span);
1279
1280        SelectorParser::new(sel_toks, allows_parent, allows_placeholder, span).parse()
1281    }
1282
1283    fn visit_extend_rule(&mut self, extend_rule: AstExtendRule) -> SassResult<Option<Value>> {
1284        if !self.style_rule_exists() || self.declaration_name.is_some() {
1285            return Err((
1286                "@extend may only be used within style rules.",
1287                extend_rule.span,
1288            )
1289                .into());
1290        }
1291
1292        let super_selector = self.style_rule_ignoring_at_root.clone().unwrap();
1293
1294        let target_text = self.interpolation_to_value(extend_rule.value, false, true)?;
1295
1296        let list = self.parse_selector_from_string(&target_text, false, true, extend_rule.span)?;
1297
1298        for complex in list.components {
1299            if complex.components.len() != 1 || !complex.components.first().unwrap().is_compound() {
1300                // If the selector was a compound selector but not a simple
1301                // selector, emit a more explicit error.
1302                return Err(("complex selectors may not be extended.", extend_rule.span).into());
1303            }
1304
1305            let compound = match complex.components.first() {
1306                Some(ComplexSelectorComponent::Compound(c)) => c,
1307                Some(..) | None => unreachable!("checked by above condition"),
1308            };
1309            if compound.components.len() != 1 {
1310                return Err((
1311                    format!(
1312                        "compound selectors may no longer be extended.\nConsider `@extend {}` instead.\nSee http://bit.ly/ExtendCompound for details.\n",
1313                        compound.components.iter().map(ToString::to_string).collect::<Vec<String>>().join(", ")
1314                    )
1315                , extend_rule.span).into());
1316            }
1317
1318            self.extender.add_extension(
1319                super_selector.clone().into_selector().0,
1320                compound.components.first().unwrap(),
1321                &ExtendRule {
1322                    is_optional: extend_rule.is_optional,
1323                },
1324                &self.media_queries,
1325                extend_rule.span,
1326            );
1327        }
1328
1329        Ok(None)
1330    }
1331
1332    fn visit_error_rule(&mut self, error_rule: AstErrorRule) -> SassResult<Box<SassError>> {
1333        let value = self
1334            .visit_expr(error_rule.value)?
1335            .inspect(error_rule.span)?;
1336
1337        Ok((value, error_rule.span).into())
1338    }
1339
1340    fn merge_media_queries(
1341        queries1: &[MediaQuery],
1342        queries2: &[MediaQuery],
1343    ) -> Option<Vec<MediaQuery>> {
1344        let mut queries = Vec::new();
1345
1346        for query1 in queries1 {
1347            for query2 in queries2 {
1348                match query1.merge(query2) {
1349                    MediaQueryMergeResult::Empty => continue,
1350                    MediaQueryMergeResult::Unrepresentable => return None,
1351                    MediaQueryMergeResult::Success(result) => queries.push(result),
1352                }
1353            }
1354        }
1355
1356        Some(queries)
1357    }
1358
1359    fn visit_media_queries(
1360        &mut self,
1361        queries: Interpolation,
1362        span: Span,
1363    ) -> SassResult<Vec<CssMediaQuery>> {
1364        let resolved = self.perform_interpolation(queries, true)?;
1365
1366        CssMediaQuery::parse_list(&resolved, span)
1367    }
1368
1369    fn visit_media_rule(&mut self, media_rule: AstMedia) -> SassResult<Option<Value>> {
1370        if self.declaration_name.is_some() {
1371            return Err((
1372                "Media rules may not be used within nested declarations.",
1373                media_rule.span,
1374            )
1375                .into());
1376        }
1377
1378        let queries1 = self.visit_media_queries(media_rule.query, media_rule.query_span)?;
1379        // todo: superfluous clone?
1380        let queries2 = self.media_queries.clone();
1381        let merged_queries = queries2
1382            .as_ref()
1383            .and_then(|queries2| Self::merge_media_queries(queries2, &queries1));
1384
1385        let merged_sources = match &merged_queries {
1386            Some(merged_queries) if merged_queries.is_empty() => return Ok(None),
1387            Some(..) => {
1388                let mut set = IndexSet::new();
1389                set.extend(self.media_query_sources.clone().unwrap());
1390                set.extend(self.media_queries.clone().unwrap());
1391                set.extend(queries1.clone());
1392                set
1393            }
1394            None => IndexSet::new(),
1395        };
1396
1397        let children = media_rule.body;
1398
1399        let query = merged_queries.clone().unwrap_or_else(|| queries1.clone());
1400
1401        let media_rule = CssStmt::Media(
1402            MediaRule {
1403                query,
1404                body: Vec::new(),
1405            },
1406            false,
1407        );
1408
1409        self.with_parent(
1410            media_rule,
1411            true,
1412            |visitor| {
1413                visitor.with_media_queries(
1414                    Some(merged_queries.unwrap_or(queries1)),
1415                    Some(merged_sources.clone()),
1416                    |visitor| {
1417                        if !visitor.style_rule_exists() {
1418                            for stmt in children {
1419                                let result = visitor.visit_stmt(stmt)?;
1420                                debug_assert!(result.is_none());
1421                            }
1422                        } else {
1423                            // If we're in a style rule, copy it into the media query so that
1424                            // declarations immediately inside @media have somewhere to go.
1425                            //
1426                            // For example, "a {@media screen {b: c}}" should produce
1427                            // "@media screen {a {b: c}}".
1428                            let selector = visitor.style_rule_ignoring_at_root.clone().unwrap();
1429                            let ruleset = CssStmt::RuleSet {
1430                                selector,
1431                                body: Vec::new(),
1432                                is_group_end: false,
1433                            };
1434
1435                            visitor.with_parent(
1436                                ruleset,
1437                                false,
1438                                |visitor| {
1439                                    for stmt in children {
1440                                        let result = visitor.visit_stmt(stmt)?;
1441                                        debug_assert!(result.is_none());
1442                                    }
1443
1444                                    Ok(())
1445                                },
1446                                |_| false,
1447                            )?;
1448                        }
1449
1450                        Ok(())
1451                    },
1452                )
1453            },
1454            |stmt| match stmt {
1455                CssStmt::RuleSet { .. } => true,
1456                // todo: node.queries.every(mergedSources.contains))
1457                CssStmt::Media(media_rule, ..) => {
1458                    !merged_sources.is_empty()
1459                        && media_rule
1460                            .query
1461                            .iter()
1462                            .all(|query| merged_sources.contains(query))
1463                }
1464                _ => false,
1465            },
1466        )?;
1467
1468        Ok(None)
1469    }
1470
1471    fn visit_unknown_at_rule(
1472        &mut self,
1473        unknown_at_rule: AstUnknownAtRule,
1474    ) -> SassResult<Option<Value>> {
1475        if self.declaration_name.is_some() {
1476            return Err((
1477                "At-rules may not be used within nested declarations.",
1478                unknown_at_rule.span,
1479            )
1480                .into());
1481        }
1482
1483        let name = self.interpolation_to_value(unknown_at_rule.name, false, false)?;
1484
1485        let value = unknown_at_rule
1486            .value
1487            .map(|v| self.interpolation_to_value(v, true, true))
1488            .transpose()?;
1489
1490        if unknown_at_rule.body.is_none() {
1491            let stmt = CssStmt::UnknownAtRule(
1492                UnknownAtRule {
1493                    name,
1494                    params: value.unwrap_or_default(),
1495                    body: Vec::new(),
1496                    has_body: false,
1497                },
1498                false,
1499            );
1500
1501            self.css_tree.add_stmt(stmt, self.parent);
1502
1503            return Ok(None);
1504        }
1505
1506        let was_in_keyframes = self.flags.in_keyframes();
1507        let was_in_unknown_at_rule = self.flags.in_unknown_at_rule();
1508
1509        if unvendor(&name) == "keyframes" {
1510            self.flags.set(ContextFlags::IN_KEYFRAMES, true);
1511        } else {
1512            self.flags.set(ContextFlags::IN_UNKNOWN_AT_RULE, true);
1513        }
1514
1515        let children = unknown_at_rule.body.unwrap();
1516
1517        let stmt = CssStmt::UnknownAtRule(
1518            UnknownAtRule {
1519                name,
1520                params: value.unwrap_or_default(),
1521                body: Vec::new(),
1522                has_body: true,
1523            },
1524            false,
1525        );
1526
1527        self.with_parent(
1528            stmt,
1529            true,
1530            |visitor| {
1531                if !visitor.style_rule_exists() || visitor.flags.in_keyframes() {
1532                    for stmt in children {
1533                        let result = visitor.visit_stmt(stmt)?;
1534                        debug_assert!(result.is_none());
1535                    }
1536                } else {
1537                    // If we're in a style rule, copy it into the at-rule so that
1538                    // declarations immediately inside it have somewhere to go.
1539                    //
1540                    // For example, "a {@foo {b: c}}" should produce "@foo {a {b: c}}".
1541                    let selector = visitor.style_rule_ignoring_at_root.clone().unwrap();
1542
1543                    let style_rule = CssStmt::RuleSet {
1544                        selector,
1545                        body: Vec::new(),
1546                        is_group_end: false,
1547                    };
1548
1549                    visitor.with_parent(
1550                        style_rule,
1551                        false,
1552                        |visitor| {
1553                            for stmt in children {
1554                                let result = visitor.visit_stmt(stmt)?;
1555                                debug_assert!(result.is_none());
1556                            }
1557
1558                            Ok(())
1559                        },
1560                        |_| false,
1561                    )?;
1562                }
1563
1564                Ok(())
1565            },
1566            CssStmt::is_style_rule,
1567        )?;
1568
1569        self.flags.set(ContextFlags::IN_KEYFRAMES, was_in_keyframes);
1570        self.flags
1571            .set(ContextFlags::IN_UNKNOWN_AT_RULE, was_in_unknown_at_rule);
1572
1573        Ok(None)
1574    }
1575
1576    pub(crate) fn emit_warning(&mut self, message: &str, span: Span) {
1577        if self.options.quiet {
1578            return;
1579        }
1580        let loc = self.map.look_up_span(span);
1581        self.options.logger.warn(loc, message);
1582    }
1583
1584    fn visit_warn_rule(&mut self, warn_rule: AstWarn) -> SassResult<()> {
1585        if self.warnings_emitted.insert(warn_rule.span) {
1586            let value = self.visit_expr(warn_rule.value)?;
1587            let message = value.to_css_string(warn_rule.span, self.options.is_compressed())?;
1588            self.emit_warning(&message, warn_rule.span);
1589        }
1590
1591        Ok(())
1592    }
1593
1594    fn with_media_queries<T>(
1595        &mut self,
1596        queries: Option<Vec<MediaQuery>>,
1597        sources: Option<IndexSet<MediaQuery>>,
1598        callback: impl FnOnce(&mut Self) -> T,
1599    ) -> T {
1600        let old_media_queries = self.media_queries.take();
1601        let old_media_query_sources = self.media_query_sources.take();
1602        self.media_queries = queries;
1603        self.media_query_sources = sources;
1604        let result = callback(self);
1605        self.media_queries = old_media_queries;
1606        self.media_query_sources = old_media_query_sources;
1607        result
1608    }
1609
1610    fn with_environment<T, F: FnOnce(&mut Self) -> T>(
1611        &mut self,
1612        env: Environment,
1613        callback: F,
1614    ) -> T {
1615        let mut old_env = env;
1616        mem::swap(&mut self.env, &mut old_env);
1617        let val = callback(self);
1618        mem::swap(&mut self.env, &mut old_env);
1619        val
1620    }
1621
1622    fn add_child<F: Fn(&CssStmt) -> bool>(
1623        &mut self,
1624        node: CssStmt,
1625        through: Option<F>,
1626    ) -> CssTreeIdx {
1627        if self.parent.is_none() || self.parent == Some(CssTree::ROOT) {
1628            return self.css_tree.add_stmt(node, self.parent);
1629        }
1630
1631        let mut parent = self.parent.unwrap();
1632
1633        if let Some(through) = through {
1634            while parent != CssTree::ROOT && through(self.css_tree.get(parent).as_ref().unwrap()) {
1635                let grandparent = self.css_tree.child_to_parent.get(&parent).copied();
1636                debug_assert!(
1637                    grandparent.is_some(),
1638                    "through() must return false for at least one parent of $node."
1639                );
1640                parent = grandparent.unwrap();
1641            }
1642
1643            // If the parent has a (visible) following sibling, we shouldn't add to
1644            // the parent. Instead, we should create a copy and add it after the
1645            // interstitial sibling.
1646            if self.css_tree.has_following_sibling(parent) {
1647                let grandparent = self.css_tree.child_to_parent.get(&parent).copied().unwrap();
1648                let parent_node = self
1649                    .css_tree
1650                    .get(parent)
1651                    .as_ref()
1652                    .map(CssStmt::copy_without_children)
1653                    .unwrap();
1654                parent = self.css_tree.add_child(parent_node, grandparent);
1655            }
1656        }
1657
1658        self.css_tree.add_child(node, parent)
1659    }
1660
1661    fn with_parent<F: FnOnce(&mut Self) -> SassResult<()>, FT: Fn(&CssStmt) -> bool>(
1662        &mut self,
1663        parent: CssStmt,
1664        // default=true
1665        scope_when: bool,
1666        callback: F,
1667        // todo: optional
1668        through: FT,
1669    ) -> SassResult<()> {
1670        let parent_idx = self.add_child(parent, Some(through));
1671        let old_parent = self.parent;
1672        self.parent = Some(parent_idx);
1673        let result = self.with_scope(false, scope_when, callback);
1674        self.parent = old_parent;
1675        result
1676    }
1677
1678    fn with_scope<T, F: FnOnce(&mut Self) -> T>(
1679        &mut self,
1680        // default=false
1681        semi_global: bool,
1682        // default=true
1683        when: bool,
1684        callback: F,
1685    ) -> T {
1686        let semi_global = semi_global && self.flags.in_semi_global_scope();
1687        let was_in_semi_global_scope = self.flags.in_semi_global_scope();
1688        self.flags
1689            .set(ContextFlags::IN_SEMI_GLOBAL_SCOPE, semi_global);
1690
1691        if !when {
1692            let v = callback(self);
1693            self.flags
1694                .set(ContextFlags::IN_SEMI_GLOBAL_SCOPE, was_in_semi_global_scope);
1695
1696            return v;
1697        }
1698
1699        self.env.scopes_mut().enter_new_scope();
1700
1701        let v = callback(self);
1702
1703        self.flags
1704            .set(ContextFlags::IN_SEMI_GLOBAL_SCOPE, was_in_semi_global_scope);
1705        self.env.scopes_mut().exit_scope();
1706
1707        v
1708    }
1709
1710    fn with_content<T>(
1711        &mut self,
1712        content: Option<Arc<CallableContentBlock>>,
1713        callback: impl FnOnce(&mut Self) -> T,
1714    ) -> T {
1715        let old_content = self.env.content.take();
1716        self.env.content = content;
1717        let v = callback(self);
1718        self.env.content = old_content;
1719        v
1720    }
1721
1722    fn visit_include_stmt(&mut self, include_stmt: AstInclude) -> SassResult<Option<Value>> {
1723        let mixin = self
1724            .env
1725            .get_mixin(include_stmt.name, include_stmt.namespace)?;
1726
1727        match mixin {
1728            Mixin::Builtin(mixin) => {
1729                if include_stmt.content.is_some() {
1730                    return Err(("Mixin doesn't accept a content block.", include_stmt.span).into());
1731                }
1732
1733                let args = self.eval_args(include_stmt.args, include_stmt.name.span)?;
1734                mixin(args, self)?;
1735
1736                Ok(None)
1737            }
1738            Mixin::UserDefined(mixin, env) => {
1739                if include_stmt.content.is_some() && !mixin.has_content {
1740                    return Err(("Mixin doesn't accept a content block.", include_stmt.span).into());
1741                }
1742
1743                let AstInclude { args, content, .. } = include_stmt;
1744
1745                let old_in_mixin = self.flags.in_mixin();
1746                self.flags.set(ContextFlags::IN_MIXIN, true);
1747
1748                let callable_content = content.map(|c| {
1749                    Arc::new(CallableContentBlock {
1750                        content: c,
1751                        env: self.env.new_closure(),
1752                    })
1753                });
1754
1755                self.run_user_defined_callable::<_, (), _>(
1756                    MaybeEvaledArguments::Invocation(args),
1757                    mixin,
1758                    &env,
1759                    include_stmt.name.span,
1760                    |mixin, visitor| {
1761                        visitor.with_content(callable_content, |visitor| {
1762                            for stmt in mixin.body {
1763                                let result = visitor.visit_stmt(stmt)?;
1764                                debug_assert!(result.is_none());
1765                            }
1766                            Ok(())
1767                        })
1768                    },
1769                )?;
1770
1771                self.flags.set(ContextFlags::IN_MIXIN, old_in_mixin);
1772
1773                Ok(None)
1774            }
1775        }
1776    }
1777
1778    fn visit_mixin_decl(&mut self, mixin: AstMixin) {
1779        self.env.insert_mixin(
1780            mixin.name,
1781            Mixin::UserDefined(mixin, self.env.new_closure()),
1782        );
1783    }
1784
1785    fn visit_each_stmt(&mut self, each_stmt: AstEach) -> SassResult<Option<Value>> {
1786        let list = self.visit_expr(each_stmt.list)?.as_list();
1787
1788        // todo: not setting semi_global: true maybe means we can't assign to global scope when declared as global
1789        self.env.scopes_mut().enter_new_scope();
1790
1791        let mut result = None;
1792
1793        'outer: for val in list {
1794            if each_stmt.variables.len() == 1 {
1795                let val = self.without_slash(val);
1796                self.env
1797                    .scopes_mut()
1798                    .insert_var_last(each_stmt.variables[0], val);
1799            } else {
1800                for (&var, val) in each_stmt.variables.iter().zip(
1801                    val.as_list()
1802                        .into_iter()
1803                        .chain(std::iter::once(Value::Null).cycle()),
1804                ) {
1805                    let val = self.without_slash(val);
1806                    self.env.scopes_mut().insert_var_last(var, val);
1807                }
1808            }
1809
1810            for stmt in each_stmt.body.clone() {
1811                let val = self.visit_stmt(stmt)?;
1812                if val.is_some() {
1813                    result = val;
1814                    break 'outer;
1815                }
1816            }
1817        }
1818
1819        self.env.scopes_mut().exit_scope();
1820
1821        Ok(result)
1822    }
1823
1824    fn visit_for_stmt(&mut self, for_stmt: AstFor) -> SassResult<Option<Value>> {
1825        let from_span = for_stmt.from.span;
1826        let to_span = for_stmt.to.span;
1827        let from_number = self
1828            .visit_expr(for_stmt.from.node)?
1829            .assert_number(from_span)?;
1830        let to_number = self.visit_expr(for_stmt.to.node)?.assert_number(to_span)?;
1831
1832        if !to_number.unit().comparable(from_number.unit()) {
1833            // todo: better error message here
1834            return Err((
1835                "to and from values have incompatible units",
1836                from_span.merge(to_span),
1837            )
1838                .into());
1839        }
1840
1841        let from = from_number.num.assert_int(from_span)?;
1842        let mut to = to_number
1843            .num
1844            .convert(to_number.unit(), from_number.unit())
1845            .assert_int(to_span)?;
1846
1847        let direction = if from > to { -1 } else { 1 };
1848
1849        if to == i64::MAX || to == i64::MIN {
1850            return Err((
1851                "@for loop upper bound exceeds valid integer representation (i64::MAX)",
1852                to_span,
1853            )
1854                .into());
1855        }
1856
1857        if !for_stmt.is_exclusive {
1858            to += direction;
1859        }
1860
1861        if from == to {
1862            return Ok(None);
1863        }
1864
1865        // todo: self.with_scopes
1866        self.env.scopes_mut().enter_new_scope();
1867
1868        let mut result = None;
1869
1870        let mut i = from;
1871        'outer: while i != to {
1872            self.env.scopes_mut().insert_var_last(
1873                for_stmt.variable.node,
1874                Value::Dimension(SassNumber {
1875                    num: Number::from(i),
1876                    unit: from_number.unit().clone(),
1877                    as_slash: None,
1878                }),
1879            );
1880
1881            for stmt in for_stmt.body.clone() {
1882                let val = self.visit_stmt(stmt)?;
1883                if val.is_some() {
1884                    result = val;
1885                    break 'outer;
1886                }
1887            }
1888
1889            i += direction;
1890        }
1891
1892        self.env.scopes_mut().exit_scope();
1893
1894        Ok(result)
1895    }
1896
1897    fn visit_while_stmt(&mut self, while_stmt: &AstWhile) -> SassResult<Option<Value>> {
1898        self.with_scope(true, true, |visitor| {
1899            let mut result = None;
1900
1901            'outer: while visitor
1902                .visit_expr(while_stmt.condition.clone())?
1903                .is_truthy()
1904            {
1905                for stmt in while_stmt.body.clone() {
1906                    let val = visitor.visit_stmt(stmt)?;
1907                    if val.is_some() {
1908                        result = val;
1909                        break 'outer;
1910                    }
1911                }
1912            }
1913
1914            Ok(result)
1915        })
1916    }
1917
1918    fn visit_if_stmt(&mut self, if_stmt: AstIf) -> SassResult<Option<Value>> {
1919        let mut clause: Option<Vec<AstStmt>> = if_stmt.else_clause;
1920        for clause_to_check in if_stmt.if_clauses {
1921            if self.visit_expr(clause_to_check.condition)?.is_truthy() {
1922                clause = Some(clause_to_check.body);
1923                break;
1924            }
1925        }
1926
1927        // todo: self.with_scope
1928        self.env.scopes_mut().enter_new_scope();
1929
1930        let mut result = None;
1931
1932        if let Some(stmts) = clause {
1933            for stmt in stmts {
1934                let val = self.visit_stmt(stmt)?;
1935                if val.is_some() {
1936                    result = val;
1937                    break;
1938                }
1939            }
1940        }
1941
1942        self.env.scopes_mut().exit_scope();
1943
1944        Ok(result)
1945    }
1946
1947    fn visit_loud_comment(&mut self, comment: AstLoudComment) -> SassResult<Option<Value>> {
1948        if self.flags.in_function() {
1949            return Ok(None);
1950        }
1951
1952        // todo: Comments are allowed to appear between CSS imports
1953        // if (_parent == _root && _endOfImports == _root.children.length) {
1954        //   _endOfImports++;
1955        // }
1956
1957        let comment = CssStmt::Comment(
1958            self.perform_interpolation(comment.text, false)?,
1959            comment.span,
1960        );
1961        self.css_tree.add_stmt(comment, self.parent);
1962
1963        Ok(None)
1964    }
1965
1966    fn visit_variable_decl(&mut self, decl: AstVariableDecl) -> SassResult<Option<Value>> {
1967        let name = Spanned {
1968            node: decl.name,
1969            span: decl.span,
1970        };
1971
1972        if decl.is_guarded {
1973            if decl.namespace.is_none() && self.env.at_root() {
1974                let var_override = (*self.configuration).borrow_mut().remove(decl.name);
1975                if !matches!(
1976                    var_override,
1977                    Some(ConfiguredValue {
1978                        value: Value::Null,
1979                        ..
1980                    }) | None
1981                ) {
1982                    self.env.insert_var(
1983                        name,
1984                        None,
1985                        var_override.unwrap().value,
1986                        true,
1987                        self.flags.in_semi_global_scope(),
1988                    )?;
1989                    return Ok(None);
1990                }
1991            }
1992
1993            if self.env.var_exists(decl.name, decl.namespace)? {
1994                let value = self.env.get_var(name, decl.namespace).unwrap();
1995
1996                if value != Value::Null {
1997                    return Ok(None);
1998                }
1999            }
2000        }
2001
2002        let value = self.visit_expr(decl.value)?;
2003        let value = self.without_slash(value);
2004
2005        self.env.insert_var(
2006            name,
2007            decl.namespace,
2008            value,
2009            decl.is_global,
2010            self.flags.in_semi_global_scope(),
2011        )?;
2012
2013        Ok(None)
2014    }
2015
2016    fn interpolation_to_value(
2017        &mut self,
2018        interpolation: Interpolation,
2019        // default=false
2020        trim: bool,
2021        // default=false
2022        warn_for_color: bool,
2023    ) -> SassResult<String> {
2024        let result = self.perform_interpolation(interpolation, warn_for_color)?;
2025
2026        Ok(if trim {
2027            trim_ascii(&result, true).to_owned()
2028        } else {
2029            result
2030        })
2031    }
2032
2033    fn perform_interpolation(
2034        &mut self,
2035        mut interpolation: Interpolation,
2036        // todo check to emit warning if this is true
2037        _warn_for_color: bool,
2038    ) -> SassResult<String> {
2039        let result = match interpolation.contents.len() {
2040            0 => String::new(),
2041            1 => match interpolation.contents.pop() {
2042                Some(InterpolationPart::String(s)) => s,
2043                Some(InterpolationPart::Expr(e)) => {
2044                    let span = e.span;
2045                    let result = self.visit_expr(e.node)?;
2046                    // todo: span for specific expr
2047                    self.serialize(result, QuoteKind::None, span)?
2048                }
2049                None => unreachable!(),
2050            },
2051            _ => interpolation
2052                .contents
2053                .into_iter()
2054                .map(|part| match part {
2055                    InterpolationPart::String(s) => Ok(s),
2056                    InterpolationPart::Expr(e) => {
2057                        let span = e.span;
2058                        let result = self.visit_expr(e.node)?;
2059                        // todo: span for specific expr
2060                        self.serialize(result, QuoteKind::None, span)
2061                    }
2062                })
2063                .collect::<SassResult<String>>()?,
2064        };
2065
2066        Ok(result)
2067    }
2068
2069    fn evaluate_to_css(
2070        &mut self,
2071        expr: AstExpr,
2072        quote: QuoteKind,
2073        span: Span,
2074    ) -> SassResult<String> {
2075        let result = self.visit_expr(expr)?;
2076        self.serialize(result, quote, span)
2077    }
2078
2079    #[allow(clippy::unused_self)]
2080    fn without_slash(&mut self, v: Value) -> Value {
2081        match v {
2082            Value::Dimension(SassNumber { .. }) if v.as_slash().is_some() => {
2083                // todo: emit warning. we don't currently because it can be quite loud
2084                // self.emit_warning(
2085                //     Cow::Borrowed("Using / for division is deprecated and will be removed at some point in the future"),
2086                //     self.empty_span,
2087                // );
2088            }
2089            _ => {}
2090        }
2091
2092        v.without_slash()
2093    }
2094
2095    fn eval_maybe_args(
2096        &mut self,
2097        args: MaybeEvaledArguments,
2098        span: Span,
2099    ) -> SassResult<ArgumentResult> {
2100        match args {
2101            MaybeEvaledArguments::Invocation(args) => self.eval_args(args, span),
2102            MaybeEvaledArguments::Evaled(args) => Ok(args),
2103        }
2104    }
2105
2106    fn eval_args(
2107        &mut self,
2108        arguments: ArgumentInvocation,
2109        span: Span,
2110    ) -> SassResult<ArgumentResult> {
2111        let mut positional = Vec::with_capacity(arguments.positional.len());
2112
2113        for expr in arguments.positional {
2114            let val = self.visit_expr(expr)?;
2115            positional.push(self.without_slash(val));
2116        }
2117
2118        let mut named = BTreeMap::new();
2119
2120        for (key, expr) in arguments.named {
2121            let val = self.visit_expr(expr)?;
2122            named.insert(key, self.without_slash(val));
2123        }
2124
2125        if arguments.rest.is_none() {
2126            return Ok(ArgumentResult {
2127                positional,
2128                named,
2129                separator: ListSeparator::Undecided,
2130                span,
2131                touched: BTreeSet::new(),
2132            });
2133        }
2134
2135        let rest = self.visit_expr(arguments.rest.unwrap())?;
2136
2137        let mut separator = ListSeparator::Undecided;
2138
2139        match rest {
2140            Value::Map(rest) => self.add_rest_map(&mut named, rest)?,
2141            Value::List(elems, list_separator, _) => {
2142                let mut list = elems
2143                    .into_iter()
2144                    .map(|e| self.without_slash(e))
2145                    .collect::<Vec<_>>();
2146                positional.append(&mut list);
2147                separator = list_separator;
2148            }
2149            Value::ArgList(arglist) => {
2150                // todo: superfluous clone
2151                for (&key, value) in arglist.keywords() {
2152                    named.insert(key, self.without_slash(value.clone()));
2153                }
2154
2155                let mut list = arglist
2156                    .elems
2157                    .into_iter()
2158                    .map(|e| self.without_slash(e))
2159                    .collect::<Vec<_>>();
2160                positional.append(&mut list);
2161                separator = arglist.separator;
2162            }
2163            _ => {
2164                positional.push(self.without_slash(rest));
2165            }
2166        }
2167
2168        if arguments.keyword_rest.is_none() {
2169            return Ok(ArgumentResult {
2170                positional,
2171                named,
2172                separator: ListSeparator::Undecided,
2173                span: arguments.span,
2174                touched: BTreeSet::new(),
2175            });
2176        }
2177
2178        match self.visit_expr(arguments.keyword_rest.unwrap())? {
2179            Value::Map(keyword_rest) => {
2180                self.add_rest_map(&mut named, keyword_rest)?;
2181
2182                Ok(ArgumentResult {
2183                    positional,
2184                    named,
2185                    separator,
2186                    span: arguments.span,
2187                    touched: BTreeSet::new(),
2188                })
2189            }
2190            v => Err((
2191                format!(
2192                    "Variable keyword arguments must be a map (was {}).",
2193                    v.inspect(arguments.span)?
2194                ),
2195                arguments.span,
2196            )
2197                .into()),
2198        }
2199    }
2200
2201    fn add_rest_map(
2202        &mut self,
2203        named: &mut BTreeMap<Identifier, Value>,
2204        rest: SassMap,
2205    ) -> SassResult<()> {
2206        for (key, val) in rest {
2207            match key.node {
2208                Value::String(text, ..) => {
2209                    let val = self.without_slash(val);
2210                    named.insert(Identifier::from(text), val);
2211                }
2212                _ => {
2213                    return Err((
2214                        // todo: we have to render the map for this error message
2215                        "Variable keyword argument map must have string keys.",
2216                        key.span,
2217                    )
2218                        .into());
2219                }
2220            }
2221        }
2222
2223        Ok(())
2224    }
2225
2226    fn run_user_defined_callable<
2227        F: UserDefinedCallable,
2228        V: fmt::Debug,
2229        R: FnOnce(F, &mut Self) -> SassResult<V>,
2230    >(
2231        &mut self,
2232        arguments: MaybeEvaledArguments,
2233        func: F,
2234        env: &Environment,
2235        span: Span,
2236        run: R,
2237    ) -> SassResult<V> {
2238        let mut evaluated = self.eval_maybe_args(arguments, span)?;
2239
2240        let mut name = func.name().to_string();
2241
2242        if name != "@content" {
2243            name.push_str("()");
2244        }
2245
2246        self.with_environment(env.new_closure(), |visitor| {
2247            visitor.with_scope(false, true, move |visitor| {
2248                func.arguments().verify(
2249                    evaluated.positional.len(),
2250                    &evaluated.named,
2251                    evaluated.span,
2252                )?;
2253
2254                let declared_arguments = &func.arguments().args;
2255                let min_len = evaluated.positional.len().min(declared_arguments.len());
2256
2257                let positional_len = evaluated.positional.len();
2258
2259                #[allow(clippy::needless_range_loop)]
2260                for i in (0..min_len).rev() {
2261                    visitor.env.scopes_mut().insert_var_last(
2262                        declared_arguments[i].name,
2263                        evaluated.positional.remove(i),
2264                    );
2265                }
2266
2267                // todo: better name for var
2268                let additional_declared_args = if declared_arguments.len() > positional_len {
2269                    &declared_arguments[positional_len..declared_arguments.len()]
2270                } else {
2271                    &[]
2272                };
2273
2274                for argument in additional_declared_args {
2275                    let name = argument.name;
2276                    let value = evaluated.named.remove(&argument.name).map_or_else(
2277                        || {
2278                            // todo: superfluous clone
2279                            let v = visitor.visit_expr(argument.default.clone().unwrap())?;
2280                            Ok(visitor.without_slash(v))
2281                        },
2282                        SassResult::Ok,
2283                    )?;
2284                    visitor.env.scopes_mut().insert_var_last(name, value);
2285                }
2286
2287                let were_keywords_accessed = Rc::new(Cell::new(false));
2288
2289                let num_named_args = evaluated.named.len();
2290
2291                let has_arg_list = if let Some(rest_arg) = func.arguments().rest {
2292                    let rest = if !evaluated.positional.is_empty() {
2293                        evaluated.positional
2294                    } else {
2295                        Vec::new()
2296                    };
2297
2298                    let arg_list = Value::ArgList(ArgList::new(
2299                        rest,
2300                        Rc::clone(&were_keywords_accessed),
2301                        // todo: superfluous clone
2302                        evaluated.named.clone(),
2303                        if evaluated.separator == ListSeparator::Undecided {
2304                            ListSeparator::Comma
2305                        } else {
2306                            ListSeparator::Space
2307                        },
2308                    ));
2309
2310                    visitor.env.scopes_mut().insert_var_last(rest_arg, arg_list);
2311
2312                    true
2313                } else {
2314                    false
2315                };
2316
2317                let val = run(func, visitor)?;
2318
2319                if !has_arg_list || num_named_args == 0 {
2320                    return Ok(val);
2321                }
2322
2323                if (*were_keywords_accessed).get() {
2324                    return Ok(val);
2325                }
2326
2327                let argument_word = if num_named_args == 1 {
2328                    "argument"
2329                } else {
2330                    "arguments"
2331                };
2332
2333                let argument_names = to_sentence(
2334                    evaluated
2335                        .named
2336                        .keys()
2337                        .map(|key| format!("${key}", key = key))
2338                        .collect(),
2339                    "or",
2340                );
2341
2342                Err((
2343                    format!(
2344                        "No {argument_word} named {argument_names}.",
2345                        argument_word = argument_word,
2346                        argument_names = argument_names
2347                    ),
2348                    span,
2349                )
2350                    .into())
2351            })
2352        })
2353    }
2354
2355    pub(crate) fn run_function_callable(
2356        &mut self,
2357        func: SassFunction,
2358        arguments: ArgumentInvocation,
2359        span: Span,
2360    ) -> SassResult<Value> {
2361        self.run_function_callable_with_maybe_evaled(
2362            func,
2363            MaybeEvaledArguments::Invocation(arguments),
2364            span,
2365        )
2366    }
2367
2368    pub(crate) fn run_function_callable_with_maybe_evaled(
2369        &mut self,
2370        func: SassFunction,
2371        arguments: MaybeEvaledArguments,
2372        span: Span,
2373    ) -> SassResult<Value> {
2374        match func {
2375            SassFunction::Builtin(func, _name) => {
2376                let evaluated = self.eval_maybe_args(arguments, span)?;
2377                let val = func.0(evaluated, self)?;
2378                Ok(self.without_slash(val))
2379            }
2380            SassFunction::UserDefined(UserDefinedFunction { function, env, .. }) => self
2381                .run_user_defined_callable(arguments, function, &env, span, |function, visitor| {
2382                    for stmt in function.body.clone() {
2383                        let result = visitor.visit_stmt(stmt)?;
2384
2385                        if let Some(val) = result {
2386                            return Ok(val);
2387                        }
2388                    }
2389
2390                    Err(("Function finished without @return.", span).into())
2391                }),
2392            SassFunction::Plain { name } => {
2393                let has_named;
2394                let mut rest = None;
2395
2396                // todo: somewhat hacky solution to support plain css fns passed
2397                // as strings to `call(..)`
2398                let arguments = match arguments {
2399                    MaybeEvaledArguments::Invocation(args) => {
2400                        has_named = !args.named.is_empty() || args.keyword_rest.is_some();
2401                        rest = args.rest;
2402                        args.positional
2403                            .into_iter()
2404                            .map(|arg| self.evaluate_to_css(arg, QuoteKind::Quoted, span))
2405                            .collect::<SassResult<Vec<_>>>()?
2406                    }
2407                    MaybeEvaledArguments::Evaled(args) => {
2408                        has_named = !args.named.is_empty();
2409
2410                        args.positional
2411                            .into_iter()
2412                            .map(|arg| arg.to_css_string(span, self.options.is_compressed()))
2413                            .collect::<SassResult<Vec<_>>>()?
2414                    }
2415                };
2416
2417                if has_named {
2418                    return Err(
2419                        ("Plain CSS functions don't support keyword arguments.", span).into(),
2420                    );
2421                }
2422
2423                let mut buffer = format!("{}(", name.as_str());
2424                let mut first = true;
2425
2426                for argument in arguments {
2427                    if first {
2428                        first = false;
2429                    } else {
2430                        buffer.push_str(", ");
2431                    }
2432
2433                    buffer.push_str(&argument);
2434                }
2435
2436                if let Some(rest_arg) = rest {
2437                    let rest = self.visit_expr(rest_arg)?;
2438                    if !first {
2439                        buffer.push_str(", ");
2440                    }
2441                    buffer.push_str(&self.serialize(rest, QuoteKind::Quoted, span)?);
2442                }
2443                buffer.push(')');
2444
2445                Ok(Value::String(buffer, QuoteKind::None))
2446            }
2447        }
2448    }
2449
2450    fn visit_list_expr(&mut self, list: ListExpr) -> SassResult<Value> {
2451        let elems = list
2452            .elems
2453            .into_iter()
2454            .map(|e| {
2455                let value = self.visit_expr(e.node)?;
2456                Ok(value)
2457            })
2458            .collect::<SassResult<Vec<_>>>()?;
2459
2460        Ok(Value::List(elems, list.separator, list.brackets))
2461    }
2462
2463    fn visit_function_call_expr(&mut self, func_call: FunctionCallExpr) -> SassResult<Value> {
2464        let name = func_call.name;
2465
2466        let func = match self.env.get_fn(name, func_call.namespace)? {
2467            Some(func) => func,
2468            None => {
2469                if let Some(f) = self.options.custom_fns.get(name.as_str()) {
2470                    SassFunction::Builtin(f.clone(), name)
2471                } else if let Some(f) = GLOBAL_FUNCTIONS.get(name.as_str()) {
2472                    SassFunction::Builtin(f.clone(), name)
2473                } else {
2474                    if func_call.namespace.is_some() {
2475                        return Err(("Undefined function.", func_call.span).into());
2476                    }
2477
2478                    SassFunction::Plain { name }
2479                }
2480            }
2481        };
2482
2483        let old_in_function = self.flags.in_function();
2484        self.flags.set(ContextFlags::IN_FUNCTION, true);
2485        let value =
2486            self.run_function_callable(func, (*func_call.arguments).clone(), func_call.span)?;
2487        self.flags.set(ContextFlags::IN_FUNCTION, old_in_function);
2488
2489        Ok(value)
2490    }
2491
2492    fn visit_interpolated_func_expr(&mut self, func: InterpolatedFunction) -> SassResult<Value> {
2493        let InterpolatedFunction {
2494            name,
2495            arguments: args,
2496            span,
2497        } = func;
2498        let fn_name = self.perform_interpolation(name, false)?;
2499
2500        if !args.named.is_empty() || args.keyword_rest.is_some() {
2501            return Err(("Plain CSS functions don't support keyword arguments.", span).into());
2502        }
2503
2504        let mut buffer = format!("{}(", fn_name);
2505
2506        let mut first = true;
2507        for arg in args.positional.clone() {
2508            if first {
2509                first = false;
2510            } else {
2511                buffer.push_str(", ");
2512            }
2513            let evaluated = self.evaluate_to_css(arg, QuoteKind::Quoted, span)?;
2514            buffer.push_str(&evaluated);
2515        }
2516
2517        if let Some(rest_arg) = args.rest {
2518            let rest = self.visit_expr(rest_arg)?;
2519            if !first {
2520                buffer.push_str(", ");
2521            }
2522            buffer.push_str(&self.serialize(rest, QuoteKind::None, span)?);
2523        }
2524
2525        buffer.push(')');
2526
2527        Ok(Value::String(buffer, QuoteKind::None))
2528    }
2529
2530    fn visit_parent_selector(&self) -> Value {
2531        match &self.style_rule_ignoring_at_root {
2532            Some(selector) => selector.as_selector_list().clone().to_sass_list(),
2533            None => Value::Null,
2534        }
2535    }
2536
2537    fn visit_expr(&mut self, expr: AstExpr) -> SassResult<Value> {
2538        Ok(match expr {
2539            AstExpr::Color(color) => Value::Color(color),
2540            AstExpr::Number { n, unit } => Value::Dimension(SassNumber {
2541                num: n,
2542                unit,
2543                as_slash: None,
2544            }),
2545            AstExpr::List(list) => self.visit_list_expr(list)?,
2546            AstExpr::String(StringExpr(text, quote), ..) => self.visit_string(text, quote)?,
2547            AstExpr::BinaryOp(binop) => self.visit_bin_op(
2548                binop.lhs.clone(),
2549                binop.op,
2550                binop.rhs.clone(),
2551                binop.allows_slash,
2552                binop.span,
2553            )?,
2554            AstExpr::True => Value::True,
2555            AstExpr::False => Value::False,
2556            AstExpr::Calculation { name, args } => {
2557                self.visit_calculation_expr(name, args, self.empty_span)?
2558            }
2559            AstExpr::FunctionCall(func_call) => self.visit_function_call_expr(func_call)?,
2560            AstExpr::If(if_expr) => self.visit_ternary((*if_expr).clone())?,
2561            AstExpr::InterpolatedFunction(func) => {
2562                self.visit_interpolated_func_expr((*func).clone())?
2563            }
2564            AstExpr::Map(map) => self.visit_map(map)?,
2565            AstExpr::Null => Value::Null,
2566            AstExpr::Paren(expr) => self.visit_expr((*expr).clone())?,
2567            AstExpr::ParentSelector => self.visit_parent_selector(),
2568            AstExpr::UnaryOp(op, expr, span) => self.visit_unary_op(op, (*expr).clone(), span)?,
2569            AstExpr::Variable { name, namespace } => self.env.get_var(name, namespace)?,
2570            AstExpr::Supports(condition) => Value::String(
2571                self.visit_supports_condition((*condition).clone())?,
2572                QuoteKind::None,
2573            ),
2574        })
2575    }
2576
2577    fn visit_calculation_value(
2578        &mut self,
2579        expr: AstExpr,
2580        in_min_or_max: bool,
2581        span: Span,
2582    ) -> SassResult<CalculationArg> {
2583        Ok(match expr {
2584            AstExpr::Paren(inner) => match &*inner {
2585                AstExpr::FunctionCall(FunctionCallExpr { ref name, .. })
2586                    if name.as_str().to_ascii_lowercase() == "var" =>
2587                {
2588                    let result =
2589                        self.visit_calculation_value((*inner).clone(), in_min_or_max, span)?;
2590
2591                    if let CalculationArg::String(text) = result {
2592                        CalculationArg::String(format!("({})", text))
2593                    } else {
2594                        result
2595                    }
2596                }
2597                _ => self.visit_calculation_value((*inner).clone(), in_min_or_max, span)?,
2598            },
2599            AstExpr::String(string_expr, _span) => {
2600                debug_assert!(string_expr.1 == QuoteKind::None);
2601                CalculationArg::Interpolation(self.perform_interpolation(string_expr.0, false)?)
2602            }
2603            AstExpr::BinaryOp(binop) => SassCalculation::operate_internal(
2604                binop.op,
2605                self.visit_calculation_value(binop.lhs.clone(), in_min_or_max, span)?,
2606                self.visit_calculation_value(binop.rhs.clone(), in_min_or_max, span)?,
2607                in_min_or_max,
2608                !self.flags.in_supports_declaration(),
2609                self.options,
2610                span,
2611            )?,
2612            AstExpr::Number { .. }
2613            | AstExpr::Calculation { .. }
2614            | AstExpr::Variable { .. }
2615            | AstExpr::FunctionCall { .. }
2616            | AstExpr::If(..) => {
2617                let result = self.visit_expr(expr)?;
2618                match result {
2619                    Value::Dimension(SassNumber {
2620                        num,
2621                        unit,
2622                        as_slash,
2623                    }) => CalculationArg::Number(SassNumber {
2624                        num,
2625                        unit,
2626                        as_slash,
2627                    }),
2628                    Value::Calculation(calc) => CalculationArg::Calculation(calc),
2629                    Value::String(s, QuoteKind::None) => CalculationArg::String(s),
2630                    value => {
2631                        return Err((
2632                            format!(
2633                                "Value {} can't be used in a calculation.",
2634                                value.inspect(span)?
2635                            ),
2636                            span,
2637                        )
2638                            .into())
2639                    }
2640                }
2641            }
2642            v => unreachable!("{:?}", v),
2643        })
2644    }
2645
2646    fn visit_calculation_expr(
2647        &mut self,
2648        name: CalculationName,
2649        args: Vec<AstExpr>,
2650        span: Span,
2651    ) -> SassResult<Value> {
2652        let mut args = args
2653            .into_iter()
2654            .map(|arg| self.visit_calculation_value(arg, name.in_min_or_max(), span))
2655            .collect::<SassResult<Vec<_>>>()?;
2656
2657        if self.flags.in_supports_declaration() {
2658            return Ok(Value::Calculation(SassCalculation::unsimplified(
2659                name, args,
2660            )));
2661        }
2662
2663        match name {
2664            CalculationName::Calc => {
2665                debug_assert_eq!(args.len(), 1);
2666                Ok(SassCalculation::calc(args.remove(0)))
2667            }
2668            CalculationName::Min => SassCalculation::min(args, self.options, span),
2669            CalculationName::Max => SassCalculation::max(args, self.options, span),
2670            CalculationName::Clamp => {
2671                let min = args.remove(0);
2672                let value = if args.is_empty() {
2673                    None
2674                } else {
2675                    Some(args.remove(0))
2676                };
2677                let max = if args.is_empty() {
2678                    None
2679                } else {
2680                    Some(args.remove(0))
2681                };
2682                SassCalculation::clamp(min, value, max, self.options, span)
2683            }
2684        }
2685    }
2686
2687    fn visit_unary_op(&mut self, op: UnaryOp, expr: AstExpr, span: Span) -> SassResult<Value> {
2688        let operand = self.visit_expr(expr)?;
2689
2690        match op {
2691            UnaryOp::Plus => operand.unary_plus(self, span),
2692            UnaryOp::Neg => operand.unary_neg(self, span),
2693            UnaryOp::Div => operand.unary_div(self, span),
2694            UnaryOp::Not => Ok(operand.unary_not()),
2695        }
2696    }
2697
2698    fn visit_ternary(&mut self, if_expr: Ternary) -> SassResult<Value> {
2699        if_arguments().verify(if_expr.0.positional.len(), &if_expr.0.named, if_expr.0.span)?;
2700
2701        let mut positional = if_expr.0.positional;
2702        let mut named = if_expr.0.named;
2703
2704        let condition = if positional.is_empty() {
2705            named.remove(&Identifier::from("condition")).unwrap()
2706        } else {
2707            positional.remove(0)
2708        };
2709
2710        let if_true = if positional.is_empty() {
2711            named.remove(&Identifier::from("if_true")).unwrap()
2712        } else {
2713            positional.remove(0)
2714        };
2715
2716        let if_false = if positional.is_empty() {
2717            named.remove(&Identifier::from("if_false")).unwrap()
2718        } else {
2719            positional.remove(0)
2720        };
2721
2722        let value = if self.visit_expr(condition)?.is_truthy() {
2723            self.visit_expr(if_true)?
2724        } else {
2725            self.visit_expr(if_false)?
2726        };
2727
2728        Ok(self.without_slash(value))
2729    }
2730
2731    fn visit_string(&mut self, mut text: Interpolation, quote: QuoteKind) -> SassResult<Value> {
2732        // Don't use [performInterpolation] here because we need to get the raw text
2733        // from strings, rather than the semantic value.
2734        let old_in_supports_declaration = self.flags.in_supports_declaration();
2735        self.flags.set(ContextFlags::IN_SUPPORTS_DECLARATION, false);
2736
2737        let result = match text.contents.len() {
2738            0 => String::new(),
2739            1 => match text.contents.pop() {
2740                Some(InterpolationPart::String(s)) => s,
2741                Some(InterpolationPart::Expr(Spanned { node, span })) => {
2742                    match self.visit_expr(node)? {
2743                        Value::String(s, ..) => s,
2744                        e => self.serialize(e, QuoteKind::None, span)?,
2745                    }
2746                }
2747                None => unreachable!(),
2748            },
2749            _ => text
2750                .contents
2751                .into_iter()
2752                .map(|part| match part {
2753                    InterpolationPart::String(s) => Ok(s),
2754                    InterpolationPart::Expr(Spanned { node, span }) => {
2755                        match self.visit_expr(node)? {
2756                            Value::String(s, ..) => Ok(s),
2757                            e => self.serialize(e, QuoteKind::None, span),
2758                        }
2759                    }
2760                })
2761                .collect::<SassResult<String>>()?,
2762        };
2763
2764        self.flags.set(
2765            ContextFlags::IN_SUPPORTS_DECLARATION,
2766            old_in_supports_declaration,
2767        );
2768
2769        Ok(Value::String(result, quote))
2770    }
2771
2772    fn visit_map(&mut self, map: AstSassMap) -> SassResult<Value> {
2773        let mut sass_map = SassMap::new();
2774
2775        for pair in map.0 {
2776            let key_span = pair.0.span;
2777            let key = self.visit_expr(pair.0.node)?;
2778            let value = self.visit_expr(pair.1)?;
2779
2780            if sass_map.get_ref(&key).is_some() {
2781                return Err(("Duplicate key.", key_span).into());
2782            }
2783
2784            sass_map.insert(
2785                Spanned {
2786                    node: key,
2787                    span: key_span,
2788                },
2789                value,
2790            );
2791        }
2792
2793        Ok(Value::Map(sass_map))
2794    }
2795
2796    fn visit_bin_op(
2797        &mut self,
2798        lhs: AstExpr,
2799        op: BinaryOp,
2800        rhs: AstExpr,
2801        allows_slash: bool,
2802        span: Span,
2803    ) -> SassResult<Value> {
2804        let left = self.visit_expr(lhs)?;
2805
2806        Ok(match op {
2807            BinaryOp::SingleEq => {
2808                let right = self.visit_expr(rhs)?;
2809                single_eq(&left, &right, self.options, span)?
2810            }
2811            BinaryOp::Or => {
2812                if left.is_truthy() {
2813                    left
2814                } else {
2815                    self.visit_expr(rhs)?
2816                }
2817            }
2818            BinaryOp::And => {
2819                if left.is_truthy() {
2820                    self.visit_expr(rhs)?
2821                } else {
2822                    left
2823                }
2824            }
2825            BinaryOp::Equal => {
2826                let right = self.visit_expr(rhs)?;
2827                Value::bool(left == right)
2828            }
2829            BinaryOp::NotEqual => {
2830                let right = self.visit_expr(rhs)?;
2831                Value::bool(left != right)
2832            }
2833            BinaryOp::GreaterThan
2834            | BinaryOp::GreaterThanEqual
2835            | BinaryOp::LessThan
2836            | BinaryOp::LessThanEqual => {
2837                let right = self.visit_expr(rhs)?;
2838                cmp(&left, &right, self.options, span, op)?
2839            }
2840            BinaryOp::Plus => {
2841                let right = self.visit_expr(rhs)?;
2842                add(left, right, self.options, span)?
2843            }
2844            BinaryOp::Minus => {
2845                let right = self.visit_expr(rhs)?;
2846                sub(left, right, self.options, span)?
2847            }
2848            BinaryOp::Mul => {
2849                let right = self.visit_expr(rhs)?;
2850                mul(left, right, self.options, span)?
2851            }
2852            BinaryOp::Div => {
2853                let right = self.visit_expr(rhs)?;
2854
2855                let left_is_number = matches!(left, Value::Dimension { .. });
2856                let right_is_number = matches!(right, Value::Dimension { .. });
2857
2858                if left_is_number && right_is_number && allows_slash {
2859                    let result = div(left.clone(), right.clone(), self.options, span)?;
2860                    return result.with_slash(
2861                        left.assert_number(span)?,
2862                        right.assert_number(span)?,
2863                        span,
2864                    );
2865                } else if left_is_number && right_is_number {
2866                    // todo: emit warning here. it prints too frequently, so we do not currently
2867                    // self.emit_warning(
2868                    //     Cow::Borrowed(format!(
2869                    //         "Using / for division outside of calc() is deprecated"
2870                    //     )),
2871                    //     span,
2872                    // );
2873                }
2874
2875                div(left, right, self.options, span)?
2876            }
2877            BinaryOp::Rem => {
2878                let right = self.visit_expr(rhs)?;
2879                rem(left, right, self.options, span)?
2880            }
2881        })
2882    }
2883
2884    // todo: superfluous taking `expr` by value
2885    fn serialize(&mut self, mut expr: Value, quote: QuoteKind, span: Span) -> SassResult<String> {
2886        if quote == QuoteKind::None {
2887            expr = expr.unquote();
2888        }
2889
2890        expr.to_css_string(span, self.options.is_compressed())
2891    }
2892
2893    pub(crate) fn visit_ruleset(&mut self, ruleset: AstRuleSet) -> SassResult<Option<Value>> {
2894        if self.declaration_name.is_some() {
2895            return Err((
2896                "Style rules may not be used within nested declarations.",
2897                ruleset.span,
2898            )
2899                .into());
2900        }
2901
2902        let AstRuleSet {
2903            selector: ruleset_selector,
2904            body: ruleset_body,
2905            ..
2906        } = ruleset;
2907
2908        let selector_text = self.interpolation_to_value(ruleset_selector, true, true)?;
2909
2910        if self.flags.in_keyframes() {
2911            let span = ruleset.selector_span;
2912            let sel_toks = Lexer::new_from_string(&selector_text, span);
2913            let parsed_selector =
2914                KeyframesSelectorParser::new(sel_toks).parse_keyframes_selector()?;
2915
2916            let keyframes_ruleset = CssStmt::KeyframesRuleSet(KeyframesRuleSet {
2917                selector: parsed_selector,
2918                body: Vec::new(),
2919            });
2920
2921            self.with_parent(
2922                keyframes_ruleset,
2923                true,
2924                |visitor| {
2925                    for stmt in ruleset_body {
2926                        let result = visitor.visit_stmt(stmt)?;
2927                        debug_assert!(result.is_none());
2928                    }
2929
2930                    Ok(())
2931                },
2932                CssStmt::is_style_rule,
2933            )?;
2934
2935            return Ok(None);
2936        }
2937
2938        let mut parsed_selector = self.parse_selector_from_string(
2939            &selector_text,
2940            !self.is_plain_css,
2941            !self.is_plain_css,
2942            ruleset.selector_span,
2943        )?;
2944
2945        parsed_selector = parsed_selector.resolve_parent_selectors(
2946            self.style_rule_ignoring_at_root
2947                .as_ref()
2948                // todo: this clone should be superfluous(?)
2949                .map(|x| x.as_selector_list().clone()),
2950            !self.flags.at_root_excluding_style_rule(),
2951        )?;
2952
2953        // todo: _mediaQueries
2954        let selector = self
2955            .extender
2956            .add_selector(parsed_selector, &self.media_queries);
2957
2958        let rule = CssStmt::RuleSet {
2959            selector: selector.clone(),
2960            body: Vec::new(),
2961            is_group_end: false,
2962        };
2963
2964        let old_at_root_excluding_style_rule = self.flags.at_root_excluding_style_rule();
2965
2966        self.flags
2967            .set(ContextFlags::AT_ROOT_EXCLUDING_STYLE_RULE, false);
2968
2969        let old_style_rule_ignoring_at_root = self.style_rule_ignoring_at_root.take();
2970        self.style_rule_ignoring_at_root = Some(selector);
2971
2972        self.with_parent(
2973            rule,
2974            true,
2975            |visitor| {
2976                for stmt in ruleset_body {
2977                    let result = visitor.visit_stmt(stmt)?;
2978                    debug_assert!(result.is_none());
2979                }
2980
2981                Ok(())
2982            },
2983            CssStmt::is_style_rule,
2984        )?;
2985
2986        self.style_rule_ignoring_at_root = old_style_rule_ignoring_at_root;
2987        self.flags.set(
2988            ContextFlags::AT_ROOT_EXCLUDING_STYLE_RULE,
2989            old_at_root_excluding_style_rule,
2990        );
2991
2992        self.set_group_end();
2993
2994        Ok(None)
2995    }
2996
2997    fn set_group_end(&mut self) -> Option<()> {
2998        if !self.style_rule_exists() {
2999            let children = self
3000                .css_tree
3001                .parent_to_child
3002                .get(&self.parent.unwrap_or(CssTree::ROOT))?;
3003            let child = *children.last()?;
3004            self.css_tree
3005                .get_mut(child)
3006                .as_mut()
3007                .map(CssStmt::set_group_end)?;
3008        }
3009
3010        Some(())
3011    }
3012
3013    fn style_rule_exists(&self) -> bool {
3014        !self.flags.at_root_excluding_style_rule() && self.style_rule_ignoring_at_root.is_some()
3015    }
3016
3017    pub(crate) fn visit_style(&mut self, style: AstStyle) -> SassResult<Option<Value>> {
3018        if !self.style_rule_exists()
3019            && !self.flags.in_unknown_at_rule()
3020            && !self.flags.in_keyframes()
3021        {
3022            return Err((
3023                "Declarations may only be used within style rules.",
3024                style.span,
3025            )
3026                .into());
3027        }
3028
3029        let is_custom_property = style.is_custom_property();
3030
3031        let mut name = self.interpolation_to_value(style.name, false, true)?;
3032
3033        if let Some(declaration_name) = &self.declaration_name {
3034            name = format!("{}-{}", declaration_name, name);
3035        }
3036
3037        if let Some(value) = style
3038            .value
3039            .map(|s| {
3040                SassResult::Ok(Spanned {
3041                    node: self.visit_expr(s.node)?,
3042                    span: s.span,
3043                })
3044            })
3045            .transpose()?
3046        {
3047            // If the value is an empty list, preserve it, because converting it to CSS
3048            // will throw an error that we want the user to see.
3049            if !value.is_blank() || value.is_empty_list() {
3050                // todo: superfluous clones?
3051                self.css_tree.add_stmt(
3052                    CssStmt::Style(Style {
3053                        property: InternedString::get_or_intern(&name),
3054                        value: Box::new(value),
3055                        declared_as_custom_property: is_custom_property,
3056                    }),
3057                    self.parent,
3058                );
3059            } else if name.starts_with("--") {
3060                return Err(("Custom property values may not be empty.", style.span).into());
3061            }
3062        }
3063
3064        let children = style.body;
3065
3066        if !children.is_empty() {
3067            let old_declaration_name = self.declaration_name.take();
3068            self.declaration_name = Some(name);
3069            self.with_scope::<SassResult<()>, _>(false, true, |visitor| {
3070                for stmt in children {
3071                    let result = visitor.visit_stmt(stmt)?;
3072                    debug_assert!(result.is_none());
3073                }
3074
3075                Ok(())
3076            })?;
3077            self.declaration_name = old_declaration_name;
3078        }
3079
3080        Ok(None)
3081    }
3082}