1 use rustc_data_structures
::fx
::FxHashSet
;
3 use std
::hash
::{Hash, Hasher}
;
6 use rustc_errors
::Handler
;
11 #[derive(Debug, Clone, Eq)]
12 pub(crate) struct CssPath
{
13 pub(crate) name
: String
,
14 pub(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 is not required to have all of the 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
{
75 Events
::StartLineComment(_
) | Events
::StartComment(_
) | Events
::EndComment(_
)
80 fn previous_is_line_comment(events
: &[Events
]) -> bool
{
81 matches
!(events
.last(), Some(&Events
::StartLineComment(_
)))
84 fn is_line_comment(pos
: usize, v
: &[u8], events
: &[Events
]) -> bool
{
85 if let Some(&Events
::StartComment(_
)) = events
.last() {
91 fn load_css_events(v
: &[u8]) -> Vec
<Events
> {
93 let mut events
= Vec
::with_capacity(100);
95 while pos
+ 1 < v
.len() {
97 b'
/'
if v
[pos
+ 1] == b'
*'
=> {
98 events
.push(Events
::StartComment(pos
));
101 b'
/'
if is_line_comment(pos
, v
, &events
) => {
102 events
.push(Events
::StartLineComment(pos
));
105 b'
\n'
if previous_is_line_comment(&events
) => {
106 events
.push(Events
::EndComment(pos
));
108 b'
*'
if v
[pos
+ 1] == b'
/'
=> {
109 events
.push(Events
::EndComment(pos
+ 2));
112 b'
{'
if !previous_is_line_comment(&events
) => {
113 if let Some(&Events
::StartComment(_
)) = events
.last() {
117 events
.push(Events
::InBlock(pos
+ 1));
119 b'
}'
if !previous_is_line_comment(&events
) => {
120 if let Some(&Events
::StartComment(_
)) = events
.last() {
124 events
.push(Events
::OutBlock(pos
+ 1));
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
]);
143 fn get_previous_positions(events
: &[Events
], mut pos
: usize) -> Vec
<usize> {
144 let mut ret
= Vec
::with_capacity(3);
146 ret
.push(events
[pos
].get_pos());
151 if pos
< 1 || !events
[pos
].is_comment() {
152 let x
= events
[pos
].get_pos();
153 if *ret
.last().unwrap() != x
{
160 ret
.push(events
[pos
].get_pos());
163 if ret
.len() & 1 != 0 && events
[pos
].is_comment() {
166 ret
.iter().rev().cloned().collect()
169 fn build_rule(v
: &[u8], positions
: &[usize]) -> String
{
170 minifier
::css
::minify(
173 .map(|x
| ::std
::str::from_utf8(&v
[x
[0]..x
[1]]).unwrap_or(""))
177 .filter_map(|c
| match c
{
178 '
\n'
| '
\t'
=> Some(' '
),
179 '
/'
| '{' | '}'
=> None
,
184 .filter(|s
| !s
.is_empty())
186 .collect
::<String
>(),
188 .map(|css
| css
.to_string())
189 .unwrap_or_else(|_
| String
::new())
192 fn inner(v
: &[u8], events
: &[Events
], pos
: &mut usize) -> FxHashSet
<CssPath
> {
193 let mut paths
= Vec
::with_capacity(50);
195 while *pos
< events
.len() {
196 if let Some(Events
::OutBlock(_
)) = get_useful_next(events
, pos
) {
200 if let Some(Events
::InBlock(_
)) = get_useful_next(events
, pos
) {
201 paths
.push(CssPath
::new(build_rule(v
, &get_previous_positions(events
, *pos
))));
204 while let Some(Events
::InBlock(_
)) = get_useful_next(events
, pos
) {
205 if let Some(ref mut path
) = paths
.last_mut() {
206 for entry
in inner(v
, events
, pos
).iter() {
207 path
.children
.insert(entry
.clone());
211 if let Some(Events
::OutBlock(_
)) = get_useful_next(events
, pos
) {
215 paths
.iter().cloned().collect()
218 pub(crate) fn load_css_paths(v
: &[u8]) -> CssPath
{
219 let events
= load_css_events(v
);
222 let mut parent
= CssPath
::new("parent".to_owned());
223 parent
.children
= inner(v
, &events
, &mut pos
);
227 pub(crate) fn get_differences(against
: &CssPath
, other
: &CssPath
, v
: &mut Vec
<String
>) {
228 if against
.name
== other
.name
{
229 for child
in &against
.children
{
230 let mut found
= false;
231 let mut found_working
= false;
232 let mut tmp
= Vec
::new();
234 for other_child
in &other
.children
{
235 if child
.name
== other_child
.name
{
236 if child
!= other_child
{
237 get_differences(child
, other_child
, &mut tmp
);
239 found_working
= true;
246 v
.push(format
!(" Missing \"{}\" rule", child
.name
));
247 } else if !found_working
{
248 v
.extend(tmp
.iter().cloned());
254 pub(crate) fn test_theme_against
<P
: AsRef
<Path
>>(
258 ) -> (bool
, Vec
<String
>) {
259 let data
= match fs
::read(f
) {
262 diag
.struct_err(&e
.to_string()).emit();
263 return (false, vec
![]);
267 let paths
= load_css_paths(&data
);
268 let mut ret
= vec
![];
269 get_differences(against
, &paths
, &mut ret
);