1use super::{token_as_ident, ASTNode, CSSToken, ParseError, Span, ToStringSettings};
2use source_map::ToString;
3use tokenizer_lib::{Token, TokenReader};
4
5#[derive(Debug, PartialEq, Eq, Clone)]
7pub struct Selector {
8 tag_name: Option<String>,
10 identifier: Option<String>,
12 class_names: Option<Vec<String>>,
14 descendant: Option<Box<Selector>>,
16 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 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 pub fn nest_selector(&self, other: Self) -> Self {
171 let mut new_selector = self.clone();
172 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}