1 use rustc_data_structures::fx::FxHashSet;
3 use std::hash::{Hash, Hasher};
8 macro_rules! try_something {
9 ($e:expr, $diag:expr, $out:expr) => ({
13 $diag.struct_err(&e.to_string()).emit();
20 #[derive(Debug, Clone, Eq)]
23 pub children: FxHashSet<CssPath>,
26 // This PartialEq implementation IS NOT COMMUTATIVE!!!
28 // The order is very important: the second object must have all first's rules.
29 // However, the first doesn't require to have all second's rules.
30 impl PartialEq for CssPath {
31 fn eq(&self, other: &CssPath) -> bool {
32 if self.name != other.name {
35 for child in &self.children {
36 if !other.children.iter().any(|c| child == c) {
45 impl Hash for CssPath {
46 fn hash<H: Hasher>(&self, state: &mut H) {
47 self.name.hash(state);
48 for x in &self.children {
55 fn new(name: String) -> CssPath {
58 children: FxHashSet::default(),
63 /// All variants contain the position they occur.
64 #[derive(Debug, Clone, Copy)]
66 StartLineComment(usize),
74 fn get_pos(&self) -> usize {
76 Events::StartLineComment(p) |
77 Events::StartComment(p) |
78 Events::EndComment(p) |
80 Events::OutBlock(p) => p,
84 fn is_comment(&self) -> bool {
86 Events::StartLineComment(_) |
87 Events::StartComment(_) |
88 Events::EndComment(_) => true,
94 fn previous_is_line_comment(events: &[Events]) -> bool {
95 if let Some(&Events::StartLineComment(_)) = events.last() {
102 fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool {
103 if let Some(&Events::StartComment(_)) = events.last() {
106 pos + 1 < v.len() && v[pos + 1] == b'/'
109 fn load_css_events(v: &[u8]) -> Vec<Events> {
111 let mut events = Vec::with_capacity(100);
113 while pos + 1 < v.len() {
115 b'/' if pos + 1 < v.len() && v[pos + 1] == b'*' => {
116 events.push(Events::StartComment(pos));
119 b'/' if is_line_comment(pos, v, &events) => {
120 events.push(Events::StartLineComment(pos));
123 b'\n' if previous_is_line_comment(&events) => {
124 events.push(Events::EndComment(pos));
126 b'*' if pos + 1 < v.len() && v[pos + 1] == b'/' => {
127 events.push(Events::EndComment(pos + 2));
130 b'{' if !previous_is_line_comment(&events) => {
131 if let Some(&Events::StartComment(_)) = events.last() {
135 events.push(Events::InBlock(pos + 1));
137 b'}' if !previous_is_line_comment(&events) => {
138 if let Some(&Events::StartComment(_)) = events.last() {
142 events.push(Events::OutBlock(pos + 1));
151 fn get_useful_next(events: &[Events], pos: &mut usize) -> Option<Events> {
152 while *pos < events.len() {
153 if !events[*pos].is_comment() {
154 return Some(events[*pos]);
161 fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> {
162 let mut ret = Vec::with_capacity(3);
164 ret.push(events[pos].get_pos());
169 if pos < 1 || !events[pos].is_comment() {
170 let x = events[pos].get_pos();
171 if *ret.last().unwrap() != x {
178 ret.push(events[pos].get_pos());
181 if ret.len() & 1 != 0 && events[pos].is_comment() {
184 ret.iter().rev().cloned().collect()
187 fn build_rule(v: &[u8], positions: &[usize]) -> String {
189 .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or(""))
198 .filter(|s| s.len() > 0)
199 .collect::<Vec<&str>>()
203 fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> {
204 let mut paths = Vec::with_capacity(50);
206 while *pos < events.len() {
207 if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
211 if let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
212 paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos))));
215 while let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
216 if let Some(ref mut path) = paths.last_mut() {
217 for entry in inner(v, events, pos).iter() {
218 path.children.insert(entry.clone());
222 if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
226 paths.iter().cloned().collect()
229 pub fn load_css_paths(v: &[u8]) -> CssPath {
230 let events = load_css_events(v);
233 let mut parent = CssPath::new("parent".to_owned());
234 parent.children = inner(v, &events, &mut pos);
238 pub fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) {
239 if against.name != other.name {
242 for child in &against.children {
243 let mut found = false;
244 let mut found_working = false;
245 let mut tmp = Vec::new();
247 for other_child in &other.children {
248 if child.name == other_child.name {
249 if child != other_child {
250 get_differences(child, other_child, &mut tmp);
252 found_working = true;
259 v.push(format!(" Missing \"{}\" rule", child.name));
260 } else if found_working == false {
261 v.extend(tmp.iter().cloned());
267 pub fn test_theme_against<P: AsRef<Path>>(f: &P, against: &CssPath, diag: &Handler)
268 -> (bool, Vec<String>)
270 let data = try_something!(fs::read(f), diag, (false, vec![]));
271 let paths = load_css_paths(&data);
272 let mut ret = vec![];
273 get_differences(against, &paths, &mut ret);
282 fn test_comments_in_rules() {
291 // another line comment
298 rule g/* another multine
306 you like things like "{}" in there? :)
324 let mut ret = Vec::new();
325 get_differences(&load_css_paths(against.as_bytes()),
326 &load_css_paths(text.as_bytes()),
328 assert!(ret.is_empty());
340 let paths = load_css_paths(text.as_bytes());
341 assert!(paths.children.contains(&CssPath::new("a b c d".to_owned())));
345 fn test_comparison() {
360 let against = load_css_paths(y.as_bytes());
361 let other = load_css_paths(x.as_bytes());
363 let mut ret = Vec::new();
364 get_differences(&against, &other, &mut ret);
365 assert!(ret.is_empty());
366 get_differences(&other, &against, &mut ret);
367 assert_eq!(ret, vec![" Missing \"c\" rule".to_owned()]);