1 use std::cmp::Ordering;
2 
3 use codemap::{Span, Spanned};
4 
5 use crate::{
6     color::Color,
7     common::{Brackets, ListSeparator, Op, QuoteKind},
8     error::SassResult,
9     lexer::Lexer,
10     parse::Parser,
11     selector::Selector,
12     unit::Unit,
13     utils::hex_char_for,
14     {Cow, Token},
15 };
16 
17 use css_function::is_special_function;
18 pub(crate) use map::SassMap;
19 pub(crate) use number::Number;
20 pub(crate) use sass_function::SassFunction;
21 
22 pub(crate) mod css_function;
23 mod map;
24 mod number;
25 mod sass_function;
26 
27 #[derive(Debug, Clone)]
28 pub(crate) enum Value {
29     Important,
30     True,
31     False,
32     Null,
33     /// A `None` value for `Number` indicates a `NaN` value
34     Dimension(Option<Number>, Unit, bool),
35     List(Vec<Value>, ListSeparator, Brackets),
36     Color(Box<Color>),
37     String(String, QuoteKind),
38     Map(SassMap),
39     ArgList(Vec<Spanned<Value>>),
40     /// Returned by `get-function()`
41     FunctionRef(SassFunction),
42 }
43 
44 impl PartialEq for Value {
eq(&self, other: &Self) -> bool45     fn eq(&self, other: &Self) -> bool {
46         match self {
47             Value::String(s1, ..) => match other {
48                 Value::String(s2, ..) => s1 == s2,
49                 _ => false,
50             },
51             Value::Dimension(Some(n), unit, _) => match other {
52                 Value::Dimension(Some(n2), unit2, _) => {
53                     if !unit.comparable(unit2) {
54                         false
55                     } else if unit == unit2 {
56                         n == n2
57                     } else if unit == &Unit::None || unit2 == &Unit::None {
58                         false
59                     } else {
60                         n == &n2.clone().convert(unit2, unit)
61                     }
62                 }
63                 _ => false,
64             },
65             Value::Dimension(None, ..) => false,
66             Value::List(list1, sep1, brackets1) => match other {
67                 Value::List(list2, sep2, brackets2) => {
68                     if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
69                         false
70                     } else {
71                         for (a, b) in list1.iter().zip(list2) {
72                             if a != b {
73                                 return false;
74                             }
75                         }
76                         true
77                     }
78                 }
79                 _ => false,
80             },
81             Value::Null => matches!(other, Value::Null),
82             Value::True => matches!(other, Value::True),
83             Value::False => matches!(other, Value::False),
84             Value::Important => matches!(other, Value::Important),
85             Value::FunctionRef(fn1) => {
86                 if let Value::FunctionRef(fn2) = other {
87                     fn1 == fn2
88                 } else {
89                     false
90                 }
91             }
92             Value::Map(map1) => {
93                 if let Value::Map(map2) = other {
94                     map1 == map2
95                 } else {
96                     false
97                 }
98             }
99             Value::Color(color1) => {
100                 if let Value::Color(color2) = other {
101                     color1 == color2
102                 } else {
103                     false
104                 }
105             }
106             Value::ArgList(list1) => match other {
107                 Value::ArgList(list2) => list1 == list2,
108                 Value::List(list2, ListSeparator::Comma, ..) => {
109                     if list1.len() != list2.len() {
110                         return false;
111                     }
112 
113                     for (el1, el2) in list1.iter().zip(list2) {
114                         if &el1.node != el2 {
115                             return false;
116                         }
117                     }
118 
119                     true
120                 }
121                 _ => false,
122             },
123         }
124     }
125 }
126 
127 impl Eq for Value {}
128 
visit_quoted_string(buf: &mut String, force_double_quote: bool, string: &str)129 fn visit_quoted_string(buf: &mut String, force_double_quote: bool, string: &str) {
130     let mut has_single_quote = false;
131     let mut has_double_quote = false;
132 
133     let mut buffer = String::new();
134 
135     if force_double_quote {
136         buffer.push('"');
137     }
138     let mut iter = string.chars().peekable();
139     while let Some(c) = iter.next() {
140         match c {
141             '\'' => {
142                 if force_double_quote {
143                     buffer.push('\'');
144                 } else if has_double_quote {
145                     return visit_quoted_string(buf, true, string);
146                 } else {
147                     has_single_quote = true;
148                     buffer.push('\'');
149                 }
150             }
151             '"' => {
152                 if force_double_quote {
153                     buffer.push('\\');
154                     buffer.push('"');
155                 } else if has_single_quote {
156                     return visit_quoted_string(buf, true, string);
157                 } else {
158                     has_double_quote = true;
159                     buffer.push('"');
160                 }
161             }
162             '\x00'..='\x08' | '\x0A'..='\x1F' => {
163                 buffer.push('\\');
164                 if c as u32 > 0xF {
165                     buffer.push(hex_char_for(c as u32 >> 4));
166                 }
167                 buffer.push(hex_char_for(c as u32 & 0xF));
168 
169                 let next = match iter.peek() {
170                     Some(v) => v,
171                     None => break,
172                 };
173 
174                 if next.is_ascii_hexdigit() || next == &' ' || next == &'\t' {
175                     buffer.push(' ');
176                 }
177             }
178             '\\' => {
179                 buffer.push('\\');
180                 buffer.push('\\');
181             }
182             _ => buffer.push(c),
183         }
184     }
185 
186     if force_double_quote {
187         buffer.push('"');
188     } else {
189         let quote = if has_double_quote { '\'' } else { '"' };
190         buffer = format!("{}{}{}", quote, buffer, quote);
191     }
192     buf.push_str(&buffer);
193 }
194 
195 impl Value {
is_null(&self) -> bool196     pub fn is_null(&self) -> bool {
197         match self {
198             Value::Null => true,
199             Value::String(i, QuoteKind::None) if i.is_empty() => true,
200             Value::List(v, _, Brackets::Bracketed) if v.is_empty() => false,
201             Value::List(v, ..) => v.iter().map(Value::is_null).all(|f| f),
202             Value::ArgList(v, ..) if v.is_empty() => false,
203             Value::ArgList(v, ..) => v.iter().map(|v| v.node.is_null()).all(|f| f),
204             _ => false,
205         }
206     }
207 
to_css_string(&self, span: Span, is_compressed: bool) -> SassResult<Cow<'static, str>>208     pub fn to_css_string(&self, span: Span, is_compressed: bool) -> SassResult<Cow<'static, str>> {
209         Ok(match self {
210             Value::Important => Cow::const_str("!important"),
211             Value::Dimension(num, unit, _) => match unit {
212                 Unit::Mul(..) | Unit::Div(..) => {
213                     if let Some(num) = num {
214                         return Err((
215                             format!(
216                                 "{}{} isn't a valid CSS value.",
217                                 num.to_string(is_compressed),
218                                 unit
219                             ),
220                             span,
221                         )
222                             .into());
223                     }
224 
225                     return Err((format!("NaN{} isn't a valid CSS value.", unit), span).into());
226                 }
227                 _ => {
228                     if let Some(num) = num {
229                         Cow::owned(format!("{}{}", num.to_string(is_compressed), unit))
230                     } else {
231                         Cow::owned(format!("NaN{}", unit))
232                     }
233                 }
234             },
235             Value::Map(..) | Value::FunctionRef(..) => {
236                 return Err((
237                     format!("{} isn't a valid CSS value.", self.inspect(span)?),
238                     span,
239                 )
240                     .into())
241             }
242             Value::List(vals, sep, brackets) => match brackets {
243                 Brackets::None => Cow::owned(
244                     vals.iter()
245                         .filter(|x| !x.is_null())
246                         .map(|x| x.to_css_string(span, is_compressed))
247                         .collect::<SassResult<Vec<Cow<'static, str>>>>()?
248                         .join(if is_compressed {
249                             sep.as_compressed_str()
250                         } else {
251                             sep.as_str()
252                         }),
253                 ),
254                 Brackets::Bracketed => Cow::owned(format!(
255                     "[{}]",
256                     vals.iter()
257                         .filter(|x| !x.is_null())
258                         .map(|x| x.to_css_string(span, is_compressed))
259                         .collect::<SassResult<Vec<Cow<'static, str>>>>()?
260                         .join(if is_compressed {
261                             sep.as_compressed_str()
262                         } else {
263                             sep.as_str()
264                         }),
265                 )),
266             },
267             Value::Color(c) => Cow::owned(c.to_string()),
268             Value::String(string, QuoteKind::None) => {
269                 let mut after_newline = false;
270                 let mut buf = String::with_capacity(string.len());
271                 for c in string.chars() {
272                     match c {
273                         '\n' => {
274                             buf.push(' ');
275                             after_newline = true;
276                         }
277                         ' ' => {
278                             if !after_newline {
279                                 buf.push(' ');
280                             }
281                         }
282                         _ => {
283                             buf.push(c);
284                             after_newline = false;
285                         }
286                     }
287                 }
288                 Cow::owned(buf)
289             }
290             Value::String(string, QuoteKind::Quoted) => {
291                 let mut buf = String::with_capacity(string.len());
292                 visit_quoted_string(&mut buf, false, string);
293                 Cow::owned(buf)
294             }
295             Value::True => Cow::const_str("true"),
296             Value::False => Cow::const_str("false"),
297             Value::Null => Cow::const_str(""),
298             Value::ArgList(args) if args.is_empty() => {
299                 return Err(("() isn't a valid CSS value.", span).into());
300             }
301             Value::ArgList(args) => Cow::owned(
302                 args.iter()
303                     .filter(|x| !x.is_null())
304                     .map(|a| a.node.to_css_string(span, is_compressed))
305                     .collect::<SassResult<Vec<Cow<'static, str>>>>()?
306                     .join(if is_compressed {
307                         ListSeparator::Comma.as_compressed_str()
308                     } else {
309                         ListSeparator::Comma.as_str()
310                     }),
311             ),
312         })
313     }
314 
is_true(&self) -> bool315     pub fn is_true(&self) -> bool {
316         !matches!(self, Value::Null | Value::False)
317     }
318 
unquote(self) -> Self319     pub fn unquote(self) -> Self {
320         match self {
321             Value::String(s1, _) => Value::String(s1, QuoteKind::None),
322             Value::List(v, sep, bracket) => {
323                 Value::List(v.into_iter().map(Value::unquote).collect(), sep, bracket)
324             }
325             v => v,
326         }
327     }
328 
span(self, span: Span) -> Spanned<Self>329     pub const fn span(self, span: Span) -> Spanned<Self> {
330         Spanned { node: self, span }
331     }
332 
kind(&self) -> &'static str333     pub fn kind(&self) -> &'static str {
334         match self {
335             Value::Color(..) => "color",
336             Value::String(..) | Value::Important => "string",
337             Value::Dimension(..) => "number",
338             Value::List(..) => "list",
339             Value::FunctionRef(..) => "function",
340             Value::ArgList(..) => "arglist",
341             Value::True | Value::False => "bool",
342             Value::Null => "null",
343             Value::Map(..) => "map",
344         }
345     }
346 
is_color(&self) -> bool347     pub fn is_color(&self) -> bool {
348         matches!(self, Value::Color(..))
349     }
350 
is_special_function(&self) -> bool351     pub fn is_special_function(&self) -> bool {
352         match self {
353             Value::String(s, QuoteKind::None) => is_special_function(s),
354             _ => false,
355         }
356     }
357 
bool(b: bool) -> Self358     pub fn bool(b: bool) -> Self {
359         if b {
360             Value::True
361         } else {
362             Value::False
363         }
364     }
365 
cmp(&self, other: &Self, span: Span, op: Op) -> SassResult<Ordering>366     pub fn cmp(&self, other: &Self, span: Span, op: Op) -> SassResult<Ordering> {
367         Ok(match self {
368             Value::Dimension(None, ..) => todo!(),
369             Value::Dimension(Some(num), unit, _) => match &other {
370                 Value::Dimension(None, ..) => todo!(),
371                 Value::Dimension(Some(num2), unit2, _) => {
372                     if !unit.comparable(unit2) {
373                         return Err(
374                             (format!("Incompatible units {} and {}.", unit2, unit), span).into(),
375                         );
376                     }
377                     if unit == unit2 || unit == &Unit::None || unit2 == &Unit::None {
378                         num.cmp(num2)
379                     } else {
380                         num.cmp(&num2.clone().convert(unit2, unit))
381                     }
382                 }
383                 _ => {
384                     return Err((
385                         format!(
386                             "Undefined operation \"{} {} {}\".",
387                             self.inspect(span)?,
388                             op,
389                             other.inspect(span)?
390                         ),
391                         span,
392                     )
393                         .into())
394                 }
395             },
396             _ => {
397                 return Err((
398                     format!(
399                         "Undefined operation \"{} {} {}\".",
400                         self.inspect(span)?,
401                         op,
402                         other.inspect(span)?
403                     ),
404                     span,
405                 )
406                     .into());
407             }
408         })
409     }
410 
not_equals(&self, other: &Self) -> bool411     pub fn not_equals(&self, other: &Self) -> bool {
412         match self {
413             Value::String(s1, ..) => match other {
414                 Value::String(s2, ..) => s1 != s2,
415                 _ => true,
416             },
417             Value::Dimension(Some(n), unit, _) => match other {
418                 Value::Dimension(Some(n2), unit2, _) => {
419                     if !unit.comparable(unit2) {
420                         true
421                     } else if unit == unit2 {
422                         n != n2
423                     } else if unit == &Unit::None || unit2 == &Unit::None {
424                         true
425                     } else {
426                         n != &n2.clone().convert(unit2, unit)
427                     }
428                 }
429                 _ => true,
430             },
431             Value::List(list1, sep1, brackets1) => match other {
432                 Value::List(list2, sep2, brackets2) => {
433                     if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
434                         true
435                     } else {
436                         for (a, b) in list1.iter().zip(list2) {
437                             if a.not_equals(b) {
438                                 return true;
439                             }
440                         }
441                         false
442                     }
443                 }
444                 _ => true,
445             },
446             s => s != other,
447         }
448     }
449 
450     // TODO:
451     // https://github.com/sass/dart-sass/blob/d4adea7569832f10e3a26d0e420ae51640740cfb/lib/src/ast/sass/expression/list.dart#L39
inspect(&self, span: Span) -> SassResult<Cow<'static, str>>452     pub fn inspect(&self, span: Span) -> SassResult<Cow<'static, str>> {
453         Ok(match self {
454             Value::List(v, _, brackets) if v.is_empty() => match brackets {
455                 Brackets::None => Cow::const_str("()"),
456                 Brackets::Bracketed => Cow::const_str("[]"),
457             },
458             Value::List(v, sep, brackets) if v.len() == 1 => match brackets {
459                 Brackets::None => match sep {
460                     ListSeparator::Space => v[0].inspect(span)?,
461                     ListSeparator::Comma => Cow::owned(format!("({},)", v[0].inspect(span)?)),
462                 },
463                 Brackets::Bracketed => match sep {
464                     ListSeparator::Space => Cow::owned(format!("[{}]", v[0].inspect(span)?)),
465                     ListSeparator::Comma => Cow::owned(format!("[{},]", v[0].inspect(span)?)),
466                 },
467             },
468             Value::List(vals, sep, brackets) => Cow::owned(match brackets {
469                 Brackets::None => vals
470                     .iter()
471                     .map(|x| x.inspect(span))
472                     .collect::<SassResult<Vec<Cow<'static, str>>>>()?
473                     .join(sep.as_str()),
474                 Brackets::Bracketed => format!(
475                     "[{}]",
476                     vals.iter()
477                         .map(|x| x.inspect(span))
478                         .collect::<SassResult<Vec<Cow<'static, str>>>>()?
479                         .join(sep.as_str()),
480                 ),
481             }),
482             Value::FunctionRef(f) => Cow::owned(format!("get-function(\"{}\")", f.name())),
483             Value::Null => Cow::const_str("null"),
484             Value::Map(map) => Cow::owned(format!(
485                 "({})",
486                 map.iter()
487                     .map(|(k, v)| Ok(format!("{}: {}", k.inspect(span)?, v.inspect(span)?)))
488                     .collect::<SassResult<Vec<String>>>()?
489                     .join(", ")
490             )),
491             Value::Dimension(Some(num), unit, _) => {
492                 Cow::owned(format!("{}{}", num.inspect(), unit))
493             }
494             Value::Dimension(None, unit, ..) => Cow::owned(format!("NaN{}", unit)),
495             Value::ArgList(args) if args.is_empty() => Cow::const_str("()"),
496             Value::ArgList(args) if args.len() == 1 => Cow::owned(format!(
497                 "({},)",
498                 args.iter()
499                     .filter(|x| !x.is_null())
500                     .map(|a| a.node.inspect(span))
501                     .collect::<SassResult<Vec<Cow<'static, str>>>>()?
502                     .join(", "),
503             )),
504             Value::ArgList(args) => Cow::owned(
505                 args.iter()
506                     .filter(|x| !x.is_null())
507                     .map(|a| a.node.inspect(span))
508                     .collect::<SassResult<Vec<Cow<'static, str>>>>()?
509                     .join(", "),
510             ),
511             Value::Important
512             | Value::True
513             | Value::False
514             | Value::Color(..)
515             | Value::String(..) => self.to_css_string(span, false)?,
516         })
517     }
518 
as_list(self) -> Vec<Value>519     pub fn as_list(self) -> Vec<Value> {
520         match self {
521             Value::List(v, ..) => v,
522             Value::Map(m) => m.as_list(),
523             Value::ArgList(v) => v.into_iter().map(|val| val.node).collect(),
524             v => vec![v],
525         }
526     }
527 
528     /// Parses `self` as a selector list, in the same manner as the
529     /// `selector-parse()` function.
530     ///
531     /// Returns a `SassError` if `self` isn't a type that can be parsed as a
532     /// selector, or if parsing fails. If `allow_parent` is `true`, this allows
533     /// parent selectors. Otherwise, they're considered parse errors.
534     ///
535     /// `name` is the argument name. It's used for error reporting.
to_selector( self, parser: &mut Parser, name: &str, allows_parent: bool, ) -> SassResult<Selector>536     pub fn to_selector(
537         self,
538         parser: &mut Parser,
539         name: &str,
540         allows_parent: bool,
541     ) -> SassResult<Selector> {
542         let string = match self.clone().selector_string(parser.span_before)? {
543             Some(v) => v,
544             None => return Err((format!("${}: {} is not a valid selector: it must be a string, a list of strings, or a list of lists of strings.", name, self.inspect(parser.span_before)?), parser.span_before).into()),
545         };
546         Ok(Parser {
547             toks: &mut Lexer::new(
548                 string
549                     .chars()
550                     .map(|c| Token::new(parser.span_before, c))
551                     .collect::<Vec<Token>>(),
552             ),
553             map: parser.map,
554             path: parser.path,
555             scopes: parser.scopes,
556             global_scope: parser.global_scope,
557             super_selectors: parser.super_selectors,
558             span_before: parser.span_before,
559             content: parser.content,
560             flags: parser.flags,
561             at_root: parser.at_root,
562             at_root_has_selector: parser.at_root_has_selector,
563             extender: parser.extender,
564             content_scopes: parser.content_scopes,
565             options: parser.options,
566             modules: parser.modules,
567             module_config: parser.module_config,
568         }
569         .parse_selector(allows_parent, true, String::new())?
570         .0)
571     }
572 
selector_string(self, span: Span) -> SassResult<Option<String>>573     fn selector_string(self, span: Span) -> SassResult<Option<String>> {
574         Ok(Some(match self {
575             Value::String(text, ..) => text,
576             Value::List(list, sep, ..) if !list.is_empty() => {
577                 let mut result = Vec::new();
578                 match sep {
579                     ListSeparator::Comma => {
580                         for complex in list {
581                             if let Value::String(text, ..) = complex {
582                                 result.push(text);
583                             } else if let Value::List(_, ListSeparator::Space, ..) = complex {
584                                 result.push(match complex.selector_string(span)? {
585                                     Some(v) => v,
586                                     None => return Ok(None),
587                                 });
588                             } else {
589                                 return Ok(None);
590                             }
591                         }
592                     }
593                     ListSeparator::Space => {
594                         for compound in list {
595                             if let Value::String(text, ..) = compound {
596                                 result.push(text);
597                             } else {
598                                 return Ok(None);
599                             }
600                         }
601                     }
602                 }
603 
604                 result.join(sep.as_str())
605             }
606             _ => return Ok(None),
607         }))
608     }
609 
is_quoted_string(&self) -> bool610     pub fn is_quoted_string(&self) -> bool {
611         matches!(self, Value::String(_, QuoteKind::Quoted))
612     }
613 }
614