]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/theme.rs
Auto merge of #80746 - ehuss:update-cargo, r=ehuss
[rust.git] / src / librustdoc / theme.rs
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 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 {
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 {
62     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
72     fn is_comment(&self) -> bool {
73         matches!(self, Events::StartLineComment(_) | Events::StartComment(_) | Events::EndComment(_))
74     }
75 }
76
77 fn previous_is_line_comment(events: &[Events]) -> bool {
78     matches!(events.last(), Some(&Events::StartLineComment(_)))
79 }
80
81 fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool {
82     if let Some(&Events::StartComment(_)) = events.last() {
83         return false;
84     }
85     v[pos + 1] == b'/'
86 }
87
88 fn load_css_events(v: &[u8]) -> Vec<Events> {
89     let mut pos = 0;
90     let mut events = Vec::with_capacity(100);
91
92     while pos + 1 < v.len() {
93         match v[pos] {
94             b'/' if v[pos + 1] == b'*' => {
95                 events.push(Events::StartComment(pos));
96                 pos += 1;
97             }
98             b'/' if is_line_comment(pos, v, &events) => {
99                 events.push(Events::StartLineComment(pos));
100                 pos += 1;
101             }
102             b'\n' if previous_is_line_comment(&events) => {
103                 events.push(Events::EndComment(pos));
104             }
105             b'*' if v[pos + 1] == b'/' => {
106                 events.push(Events::EndComment(pos + 2));
107                 pos += 1;
108             }
109             b'{' if !previous_is_line_comment(&events) => {
110                 if let Some(&Events::StartComment(_)) = events.last() {
111                     pos += 1;
112                     continue;
113                 }
114                 events.push(Events::InBlock(pos + 1));
115             }
116             b'}' if !previous_is_line_comment(&events) => {
117                 if let Some(&Events::StartComment(_)) = events.last() {
118                     pos += 1;
119                     continue;
120                 }
121                 events.push(Events::OutBlock(pos + 1));
122             }
123             _ => {}
124         }
125         pos += 1;
126     }
127     events
128 }
129
130 fn get_useful_next(events: &[Events], pos: &mut usize) -> Option<Events> {
131     while *pos < events.len() {
132         if !events[*pos].is_comment() {
133             return Some(events[*pos]);
134         }
135         *pos += 1;
136     }
137     None
138 }
139
140 fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> {
141     let mut ret = Vec::with_capacity(3);
142
143     ret.push(events[pos].get_pos());
144     if pos > 0 {
145         pos -= 1;
146     }
147     loop {
148         if pos < 1 || !events[pos].is_comment() {
149             let x = events[pos].get_pos();
150             if *ret.last().unwrap() != x {
151                 ret.push(x);
152             } else {
153                 ret.push(0);
154             }
155             break;
156         }
157         ret.push(events[pos].get_pos());
158         pos -= 1;
159     }
160     if ret.len() & 1 != 0 && events[pos].is_comment() {
161         ret.push(0);
162     }
163     ret.iter().rev().cloned().collect()
164 }
165
166 fn build_rule(v: &[u8], positions: &[usize]) -> String {
167     minifier::css::minify(
168         &positions
169             .chunks(2)
170             .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or(""))
171             .collect::<String>()
172             .trim()
173             .replace("\n", " ")
174             .replace("/", "")
175             .replace("\t", " ")
176             .replace("{", "")
177             .replace("}", "")
178             .split(' ')
179             .filter(|s| !s.is_empty())
180             .collect::<Vec<&str>>()
181             .join(" "),
182     )
183     .unwrap_or_else(|_| String::new())
184 }
185
186 fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> {
187     let mut paths = Vec::with_capacity(50);
188
189     while *pos < events.len() {
190         if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
191             *pos += 1;
192             break;
193         }
194         if let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
195             paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos))));
196             *pos += 1;
197         }
198         while let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
199             if let Some(ref mut path) = paths.last_mut() {
200                 for entry in inner(v, events, pos).iter() {
201                     path.children.insert(entry.clone());
202                 }
203             }
204         }
205         if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
206             *pos += 1;
207         }
208     }
209     paths.iter().cloned().collect()
210 }
211
212 crate fn load_css_paths(v: &[u8]) -> CssPath {
213     let events = load_css_events(v);
214     let mut pos = 0;
215
216     let mut parent = CssPath::new("parent".to_owned());
217     parent.children = inner(v, &events, &mut pos);
218     parent
219 }
220
221 crate fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) {
222     if against.name == other.name {
223         for child in &against.children {
224             let mut found = false;
225             let mut found_working = false;
226             let mut tmp = Vec::new();
227
228             for other_child in &other.children {
229                 if child.name == other_child.name {
230                     if child != other_child {
231                         get_differences(child, other_child, &mut tmp);
232                     } else {
233                         found_working = true;
234                     }
235                     found = true;
236                     break;
237                 }
238             }
239             if !found {
240                 v.push(format!("  Missing \"{}\" rule", child.name));
241             } else if !found_working {
242                 v.extend(tmp.iter().cloned());
243             }
244         }
245     }
246 }
247
248 crate fn test_theme_against<P: AsRef<Path>>(
249     f: &P,
250     against: &CssPath,
251     diag: &Handler,
252 ) -> (bool, Vec<String>) {
253     let data = match fs::read(f) {
254         Ok(c) => c,
255         Err(e) => {
256             diag.struct_err(&e.to_string()).emit();
257             return (false, vec![]);
258         }
259     };
260
261     let paths = load_css_paths(&data);
262     let mut ret = vec![];
263     get_differences(against, &paths, &mut ret);
264     (true, ret)
265 }