1 use rustc_data_structures::fx::FxHashSet;
2 use std::fs;
3 use std::hash::{Hash, Hasher};
4 use std::path::Path;
5 
6 use rustc_errors::Handler;
7 
8 #[cfg(test)]
9 mod tests;
10 
11 #[derive(Debug, Clone, Eq)]
12 crate struct CssPath {
13     crate name: String,
14     crate children: FxHashSet<CssPath>,
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 is not required to have all of the second's rules.
21 impl PartialEq for CssPath {
eq(&self, other: &CssPath) -> bool22     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 {
hash<H: Hasher>(&self, state: &mut H)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 {
new(name: String) -> CssPath46     fn new(name: String) -> CssPath {
47         CssPath { name, children: FxHashSet::default() }
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 {
get_pos(&self) -> usize62     fn get_pos(&self) -> usize {
63         match *self {
64             Events::StartLineComment(p)
65             | Events::StartComment(p)
66             | Events::EndComment(p)
67             | Events::InBlock(p)
68             | Events::OutBlock(p) => p,
69         }
70     }
71 
is_comment(&self) -> bool72     fn is_comment(&self) -> bool {
73         matches!(
74             self,
75             Events::StartLineComment(_) | Events::StartComment(_) | Events::EndComment(_)
76         )
77     }
78 }
79 
previous_is_line_comment(events: &[Events]) -> bool80 fn previous_is_line_comment(events: &[Events]) -> bool {
81     matches!(events.last(), Some(&Events::StartLineComment(_)))
82 }
83 
is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool84 fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool {
85     if let Some(&Events::StartComment(_)) = events.last() {
86         return false;
87     }
88     v[pos + 1] == b'/'
89 }
90 
load_css_events(v: &[u8]) -> Vec<Events>91 fn load_css_events(v: &[u8]) -> Vec<Events> {
92     let mut pos = 0;
93     let mut events = Vec::with_capacity(100);
94 
95     while pos + 1 < v.len() {
96         match v[pos] {
97             b'/' if v[pos + 1] == b'*' => {
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             }
108             b'*' if v[pos + 1] == b'/' => {
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;
115                     continue;
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;
122                     continue;
123                 }
124                 events.push(Events::OutBlock(pos + 1));
125             }
126             _ => {}
127         }
128         pos += 1;
129     }
130     events
131 }
132 
get_useful_next(events: &[Events], pos: &mut usize) -> Option<Events>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 
get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize>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             }
158             break;
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 
build_rule(v: &[u8], positions: &[usize]) -> String169 fn build_rule(v: &[u8], positions: &[usize]) -> String {
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())
187 }
188 
inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath>189 fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> {
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;
195             break;
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 
load_css_paths(v: &[u8]) -> CssPath215 crate 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 
get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>)224 crate fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) {
225     if against.name == other.name {
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;
239                     break;
240                 }
241             }
242             if !found {
243                 v.push(format!("  Missing \"{}\" rule", child.name));
244             } else if !found_working {
245                 v.extend(tmp.iter().cloned());
246             }
247         }
248     }
249 }
250 
test_theme_against<P: AsRef<Path>>( f: &P, against: &CssPath, diag: &Handler, ) -> (bool, Vec<String>)251 crate fn test_theme_against<P: AsRef<Path>>(
252     f: &P,
253     against: &CssPath,
254     diag: &Handler,
255 ) -> (bool, Vec<String>) {
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     };
263 
264     let paths = load_css_paths(&data);
265     let mut ret = vec![];
266     get_differences(against, &paths, &mut ret);
267     (true, ret)
268 }
269