1 // MIT License
2 //
3 // Copyright (c) 2018 Guillaume Gomez
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in all
13 // copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 // SOFTWARE.
22 
23 use css::minify;
24 
25 /*enum Element {
26     /// Rule starting with `@`:
27     ///
28     /// * charset
29     /// * font-face
30     /// * import
31     /// * keyframes
32     /// * media
33     AtRule(AtRule<'a>),
34     /// Any "normal" CSS rule block.
35     ///
36     /// Contains the selector(s) and its content.
37     ElementRule(Vec<&'a str>, Vec<Property<'a>>),
38 }
39 
40 fn get_property<'a>(source: &'a str, iterator: &mut Peekable<CharIndices>,
41                     start_pos: &mut usize) -> Option<Property<'a>> {
42     let mut end_pos = None;
43     // First we get the property name.
44     while let Some((pos, c)) = iterator.next() {
45         if let Ok(c) = ReservedChar::try_from(c) {
46             if c.is_useless() {
47                 continue
48             } else if c == ReservedChar::OpenCurlyBrace {
49                 return None
50             } else if c == ReservedChar::Colon {
51                 end_pos = Some(pos);
52                 break
53             } else { // Invalid character.
54                 return None;
55             }
56         } else if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' {
57             // everything's fine for now...
58         } else {
59             return None; // invalid character
60         }
61     }
62     if end_pos.is_none() || end_pos == Some(*start_pos + 1) {
63         return None;
64     }
65     while let Some((pos, c)) = iterator.next() {
66         if let Ok(c) = ReservedChar::try_from(c) {
67             if c == ReservedChar::DoubleQuote || c == ReservedChar::Quote {
68                 get_string(source, iterator, &mut 0, c)
69             } else if c == ReservedChar::SemiColon {
70                 // we reached the end!
71                 let end_pos = end_pos.unwrap();
72                 *start_pos = pos;
73                 return Property {
74                     name: &source[start_pos..end_pos],
75                     value: &source[end_pos..pos],
76                 }
77             }
78         }
79     }
80     None
81 }
82 
83 enum Selector<'a> {
84     Tag(&'a str),
85     /// '.'
86     Class(&'a str),
87     /// '#'
88     Id(&'a str),
89     /// '<', '>', '(', ')', '+', ' ', '[', ']'
90     Operator(char),
91 }
92 
93 struct ElementRule<'a> {
94     selectors: Vec<Selector<'a>>,
95     properties: Vec<Property<'a>>,
96 }
97 
98 fn get_element_rule<'a>(source: &'a str, iterator: &mut Peekable<CharIndices>,
99                         c: char) -> Option<Token<'a>> {
100     let mut selectors = Vec::with_capacity(2);
101 
102     while let Some(s) = get_next_selector(source, iterator, c) {
103         if !selectors.is_empty() || !s.empty_operator() {
104         }
105         selectors.push(s);
106     }
107 }
108 
109 fn get_media_query<'a>(source: &'a str, iterator: &mut Peekable<CharIndices>,
110                        start_pos: &mut usize) -> Option<Token<'a>> {
111     while let Some((pos, c)) = iterator.next() {
112         if c == '{' {
113             ;
114         }
115     }
116     None // An error occurred, sad life...
117 }
118 
119 
120 fn get_properties<'a>(source: &'a str, iterator: &mut Peekable<CharIndices>,
121                       start_pos: &mut usize) -> Vec<Property> {
122     let mut ret = Vec::with_capacity(2);
123     while let Some(property) = get_property(source, iterator, start_pos) {
124         ret.push(property);
125     }
126     ret
127 }
128 
129 pub struct Property<'a> {
130     name: &'a str,
131     value: &'a str,
132 }
133 
134 pub enum AtRule<'a> {
135     /// Contains the charset. Supposed to be the first rule in the style sheet and be present
136     /// only once.
137     Charset(&'a str),
138     /// font-face rule.
139     FontFace(Vec<Property<'a>>),
140     /// Contains the import.
141     Import(&'a str),
142     /// Contains the rule and the block.
143     Keyframes(&'a str, Tokens<'a>),
144     /// Contains the rules and the block.
145     Media(Vec<&'a str>, Tokens<'a>),
146 }
147 
148 impl fmt::Display for AtRule {
149     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150         write!(f, "@{}", &match *self {
151             AtRule::Charset(c) => format!("charset {};", c),
152             AtRule::FontFace(t) => format!("font-face {{{}}};", t),
153             AtRule::Import(i) => format!("import {};", i),
154             AtRule::Keyframes(r, t) => format!("keyframes {} {{{}}}", r, t),
155             AtRule::Media(r, t) => format!("media {} {{{}}}", r.join(" ").collect::<String>(), t),
156         })
157     }
158 }*/
159 
160 #[test]
check_minification()161 fn check_minification() {
162     let s = r#"
163 /** Baguette! */
164 .b > p + div:hover {
165     background: #fff;
166 }
167 
168 a[target = "_blank"] {
169     /* I like weird tests. */
170     border: 1px solid yellow   ;
171 }
172 "#;
173     let expected = r#"/*! Baguette! */
174 .b>p+div:hover{background:#fff;}a[target="_blank"]{border:1px solid yellow;}"#;
175     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
176 }
177 
178 #[test]
check_minification2()179 fn check_minification2() {
180     let s = r#"
181 h2, h3:not(.impl):not(.method):not(.type) {
182     background-color: #0a042f !important;
183 }
184 
185 :target { background: #494a3d; }
186 
187 .table-display tr td:first-child {
188     float: right;
189 }
190 
191 /* just some
192  * long
193  *
194  * very
195  * long
196  * comment :)
197  */
198 @media (max-width: 700px) {
199     .theme-picker {
200         left: 10px;
201         top: 54px;
202         z-index: 1;
203         background-color: rgba(0,  0  ,  0  ,  0);
204         font: 15px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
205     }
206 }"#;
207     let expected = "h2,h3:not(.impl):not(.method):not(.type){background-color:#0a042f !important;}\
208                     :target{background:#494a3d;}.table-display tr td:first-child{float:right;}\
209                     @media (max-width:700px){.theme-picker{left:10px;top:54px;z-index:1;\
210                     background-color:rgba(0,0,0,0);font:15px \"SFMono-Regular\",Consolas,\
211                     \"Liberation Mono\",Menlo,Courier,monospace;}}";
212     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
213 }
214 
215 #[test]
check_calc()216 fn check_calc() {
217     let s = ".foo { width: calc(100% - 34px); }";
218     let expected = ".foo{width:calc(100% - 34px);}";
219     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
220 }
221 
222 #[test]
check_spaces()223 fn check_spaces() {
224     let s = ".line-numbers .line-highlighted { color: #0a042f !important; }";
225     let expected = ".line-numbers .line-highlighted{color:#0a042f !important;}";
226     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
227 }
228 
229 #[test]
check_space_after_paren()230 fn check_space_after_paren() {
231     let s = ".docblock:not(.type-decl) a:not(.srclink) {}";
232     let expected = ".docblock:not(.type-decl) a:not(.srclink){}";
233     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
234 }
235 
236 #[test]
check_space_after_and()237 fn check_space_after_and() {
238     let s = "@media only screen and (max-width : 600px) {}";
239     let expected = "@media only screen and (max-width:600px){}";
240     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
241 }
242 
243 #[test]
check_space_after_brackets()244 fn check_space_after_brackets() {
245     let s = "#main[data-behavior = \"1\"] {}";
246     let expected = "#main[data-behavior=\"1\"]{}";
247     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
248 
249     let s = "#main[data-behavior = \"1\"] .aclass";
250     let expected = "#main[data-behavior=\"1\"] .aclass";
251     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
252 
253     let s = "#main[data-behavior = \"1\"] ul.aclass";
254     let expected = "#main[data-behavior=\"1\"] ul.aclass";
255     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
256 }
257 
258 #[test]
check_whitespaces_in_calc()259 fn check_whitespaces_in_calc() {
260     let s = ".foo { width: calc(130px     + 10%); }";
261     let expected = ".foo{width:calc(130px + 10%);}";
262     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
263 
264     let s = ".foo { width: calc(130px + (45% - 10% +   (12   *   2px))); }";
265     let expected = ".foo{width:calc(130px + (45% - 10% + (12 * 2px)));}";
266     assert_eq!(minify(s).expect("minify failed"), expected.to_owned());
267 }
268 
269 #[test]
check_weird_comments()270 fn check_weird_comments() {
271     let s = ".test1 {
272     font-weight: 30em;
273 }/**/
274 .test2 {
275     font-weight: 30em;
276 }/**/
277 .test3 {
278     font-weight: 30em;
279 }/**/";
280     let expected = ".test1{font-weight:30em;}.test2{font-weight:30em;}.test3{font-weight:30em;}";
281     assert_eq!(minify(s).expect("minify failed").as_str(), expected);
282 }
283 
284 #[test]
check_slash_slash()285 fn check_slash_slash() {
286     let s = "body {
287     background-image: url(data:image/webp;base64,c//S4KP//ZZ/19Uj/UA==);
288 }";
289     let expected = "body{background-image:url(data:image/webp;base64,c//S4KP//ZZ/19Uj/UA==);}";
290     assert_eq!(minify(s).expect("minify failed").as_str(), expected);
291 }
292