1use super::prelude::*;
2use css_lexer::{AssociatedWhitespaceRules, Kind, KindSet, SourceCursor};
3
4pub trait SemanticEq {
14 fn semantic_eq(&self, other: &Self, source_text: &str) -> bool;
16}
17
18impl SemanticEq for Cursor {
19 fn semantic_eq(&self, other: &Self, source_text: &str) -> bool {
20 let kind = self.token().kind();
21 if kind != other.token().kind() {
22 return false;
23 }
24 if KindSet::NAMED.contains(kind) {
25 let this = SourceCursor::from(*self, self.str_slice(source_text));
27 let other = SourceCursor::from(*other, other.str_slice(source_text));
28 return this.semantic_eq(&other, source_text);
29 }
30 self.token().with_associated_whitespace(AssociatedWhitespaceRules::NONE)
31 == other.token().with_associated_whitespace(AssociatedWhitespaceRules::NONE)
32 }
33}
34
35impl SemanticEq for SourceCursor<'_> {
36 fn semantic_eq(&self, other: &Self, _source_text: &str) -> bool {
37 let token = self.token();
38 let other_token = other.token();
39 let kind = token.kind();
40 if kind != other_token.kind() {
41 return false;
42 }
43 match kind {
44 Kind::Ident | Kind::Function | Kind::AtKeyword => {
45 token.is_dashed_ident() == other_token.is_dashed_ident()
46 && match (token.atom_bits(), other_token.atom_bits()) {
47 (0, 0) => self.eq_parsed_ignore_ascii_case(other),
48 (a, b) => a == b,
49 }
50 }
51 Kind::Dimension => {
52 token.value() == other_token.value()
53 && match (token.atom_bits(), other_token.atom_bits()) {
54 (0, 0) => self.eq_parsed_ignore_ascii_case(other),
55 (a, b) => {
59 a == b && token.len() - token.leading_len() == other_token.len() - other_token.leading_len()
60 }
61 }
62 }
63 Kind::String | Kind::Url | Kind::Hash => self.eq_parsed(other),
64 Kind::UnicodeRange => {
65 token.unicode_range_start() == other_token.unicode_range_start()
66 && token.unicode_range_end() == other_token.unicode_range_end()
67 }
68 _ if KindSet::NAMED.contains(kind) => self.source() == other.source(),
71 _ => {
72 self.token().with_associated_whitespace(AssociatedWhitespaceRules::NONE)
73 == other.token().with_associated_whitespace(AssociatedWhitespaceRules::NONE)
74 }
75 }
76 }
77}
78
79impl<T> SemanticEq for Option<T>
80where
81 T: SemanticEq,
82{
83 fn semantic_eq(&self, s: &Self, source_text: &str) -> bool {
84 match (self, s) {
85 (Some(a), Some(b)) => a.semantic_eq(b, source_text),
86 (None, None) => true,
87 (_, _) => false,
88 }
89 }
90}
91
92impl<'a, T, A: Allocator> SemanticEq for Vec<'a, T, A>
93where
94 T: SemanticEq,
95{
96 fn semantic_eq(&self, s: &Self, source_text: &str) -> bool {
97 if self.len() != s.len() {
98 return false;
99 }
100 for i in 0..self.len() {
101 if !self[i].semantic_eq(&s[i], source_text) {
102 return false;
103 }
104 }
105 true
106 }
107}
108
109macro_rules! impl_tuple {
110 ($($T:ident [ $A:ident, $B:ident ]),+) => {
111 impl<$($T),*> SemanticEq for ($($T),*)
112 where
113 $($T: SemanticEq,)*
114 {
115 fn semantic_eq(&self, o: &Self, source_text: &str) -> bool {
116 let ($($A),*) = self;
117 let ($($B),*) = o;
118 $($A.semantic_eq(&$B, source_text))&&*
119 }
120 }
121 };
122}
123
124impl_tuple!(A[sa,oa], B[sb,ob]);
125impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc]);
126impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od]);
127impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe]);
128impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of]);
129impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og]);
130impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh]);
131impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh], I[si,oi]);
132impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh], I[si,oi], J[sj,oj]);
133impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh], I[si,oi], J[sj,oj], K[sk,ok]);
134impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh], I[si,oi], J[sj,oj], K[sk,ok], L[sl,ol]);
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::Arena;
140 use crate::{ComponentValues, Parse, Parser, SimpleBlock, T, ToCursors, assert_semantic_eq, assert_semantic_ne};
141 use css_lexer::EmptyAtomSet;
142
143 fn parse<'a, T: Parse<'a> + ToCursors>(alloc: &'a Arena, source: &'a str) -> T {
144 let lexer = css_lexer::Lexer::new(&EmptyAtomSet::ATOMS, source);
145 let mut parser = Parser::new(alloc, source, lexer);
146 let result = parser.parse_entirely::<T>();
147 result.output.unwrap()
148 }
149
150 #[test]
151 fn test_cursor_semantic_eq_ignores_offset() {
152 let token = css_lexer::Token::COMMA;
153 let cursor1 = Cursor::new(css_lexer::SourceOffset(0), token);
154 let cursor2 = Cursor::new(css_lexer::SourceOffset(100), token);
155
156 assert!(cursor1.semantic_eq(&cursor2, ""));
158
159 assert_ne!(cursor1, cursor2);
161 }
162
163 #[test]
164 fn test_cursor_semantic_eq_ignores_associated_whitespace_for_all_delim_like_kinds() {
165 for token in [
169 css_lexer::Token::COLON,
170 css_lexer::Token::SEMICOLON,
171 css_lexer::Token::COMMA,
172 css_lexer::Token::LEFT_PAREN,
173 css_lexer::Token::RIGHT_PAREN,
174 css_lexer::Token::LEFT_CURLY,
175 css_lexer::Token::RIGHT_CURLY,
176 css_lexer::Token::LEFT_SQUARE,
177 css_lexer::Token::RIGHT_SQUARE,
178 ] {
179 let plain = Cursor::new(css_lexer::SourceOffset(0), token);
180 let with_whitespace_rule = Cursor::new(
181 css_lexer::SourceOffset(0),
182 token.with_associated_whitespace(css_lexer::AssociatedWhitespaceRules::EnforceBefore),
183 );
184 assert!(
185 plain.semantic_eq(&with_whitespace_rule, ""),
186 "{:?} should be semantic_eq regardless of associated whitespace",
187 token.kind()
188 );
189 }
190 }
191
192 fn pair<'a>(alloc: &'a Arena, source: &'a str) -> (ComponentValues<'a>, ComponentValues<'a>) {
193 let (first, second) = parse::<(SimpleBlock, SimpleBlock)>(alloc, source);
194 (first.values, second.values)
195 }
196
197 #[test]
198 fn test_component_values_ignores_whitespace() {
199 let alloc = Arena::new();
200 let source = "(1px solid red)(1px solid red)";
201 let (first, second) = pair(&alloc, source);
202 assert!(first.semantic_eq(&second, source));
203 }
204
205 #[test]
206 fn test_component_values_different_values() {
207 let alloc = Arena::new();
208 let source = "(1px solid red)(2px solid red)";
209 let (first, second) = pair(&alloc, source);
210 assert!(!first.semantic_eq(&second, source));
211 }
212
213 #[test]
214 fn test_idents_of_equal_length_are_told_apart() {
215 let alloc = Arena::new();
216 let source = "(foo)(bar)";
217 let (first, second) = pair(&alloc, source);
218 assert!(!first.semantic_eq(&second, source));
219
220 let source = "(foo)(foo)";
221 let (first, second) = pair(&alloc, source);
222 assert!(first.semantic_eq(&second, source));
223 }
224
225 #[test]
226 fn test_strings_of_equal_length_are_told_apart() {
227 let alloc = Arena::new();
228 let source = "(\"i\")(\"j\")";
229 let (first, second) = pair(&alloc, source);
230 assert!(!first.semantic_eq(&second, source));
231 }
232
233 #[test]
234 fn test_ident_spellings_are_equal() {
235 let alloc = Arena::new();
236 let source = "(Foo)(foo)";
237 let (first, second) = pair(&alloc, source);
238 assert!(first.semantic_eq(&second, source));
239
240 let source = "(b\\61r)(bar)";
241 let (first, second) = pair(&alloc, source);
242 assert!(first.semantic_eq(&second, source));
243 }
244
245 #[test]
246 fn test_string_spellings() {
247 let alloc = Arena::new();
248 let source = "(\"A\")(\"a\")";
249 let (first, second) = pair(&alloc, source);
250 assert!(!first.semantic_eq(&second, source));
251
252 let source = "(\"a\")('a')";
253 let (first, second) = pair(&alloc, source);
254 assert!(first.semantic_eq(&second, source));
255
256 let source = "(\"\\61\")(\"a\")";
257 let (first, second) = pair(&alloc, source);
258 assert!(first.semantic_eq(&second, source));
259 }
260
261 #[test]
262 fn test_hashes_are_case_sensitive() {
263 let alloc = Arena::new();
264 let source = "(#Foo)(#foo)";
265 let (first, second) = pair(&alloc, source);
266 assert!(!first.semantic_eq(&second, source));
267 }
268
269 #[test]
270 fn test_dimension_spellings_are_equal() {
271 let alloc = Arena::new();
272 let source = "(1.0Px)(1px)";
273 let (first, second) = pair(&alloc, source);
274 assert!(first.semantic_eq(&second, source));
275
276 let source = "(1px)(2px)";
277 let (first, second) = pair(&alloc, source);
278 assert!(!first.semantic_eq(&second, source));
279 }
280
281 #[derive(Debug, Default, derive_atom_set::AtomSet, Copy, Clone, PartialEq)]
284 pub enum TestAtomSet {
285 #[default]
286 _None,
287 Foo,
288 Bar,
289 Px,
290 }
291
292 impl TestAtomSet {
293 const ATOMS: TestAtomSet = TestAtomSet::_None;
294 }
295
296 #[test]
297 fn test_ident_atoms() {
298 assert_semantic_eq!(TestAtomSet::ATOMS, T![Ident], "Foo", "foo");
299 assert_semantic_ne!(TestAtomSet::ATOMS, T![Ident], "foo", "bar");
300 assert_semantic_ne!(TestAtomSet::ATOMS, T![Ident], "--foo", "foo");
302 }
303
304 #[test]
305 fn test_dimension_unit_atoms() {
306 assert_semantic_eq!(TestAtomSet::ATOMS, T![Dimension], "1.0Px", "1px");
307 assert_semantic_ne!(TestAtomSet::ATOMS, T![Dimension], "1px", "2px");
308 assert_semantic_ne!(TestAtomSet::ATOMS, T![Dimension], "1--px", "1px");
310 }
311}