]>
Commit | Line | Data |
---|---|---|
b7449926 | 1 | use rustc_data_structures::fx::FxHashSet; |
0731742a | 2 | use std::fs; |
2c00a5a8 | 3 | use std::hash::{Hash, Hasher}; |
2c00a5a8 XL |
4 | use std::path::Path; |
5 | ||
dfeec247 | 6 | use rustc_errors::Handler; |
94b46f34 | 7 | |
416331ca XL |
8 | #[cfg(test)] |
9 | mod tests; | |
10 | ||
2c00a5a8 | 11 | #[derive(Debug, Clone, Eq)] |
923072b8 FG |
12 | pub(crate) struct CssPath { |
13 | pub(crate) name: String, | |
14 | pub(crate) children: FxHashSet<CssPath>, | |
2c00a5a8 XL |
15 | } |
16 | ||
17 | // This PartialEq implementation IS NOT COMMUTATIVE!!! | |
18 | // | |
19 | // The order is very important: the second object must have all first's rules. | |
17df50a5 | 20 | // However, the first is not required to have all of the second's rules. |
2c00a5a8 XL |
21 | impl PartialEq for CssPath { |
22 | fn eq(&self, other: &CssPath) -> bool { | |
23 | if self.name != other.name { | |
24 | false | |
25 | } else { | |
26 | for child in &self.children { | |
27 | if !other.children.iter().any(|c| child == c) { | |
28 | return false; | |
29 | } | |
30 | } | |
31 | true | |
32 | } | |
33 | } | |
34 | } | |
35 | ||
36 | impl Hash for CssPath { | |
37 | fn hash<H: Hasher>(&self, state: &mut H) { | |
38 | self.name.hash(state); | |
39 | for x in &self.children { | |
40 | x.hash(state); | |
41 | } | |
42 | } | |
43 | } | |
44 | ||
45 | impl CssPath { | |
46 | fn new(name: String) -> CssPath { | |
dfeec247 | 47 | CssPath { name, children: FxHashSet::default() } |
2c00a5a8 XL |
48 | } |
49 | } | |
50 | ||
51 | /// All variants contain the position they occur. | |
52 | #[derive(Debug, Clone, Copy)] | |
53 | enum Events { | |
54 | StartLineComment(usize), | |
55 | StartComment(usize), | |
56 | EndComment(usize), | |
57 | InBlock(usize), | |
58 | OutBlock(usize), | |
59 | } | |
60 | ||
61 | impl Events { | |
62 | fn get_pos(&self) -> usize { | |
63 | match *self { | |
dfeec247 XL |
64 | Events::StartLineComment(p) |
65 | | Events::StartComment(p) | |
66 | | Events::EndComment(p) | |
67 | | Events::InBlock(p) | |
68 | | Events::OutBlock(p) => p, | |
2c00a5a8 XL |
69 | } |
70 | } | |
71 | ||
72 | fn is_comment(&self) -> bool { | |
5869c6ff XL |
73 | matches!( |
74 | self, | |
75 | Events::StartLineComment(_) | Events::StartComment(_) | Events::EndComment(_) | |
76 | ) | |
2c00a5a8 XL |
77 | } |
78 | } | |
79 | ||
80 | fn previous_is_line_comment(events: &[Events]) -> bool { | |
5869c6ff | 81 | matches!(events.last(), Some(&Events::StartLineComment(_))) |
2c00a5a8 XL |
82 | } |
83 | ||
84 | fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool { | |
85 | if let Some(&Events::StartComment(_)) = events.last() { | |
86 | return false; | |
87 | } | |
dc9dc135 | 88 | v[pos + 1] == b'/' |
2c00a5a8 XL |
89 | } |
90 | ||
91 | fn load_css_events(v: &[u8]) -> Vec<Events> { | |
92 | let mut pos = 0; | |
93 | let mut events = Vec::with_capacity(100); | |
94 | ||
dc9dc135 | 95 | while pos + 1 < v.len() { |
2c00a5a8 | 96 | match v[pos] { |
dc9dc135 | 97 | b'/' if v[pos + 1] == b'*' => { |
2c00a5a8 XL |
98 | events.push(Events::StartComment(pos)); |
99 | pos += 1; | |
100 | } | |
101 | b'/' if is_line_comment(pos, v, &events) => { | |
102 | events.push(Events::StartLineComment(pos)); | |
103 | pos += 1; | |
104 | } | |
105 | b'\n' if previous_is_line_comment(&events) => { | |
106 | events.push(Events::EndComment(pos)); | |
107 | } | |
dc9dc135 | 108 | b'*' if v[pos + 1] == b'/' => { |
2c00a5a8 XL |
109 | events.push(Events::EndComment(pos + 2)); |
110 | pos += 1; | |
111 | } | |
112 | b'{' if !previous_is_line_comment(&events) => { | |
113 | if let Some(&Events::StartComment(_)) = events.last() { | |
114 | pos += 1; | |
dfeec247 | 115 | continue; |
2c00a5a8 XL |
116 | } |
117 | events.push(Events::InBlock(pos + 1)); | |
118 | } | |
119 | b'}' if !previous_is_line_comment(&events) => { | |
120 | if let Some(&Events::StartComment(_)) = events.last() { | |
121 | pos += 1; | |
dfeec247 | 122 | continue; |
2c00a5a8 XL |
123 | } |
124 | events.push(Events::OutBlock(pos + 1)); | |
125 | } | |
126 | _ => {} | |
127 | } | |
128 | pos += 1; | |
129 | } | |
130 | events | |
131 | } | |
132 | ||
133 | fn get_useful_next(events: &[Events], pos: &mut usize) -> Option<Events> { | |
134 | while *pos < events.len() { | |
135 | if !events[*pos].is_comment() { | |
136 | return Some(events[*pos]); | |
137 | } | |
138 | *pos += 1; | |
139 | } | |
140 | None | |
141 | } | |
142 | ||
143 | fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> { | |
144 | let mut ret = Vec::with_capacity(3); | |
145 | ||
146 | ret.push(events[pos].get_pos()); | |
147 | if pos > 0 { | |
148 | pos -= 1; | |
149 | } | |
150 | loop { | |
151 | if pos < 1 || !events[pos].is_comment() { | |
152 | let x = events[pos].get_pos(); | |
153 | if *ret.last().unwrap() != x { | |
154 | ret.push(x); | |
155 | } else { | |
156 | ret.push(0); | |
157 | } | |
dfeec247 | 158 | break; |
2c00a5a8 XL |
159 | } |
160 | ret.push(events[pos].get_pos()); | |
161 | pos -= 1; | |
162 | } | |
163 | if ret.len() & 1 != 0 && events[pos].is_comment() { | |
164 | ret.push(0); | |
165 | } | |
166 | ret.iter().rev().cloned().collect() | |
167 | } | |
168 | ||
169 | fn build_rule(v: &[u8], positions: &[usize]) -> String { | |
74b04a01 XL |
170 | minifier::css::minify( |
171 | &positions | |
172 | .chunks(2) | |
173 | .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or("")) | |
174 | .collect::<String>() | |
175 | .trim() | |
04454e1e FG |
176 | .chars() |
177 | .filter_map(|c| match c { | |
178 | '\n' | '\t' => Some(' '), | |
179 | '/' | '{' | '}' => None, | |
180 | c => Some(c), | |
181 | }) | |
182 | .collect::<String>() | |
74b04a01 XL |
183 | .split(' ') |
184 | .filter(|s| !s.is_empty()) | |
04454e1e FG |
185 | .intersperse(" ") |
186 | .collect::<String>(), | |
74b04a01 | 187 | ) |
923072b8 | 188 | .map(|css| css.to_string()) |
74b04a01 | 189 | .unwrap_or_else(|_| String::new()) |
2c00a5a8 XL |
190 | } |
191 | ||
b7449926 | 192 | fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> { |
2c00a5a8 XL |
193 | let mut paths = Vec::with_capacity(50); |
194 | ||
195 | while *pos < events.len() { | |
196 | if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) { | |
197 | *pos += 1; | |
dfeec247 | 198 | break; |
2c00a5a8 XL |
199 | } |
200 | if let Some(Events::InBlock(_)) = get_useful_next(events, pos) { | |
201 | paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos)))); | |
202 | *pos += 1; | |
203 | } | |
204 | while let Some(Events::InBlock(_)) = get_useful_next(events, pos) { | |
205 | if let Some(ref mut path) = paths.last_mut() { | |
206 | for entry in inner(v, events, pos).iter() { | |
207 | path.children.insert(entry.clone()); | |
208 | } | |
209 | } | |
210 | } | |
211 | if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) { | |
212 | *pos += 1; | |
213 | } | |
214 | } | |
215 | paths.iter().cloned().collect() | |
216 | } | |
217 | ||
923072b8 | 218 | pub(crate) fn load_css_paths(v: &[u8]) -> CssPath { |
2c00a5a8 XL |
219 | let events = load_css_events(v); |
220 | let mut pos = 0; | |
221 | ||
222 | let mut parent = CssPath::new("parent".to_owned()); | |
223 | parent.children = inner(v, &events, &mut pos); | |
224 | parent | |
225 | } | |
226 | ||
923072b8 | 227 | pub(crate) fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) { |
ba9703b0 | 228 | if against.name == other.name { |
2c00a5a8 XL |
229 | for child in &against.children { |
230 | let mut found = false; | |
231 | let mut found_working = false; | |
232 | let mut tmp = Vec::new(); | |
233 | ||
234 | for other_child in &other.children { | |
235 | if child.name == other_child.name { | |
236 | if child != other_child { | |
237 | get_differences(child, other_child, &mut tmp); | |
238 | } else { | |
239 | found_working = true; | |
240 | } | |
241 | found = true; | |
dfeec247 | 242 | break; |
2c00a5a8 XL |
243 | } |
244 | } | |
74b04a01 | 245 | if !found { |
2c00a5a8 | 246 | v.push(format!(" Missing \"{}\" rule", child.name)); |
74b04a01 | 247 | } else if !found_working { |
2c00a5a8 XL |
248 | v.extend(tmp.iter().cloned()); |
249 | } | |
250 | } | |
251 | } | |
252 | } | |
253 | ||
923072b8 | 254 | pub(crate) fn test_theme_against<P: AsRef<Path>>( |
dc9dc135 XL |
255 | f: &P, |
256 | against: &CssPath, | |
257 | diag: &Handler, | |
258 | ) -> (bool, Vec<String>) { | |
ba9703b0 XL |
259 | let data = match fs::read(f) { |
260 | Ok(c) => c, | |
261 | Err(e) => { | |
262 | diag.struct_err(&e.to_string()).emit(); | |
263 | return (false, vec![]); | |
264 | } | |
265 | }; | |
60c5eb7d | 266 | |
2c00a5a8 | 267 | let paths = load_css_paths(&data); |
0731742a | 268 | let mut ret = vec![]; |
2c00a5a8 XL |
269 | get_differences(against, &paths, &mut ret); |
270 | (true, ret) | |
271 | } |