1 use rustc_data_structures::fx::FxHashSet;
3 use std::hash::{Hash, Hasher};
6 use rustc_errors::Handler;
11 #[derive(Debug, Clone, Eq)]
12 crate struct CssPath {
14 crate children: FxHashSet<CssPath>,
17 // This PartialEq implementation IS NOT COMMUTATIVE!!!
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 {
26 for child in &self.children {
27 if !other.children.iter().any(|c| child == c) {
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 {
46 fn new(name: String) -> CssPath {
47 CssPath { name, children: FxHashSet::default() }
51 /// All variants contain the position they occur.
52 #[derive(Debug, Clone, Copy)]
54 StartLineComment(usize),
62 fn get_pos(&self) -> usize {
64 Events::StartLineComment(p)
65 | Events::StartComment(p)
66 | Events::EndComment(p)
68 | Events::OutBlock(p) => p,
72 fn is_comment(&self) -> bool {
73 matches!(self, Events::StartLineComment(_) | Events::StartComment(_) | Events::EndComment(_))
77 fn previous_is_line_comment(events: &[Events]) -> bool {
78 matches!(events.last(), Some(&Events::StartLineComment(_)))
81 fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool {
82 if let Some(&Events::StartComment(_)) = events.last() {
88 fn load_css_events(v: &[u8]) -> Vec<Events> {
90 let mut events = Vec::with_capacity(100);
92 while pos + 1 < v.len() {
94 b'/' if v[pos + 1] == b'*' => {
95 events.push(Events::StartComment(pos));
98 b'/' if is_line_comment(pos, v, &events) => {
99 events.push(Events::StartLineComment(pos));
102 b'\n' if previous_is_line_comment(&events) => {
103 events.push(Events::EndComment(pos));
105 b'*' if v[pos + 1] == b'/' => {
106 events.push(Events::EndComment(pos + 2));
109 b'{' if !previous_is_line_comment(&events) => {
110 if let Some(&Events::StartComment(_)) = events.last() {
114 events.push(Events::InBlock(pos + 1));
116 b'}' if !previous_is_line_comment(&events) => {
117 if let Some(&Events::StartComment(_)) = events.last() {
121 events.push(Events::OutBlock(pos + 1));
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]);
140 fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> {
141 let mut ret = Vec::with_capacity(3);
143 ret.push(events[pos].get_pos());
148 if pos < 1 || !events[pos].is_comment() {
149 let x = events[pos].get_pos();
150 if *ret.last().unwrap() != x {
157 ret.push(events[pos].get_pos());
160 if ret.len() & 1 != 0 && events[pos].is_comment() {
163 ret.iter().rev().cloned().collect()
166 fn build_rule(v: &[u8], positions: &[usize]) -> String {
167 minifier::css::minify(
170 .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or(""))
179 .filter(|s| !s.is_empty())
180 .collect::<Vec<&str>>()
183 .unwrap_or_else(|_| String::new())
186 fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> {
187 let mut paths = Vec::with_capacity(50);
189 while *pos < events.len() {
190 if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
194 if let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
195 paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos))));
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());
205 if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
209 paths.iter().cloned().collect()
212 crate fn load_css_paths(v: &[u8]) -> CssPath {
213 let events = load_css_events(v);
216 let mut parent = CssPath::new("parent".to_owned());
217 parent.children = inner(v, &events, &mut pos);
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();
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);
233 found_working = true;
240 v.push(format!(" Missing \"{}\" rule", child.name));
241 } else if !found_working {
242 v.extend(tmp.iter().cloned());
248 crate fn test_theme_against<P: AsRef<Path>>(
252 ) -> (bool, Vec<String>) {
253 let data = match fs::read(f) {
256 diag.struct_err(&e.to_string()).emit();
257 return (false, vec![]);
261 let paths = load_css_paths(&data);
262 let mut ret = vec![];
263 get_differences(against, &paths, &mut ret);