]>
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 XL |
11 | #[derive(Debug, Clone, Eq)] |
12 | pub struct CssPath { | |
13 | pub name: String, | |
b7449926 | 14 | pub 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. | |
20 | // However, the first doesn't require to have all second's rules. | |
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 { | |
73 | match *self { | |
dfeec247 | 74 | Events::StartLineComment(_) | Events::StartComment(_) | Events::EndComment(_) => true, |
2c00a5a8 XL |
75 | _ => false, |
76 | } | |
77 | } | |
78 | } | |
79 | ||
80 | fn previous_is_line_comment(events: &[Events]) -> bool { | |
dfeec247 | 81 | if let Some(&Events::StartLineComment(_)) = events.last() { true } else { false } |
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() | |
176 | .replace("\n", " ") | |
177 | .replace("/", "") | |
178 | .replace("\t", " ") | |
179 | .replace("{", "") | |
180 | .replace("}", "") | |
181 | .split(' ') | |
182 | .filter(|s| !s.is_empty()) | |
183 | .collect::<Vec<&str>>() | |
184 | .join(" "), | |
185 | ) | |
186 | .unwrap_or_else(|_| String::new()) | |
2c00a5a8 XL |
187 | } |
188 | ||
b7449926 | 189 | fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> { |
2c00a5a8 XL |
190 | let mut paths = Vec::with_capacity(50); |
191 | ||
192 | while *pos < events.len() { | |
193 | if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) { | |
194 | *pos += 1; | |
dfeec247 | 195 | break; |
2c00a5a8 XL |
196 | } |
197 | if let Some(Events::InBlock(_)) = get_useful_next(events, pos) { | |
198 | paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos)))); | |
199 | *pos += 1; | |
200 | } | |
201 | while let Some(Events::InBlock(_)) = get_useful_next(events, pos) { | |
202 | if let Some(ref mut path) = paths.last_mut() { | |
203 | for entry in inner(v, events, pos).iter() { | |
204 | path.children.insert(entry.clone()); | |
205 | } | |
206 | } | |
207 | } | |
208 | if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) { | |
209 | *pos += 1; | |
210 | } | |
211 | } | |
212 | paths.iter().cloned().collect() | |
213 | } | |
214 | ||
215 | pub fn load_css_paths(v: &[u8]) -> CssPath { | |
216 | let events = load_css_events(v); | |
217 | let mut pos = 0; | |
218 | ||
219 | let mut parent = CssPath::new("parent".to_owned()); | |
220 | parent.children = inner(v, &events, &mut pos); | |
221 | parent | |
222 | } | |
223 | ||
224 | pub fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) { | |
ba9703b0 | 225 | if against.name == other.name { |
2c00a5a8 XL |
226 | for child in &against.children { |
227 | let mut found = false; | |
228 | let mut found_working = false; | |
229 | let mut tmp = Vec::new(); | |
230 | ||
231 | for other_child in &other.children { | |
232 | if child.name == other_child.name { | |
233 | if child != other_child { | |
234 | get_differences(child, other_child, &mut tmp); | |
235 | } else { | |
236 | found_working = true; | |
237 | } | |
238 | found = true; | |
dfeec247 | 239 | break; |
2c00a5a8 XL |
240 | } |
241 | } | |
74b04a01 | 242 | if !found { |
2c00a5a8 | 243 | v.push(format!(" Missing \"{}\" rule", child.name)); |
74b04a01 | 244 | } else if !found_working { |
2c00a5a8 XL |
245 | v.extend(tmp.iter().cloned()); |
246 | } | |
247 | } | |
248 | } | |
249 | } | |
250 | ||
dc9dc135 XL |
251 | pub fn test_theme_against<P: AsRef<Path>>( |
252 | f: &P, | |
253 | against: &CssPath, | |
254 | diag: &Handler, | |
255 | ) -> (bool, Vec<String>) { | |
ba9703b0 XL |
256 | let data = match fs::read(f) { |
257 | Ok(c) => c, | |
258 | Err(e) => { | |
259 | diag.struct_err(&e.to_string()).emit(); | |
260 | return (false, vec![]); | |
261 | } | |
262 | }; | |
60c5eb7d | 263 | |
2c00a5a8 | 264 | let paths = load_css_paths(&data); |
0731742a | 265 | let mut ret = vec![]; |
2c00a5a8 XL |
266 | get_differences(against, &paths, &mut ret); |
267 | (true, ret) | |
268 | } |