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