1use crate::Vec;
2use allocator_api2::alloc::Allocator;
3use css_lexer::{AssociatedWhitespaceRules, Cursor};
4
5pub trait SemanticEq {
12 fn semantic_eq(&self, other: &Self) -> bool;
14}
15
16impl SemanticEq for Cursor {
18 fn semantic_eq(&self, other: &Self) -> bool {
19 self.token().with_associated_whitespace(AssociatedWhitespaceRules::none())
24 == other.token().with_associated_whitespace(AssociatedWhitespaceRules::none())
25 }
26}
27
28impl<T> SemanticEq for Option<T>
29where
30 T: SemanticEq,
31{
32 fn semantic_eq(&self, s: &Self) -> bool {
33 match (self, s) {
34 (Some(a), Some(b)) => a.semantic_eq(b),
35 (None, None) => true,
36 (_, _) => false,
37 }
38 }
39}
40
41impl<'a, T, A: Allocator> SemanticEq for Vec<'a, T, A>
42where
43 T: SemanticEq,
44{
45 fn semantic_eq(&self, s: &Self) -> bool {
46 if self.len() != s.len() {
47 return false;
48 }
49 for i in 0..self.len() {
50 if !self[i].semantic_eq(&s[i]) {
51 return false;
52 }
53 }
54 true
55 }
56}
57
58macro_rules! impl_tuple {
59 ($($T:ident [ $A:ident, $B:ident ]),+) => {
60 impl<$($T),*> SemanticEq for ($($T),*)
61 where
62 $($T: SemanticEq,)*
63 {
64 fn semantic_eq(&self, o: &Self) -> bool {
65 let ($($A),*) = self;
66 let ($($B),*) = o;
67 $($A.semantic_eq(&$B))&&*
68 }
69 }
70 };
71}
72
73impl_tuple!(A[sa,oa], B[sb,ob]);
74impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc]);
75impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od]);
76impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe]);
77impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of]);
78impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og]);
79impl_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]);
80impl_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]);
81impl_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]);
82impl_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]);
83impl_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]);
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::Arena;
89 use crate::{ComponentValues, Parse, Parser, ToCursors};
90 use css_lexer::EmptyAtomSet;
91
92 fn parse<'a, T: Parse<'a> + ToCursors>(alloc: &'a Arena, source: &'a str) -> T {
93 let lexer = css_lexer::Lexer::new(&EmptyAtomSet::ATOMS, source);
94 let mut parser = Parser::new(alloc, source, lexer);
95 let result = parser.parse_entirely::<T>();
96 result.output.unwrap()
97 }
98
99 #[test]
100 fn test_cursor_semantic_eq_ignores_offset() {
101 let token = css_lexer::Token::COMMA;
102 let cursor1 = Cursor::new(css_lexer::SourceOffset(0), token);
103 let cursor2 = Cursor::new(css_lexer::SourceOffset(100), token);
104
105 assert!(cursor1.semantic_eq(&cursor2));
107
108 assert_ne!(cursor1, cursor2);
110 }
111
112 #[test]
113 fn test_cursor_semantic_eq_ignores_associated_whitespace_for_all_delim_like_kinds() {
114 for token in [
118 css_lexer::Token::COLON,
119 css_lexer::Token::SEMICOLON,
120 css_lexer::Token::COMMA,
121 css_lexer::Token::LEFT_PAREN,
122 css_lexer::Token::RIGHT_PAREN,
123 css_lexer::Token::LEFT_CURLY,
124 css_lexer::Token::RIGHT_CURLY,
125 css_lexer::Token::LEFT_SQUARE,
126 css_lexer::Token::RIGHT_SQUARE,
127 ] {
128 let plain = Cursor::new(css_lexer::SourceOffset(0), token);
129 let with_whitespace_rule = Cursor::new(
130 css_lexer::SourceOffset(0),
131 token.with_associated_whitespace(css_lexer::AssociatedWhitespaceRules::EnforceBefore),
132 );
133 assert!(
134 plain.semantic_eq(&with_whitespace_rule),
135 "{:?} should be semantic_eq regardless of associated whitespace",
136 token.kind()
137 );
138 }
139 }
140
141 #[test]
142 fn test_component_values_ignores_whitespace() {
143 let source1 = "1px solid red";
144 let source2 = "1px solid red"; let alloc = Arena::new();
147 let values1 = parse::<ComponentValues>(&alloc, source1);
148 let values2 = parse::<ComponentValues>(&alloc, source2);
149
150 assert!(values1.semantic_eq(&values2));
152 }
153
154 #[test]
155 fn test_component_values_different_values() {
156 let source1 = "1px solid red";
157 let source2 = "2px solid red";
158
159 let alloc = Arena::new();
160 let values1 = parse::<ComponentValues>(&alloc, source1);
161 let values2 = parse::<ComponentValues>(&alloc, source2);
162
163 assert!(!values1.semantic_eq(&values2));
165 }
166}