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#[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 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 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 empty_span: Span,
130 import_cache: BTreeMap<PathBuf, StyleSheet>,
131 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 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 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 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 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 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 _names_in_errors: bool,
548 ) -> SassResult<Arc<RefCell<Module>>> {
549 let url = stylesheet.url.clone();
550
551 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 }
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.parent = old_parent;
613 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 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 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 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 #[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 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 for_import: bool,
927 span: Span,
928 ) -> SassResult<StyleSheet> {
929 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 stylesheet.uses.is_empty() && stylesheet.forwards.is_empty() {
949 self.visit_stylesheet(stylesheet)?;
950 return Ok(());
951 }
952
953 let loads_user_defined_modules = true;
955
956 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 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 let module = env.to_dummy_module(self.empty_span);
989 self.env.import_forwards(module);
990
991 if loads_user_defined_modules {
992 }
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 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 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 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 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 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 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 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 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 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 scope_when: bool,
1666 callback: F,
1667 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 semi_global: bool,
1682 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 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 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 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 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 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 trim: bool,
2021 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 _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 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 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 }
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 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 "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 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 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 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 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 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 }
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 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 .map(|x| x.as_selector_list().clone()),
2950 !self.flags.at_root_excluding_style_rule(),
2951 )?;
2952
2953 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 !value.is_blank() || value.is_empty_list() {
3050 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}