css_parser/
selectors.rs

1use super::{token_as_ident, ASTNode, CSSToken, ParseError, Span, ToStringSettings};
2use source_map::ToString;
3use tokenizer_lib::{Token, TokenReader};
4
5/// [A css selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors)
6#[derive(Debug, PartialEq, Eq, Clone)]
7pub struct Selector {
8    /// Can be '*' for universal
9    tag_name: Option<String>,
10    /// #...
11    identifier: Option<String>,
12    /// .x.y.z
13    class_names: Option<Vec<String>>,
14    /// div h1
15    descendant: Option<Box<Selector>>,
16    /// div > h1
17    child: Option<Box<Selector>>,
18    position: Option<Span>,
19}
20
21impl ASTNode for Selector {
22    fn from_reader(reader: &mut impl TokenReader<CSSToken, Span>) -> Result<Self, ParseError> {
23        let mut selector: Self = Self {
24            tag_name: None,
25            identifier: None,
26            class_names: None,
27            descendant: None,
28            child: None,
29            position: None,
30        };
31        for i in 0.. {
32            // Handling "descendant" parsing by checking gap/space in tokens
33            let Token(peek_token, peek_span) = reader.peek().unwrap();
34
35            if matches!(peek_token, CSSToken::Comma) {
36                return Ok(selector);
37            }
38
39            if i != 0
40                && !matches!(peek_token, CSSToken::CloseAngle)
41                && !selector
42                    .position
43                    .as_ref()
44                    .unwrap()
45                    .is_adjacent_to(peek_span)
46            {
47                let descendant = Self::from_reader(reader)?;
48                selector.descendant = Some(Box::new(descendant));
49                break;
50            }
51            match reader.next().unwrap() {
52                Token(CSSToken::Ident(name), pos) => {
53                    if let Some(_) = selector.tag_name.replace(name) {
54                        return Err(ParseError {
55                            reason: "Tag name specified twice".to_owned(),
56                            position: pos,
57                        });
58                    }
59                    selector.position = Some(pos);
60                }
61                Token(CSSToken::Asterisk, pos) => {
62                    if let Some(_) = selector.tag_name.replace("*".to_owned()) {
63                        return Err(ParseError {
64                            reason: "Tag name specified twice".to_owned(),
65                            position: pos,
66                        });
67                    }
68                    selector.position = Some(pos);
69                }
70                Token(CSSToken::Dot, start_span) => {
71                    let (class_name, end_span) = token_as_ident(reader.next().unwrap())?;
72                    selector
73                        .class_names
74                        .get_or_insert_with(|| Vec::new())
75                        .push(class_name);
76                    if let Some(ref mut selector_position) = selector.position {
77                        *selector_position = selector_position.union(&end_span);
78                    } else {
79                        selector.position = Some(start_span.union(&end_span));
80                    }
81                }
82                Token(CSSToken::HashPrefixedValue(identifier), position) => {
83                    if selector.identifier.replace(identifier).is_some() {
84                        return Err(ParseError {
85                            reason: "Cannot specify to id selectors".to_owned(),
86                            position,
87                        });
88                    }
89                    if let Some(ref mut selector_position) = selector.position {
90                        *selector_position = selector_position.union(&position);
91                    } else {
92                        selector.position = Some(position);
93                    }
94                }
95                Token(CSSToken::CloseAngle, position) => {
96                    let child = Self::from_reader(reader)?;
97                    if let Some(ref mut selector_position) = selector.position {
98                        *selector_position = selector_position.union(&position);
99                    } else {
100                        return Err(ParseError {
101                            reason: "Expected selector start, found '>'".to_owned(),
102                            position,
103                        });
104                    }
105                    selector.child = Some(Box::new(child));
106                    break;
107                }
108                Token(token, position) => {
109                    return Err(ParseError {
110                        reason: format!("Expected valid selector found '{:?}'", token),
111                        position,
112                    });
113                }
114            }
115            if matches!(
116                reader.peek(),
117                Some(Token(CSSToken::OpenCurly, _)) | Some(Token(CSSToken::EOS, _))
118            ) {
119                break;
120            }
121        }
122        Ok(selector)
123    }
124
125    fn to_string_from_buffer(
126        &self,
127        buf: &mut impl ToString,
128        settings: &ToStringSettings,
129        depth: u8,
130    ) {
131        if let Some(ref pos) = self.position {
132            buf.add_mapping(pos);
133        }
134
135        if let Some(name) = &self.tag_name {
136            buf.push_str(name);
137        }
138        if let Some(id) = &self.identifier {
139            buf.push('#');
140            buf.push_str(id);
141        }
142        if let Some(class_names) = &self.class_names {
143            for class_name in class_names.iter() {
144                buf.push('.');
145                buf.push_str(class_name);
146            }
147        }
148        if let Some(descendant) = &self.descendant {
149            buf.push(' ');
150            descendant.to_string_from_buffer(buf, settings, depth);
151        } else if let Some(child) = &self.child {
152            if !settings.minify {
153                buf.push(' ');
154            }
155            buf.push('>');
156            if !settings.minify {
157                buf.push(' ');
158            }
159            child.to_string_from_buffer(buf, settings, depth);
160        }
161    }
162
163    fn get_position(&self) -> Option<&Span> {
164        self.position.as_ref()
165    }
166}
167
168impl Selector {
169    /// Returns other nested under self
170    pub fn nest_selector(&self, other: Self) -> Self {
171        let mut new_selector = self.clone();
172        // Walk down the new selector descendant and child branches until at end. Then set descendant value
173        // on the tail. Uses raw pointers & unsafe due to issues with Rust borrow checker
174        let mut tail: *mut Selector = &mut new_selector;
175        loop {
176            let cur = unsafe { &mut *tail };
177            if let Some(child) = cur.descendant.as_mut().or(cur.child.as_mut()) {
178                tail = &mut **child;
179            } else {
180                cur.descendant = Some(Box::new(other));
181                break;
182            }
183        }
184        new_selector
185    }
186}
187
188#[cfg(test)]
189mod selector_tests {
190    use source_map::SourceId;
191
192    use super::*;
193
194    const NULL_SOURCE_ID: SourceId = SourceId::null();
195
196    #[test]
197    fn tag_name() {
198        let selector = Selector::from_string("h1".to_owned(), NULL_SOURCE_ID, None).unwrap();
199        assert_eq!(
200            selector.tag_name,
201            Some("h1".to_owned()),
202            "Bad selector {:?}",
203            selector
204        );
205    }
206
207    #[test]
208    fn id() {
209        let selector = Selector::from_string("#button1".to_owned(), NULL_SOURCE_ID, None).unwrap();
210        assert_eq!(
211            selector.identifier,
212            Some("button1".to_owned()),
213            "Bad selector {:?}",
214            selector
215        );
216    }
217
218    #[test]
219    fn class_name() {
220        let selector1 =
221            Selector::from_string(".container".to_owned(), NULL_SOURCE_ID, None).unwrap();
222        assert_eq!(
223            selector1.class_names.as_ref().unwrap()[0],
224            "container".to_owned(),
225            "Bad selector {:?}",
226            selector1
227        );
228        let selector2 =
229            Selector::from_string(".container.center-x".to_owned(), NULL_SOURCE_ID, None).unwrap();
230        assert_eq!(
231            selector2.class_names.as_ref().unwrap().len(),
232            2,
233            "Bad selector {:?}",
234            selector2
235        );
236    }
237
238    #[test]
239    fn descendant() {
240        let selector =
241            Selector::from_string("div .button".to_owned(), NULL_SOURCE_ID, None).unwrap();
242        assert_eq!(
243            selector.tag_name,
244            Some("div".to_owned()),
245            "Bad selector {:?}",
246            selector
247        );
248        let descendant_selector = *selector.descendant.unwrap();
249        assert_eq!(
250            descendant_selector.class_names.as_ref().unwrap()[0],
251            "button".to_owned(),
252            "Bad selector {:?}",
253            descendant_selector
254        );
255    }
256
257    #[test]
258    fn child() {
259        let selector = Selector::from_string("div > h1".to_owned(), NULL_SOURCE_ID, None).unwrap();
260        assert_eq!(
261            selector.tag_name,
262            Some("div".to_owned()),
263            "Bad selector {:?}",
264            selector
265        );
266        let child_selector = *selector.child.unwrap();
267        assert_eq!(
268            child_selector.tag_name,
269            Some("h1".to_owned()),
270            "Bad selector {:?}",
271            child_selector
272        );
273    }
274}