1############################################################################### 2# 3# Copyright (c) 2011 Ruslan Spivak 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 13# all 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 21# THE SOFTWARE. 22# 23############################################################################### 24 25__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>' 26 27import unittest 28 29from slimit import minify 30 31 32class MinifierTestCase(unittest.TestCase): 33 34 def assertMinified(self, source, expected): 35 minified = minify(source) 36 self.maxDiff = None 37 self.assertSequenceEqual(minified, expected) 38 39 TEST_CASES = [ 40 (""" 41 jQuery.fn = jQuery.prototype = { 42 // For internal use only. 43 _data: function( elem, name, data ) { 44 return jQuery.data( elem, name, data, true ); 45 } 46 }; 47 """, 48 'jQuery.fn=jQuery.prototype={_data:function(elem,name,data){return jQuery.data(elem,name,data,true);}};'), 49 50 ('context = context instanceof jQuery ? context[0] : context;', 51 'context=context instanceof jQuery?context[0]:context;' 52 ), 53 54 (""" 55 /* 56 * A number of helper functions used for managing events. 57 * Many of the ideas behind this code originated from 58 * Dean Edwards' addEvent library. 59 */ 60 if ( elem && elem.parentNode ) { 61 // Handle the case where IE and Opera return items 62 // by name instead of ID 63 if ( elem.id !== match[2] ) { 64 return rootjQuery.find( selector ); 65 } 66 67 // Otherwise, we inject the element directly into the jQuery object 68 this.length = 1; 69 this[0] = elem; 70 } 71 """, 72 73 'if(elem&&elem.parentNode){if(elem.id!==match[2])return rootjQuery.find(selector);this.length=1;this[0]=elem;}' 74 ), 75 76 (""" 77 var a = function( obj ) { 78 for ( var name in obj ) { 79 return false; 80 } 81 return true; 82 }; 83 """, 84 'var a=function(obj){for(var name in obj)return false;return true;};' 85 ), 86 87 (""" 88 x = "string", y = 5; 89 90 (x = 5) ? true : false; 91 92 for (p in obj) 93 ; 94 95 if (true) 96 val = null; 97 else 98 val = false; 99 100 """, 101 'x="string",y=5;(x=5)?true:false;for(p in obj);if(true)val=null;else val=false;' 102 ), 103 104 # for loops + empty statement in loop body 105 (""" 106 for (x = 0; true; x++) 107 ; 108 for (; true; x++) 109 ; 110 for (x = 0, y = 5; true; x++) 111 ; 112 113 y = (x + 5) * 20; 114 115 """, 116 'for(x=0;true;x++);for(;true;x++);for(x=0,y=5;true;x++);y=(x+5)*20;'), 117 118 119 # unary expressions 120 (""" 121 delete x; 122 typeof x; 123 void x; 124 x += (!y)++; 125 """, 126 'delete x;typeof x;void x;x+=(!y)++;'), 127 128 # label + break label + continue label 129 (""" 130 label: 131 if ( i == 0 ) 132 continue label; 133 switch (day) { 134 case 5: 135 break ; 136 default: 137 break label; 138 } 139 """, 140 'label:if(i==0)continue label;switch(day){case 5:break;default:break label;}'), 141 142 # break + continue: no labels 143 (""" 144 while (i <= 7) { 145 if ( i == 3 ) 146 continue; 147 if ( i == 0 ) 148 break; 149 } 150 """, 151 'while(i<=7){if(i==3)continue;if(i==0)break;}'), 152 153 # regex + one line statements in if and if .. else 154 (""" 155 function a(x, y) { 156 var re = /ab+c/; 157 if (x == 1) 158 return x + y; 159 if (x == 3) 160 return {x: 1}; 161 else 162 return; 163 } 164 """, 165 'function a(x,y){var re=/ab+c/;if(x==1)return x+y;if(x==3)return{x:1};else return;}'), 166 167 # new 168 ('return new jQuery.fn.init( selector, context, rootjQuery );', 169 'return new jQuery.fn.init(selector,context,rootjQuery);' 170 ), 171 172 # no space after 'else' when the next token is (, { 173 (""" 174 if (true) { 175 x = true; 176 y = 3; 177 } else { 178 x = false 179 y = 5 180 } 181 """, 182 'if(true){x=true;y=3;}else{x=false;y=5;}'), 183 184 (""" 185 if (true) { 186 x = true; 187 y = 3; 188 } else 189 (x + ' qw').split(' '); 190 """, 191 "if(true){x=true;y=3;}else(x+' qw').split(' ');"), 192 193 194 ############################################################## 195 # Block braces removal 196 ############################################################## 197 198 # do while 199 ('do { x += 1; } while(true);', 'do x+=1;while(true);'), 200 # do while: multiple statements 201 ('do { x += 1; y += 1;} while(true);', 'do{x+=1;y+=1;}while(true);'), 202 203 # elision 204 ('var a = [1, 2, 3, ,,,5];', 'var a=[1,2,3,,,,5];'), 205 206 # with 207 (""" 208 with (obj) { 209 a = b; 210 } 211 """, 212 'with(obj)a=b;'), 213 214 # with: multiple statements 215 (""" 216 with (obj) { 217 a = b; 218 c = d; 219 } 220 """, 221 'with(obj){a=b;c=d;}'), 222 223 # if else 224 (""" 225 if (true) { 226 x = true; 227 } else { 228 x = false 229 } 230 """, 231 'if(true)x=true;else x=false;'), 232 233 # if: multiple statements 234 (""" 235 if (true) { 236 x = true; 237 y = false; 238 } else { 239 x = false; 240 y = true; 241 } 242 """, 243 'if(true){x=true;y=false;}else{x=false;y=true;}'), 244 245 # try catch finally: one statement 246 (""" 247 try { 248 throw "my_exception"; // generates an exception 249 } 250 catch (e) { 251 // statements to handle any exceptions 252 log(e); // pass exception object to error handler 253 } 254 finally { 255 closefiles(); // always close the resource 256 } 257 """, 258 'try{throw "my_exception";}catch(e){log(e);}finally{closefiles();}' 259 ), 260 261 # try catch finally: no statements 262 (""" 263 try { 264 } 265 catch (e) { 266 } 267 finally { 268 } 269 """, 270 'try{}catch(e){}finally{}' 271 ), 272 273 # try catch finally: multiple statements 274 (""" 275 try { 276 x = 3; 277 y = 5; 278 } 279 catch (e) { 280 log(e); 281 log('e'); 282 } 283 finally { 284 z = 7; 285 log('z'); 286 } 287 """, 288 "try{x=3;y=5;}catch(e){log(e);log('e');}finally{z=7;log('z');}" 289 ), 290 291 # tricky case with an 'if' nested in 'if .. else' 292 # We need to preserve braces in the first 'if' otherwise 293 # 'else' might get associated with nested 'if' instead 294 (""" 295 if ( obj ) { 296 for ( n in obj ) { 297 if ( v === false) { 298 break; 299 } 300 } 301 } else { 302 for ( ; i < l; ) { 303 if ( nv === false ) { 304 break; 305 } 306 } 307 } 308 """, 309 'if(obj){for(n in obj)if(v===false)break;}else for(;i<l;)if(nv===false)break;'), 310 311 # We don't care about nested 'if' when enclosing 'if' block 312 # contains multiple statements because braces won't be removed 313 # by visit_Block when there are multiple statements in the block 314 (""" 315 if ( obj ) { 316 for ( n in obj ) { 317 if ( v === false) { 318 break; 319 } 320 } 321 x = 5; 322 } else { 323 for ( ; i < l; ) { 324 if ( nv === false ) { 325 break; 326 } 327 } 328 } 329 """, 330 'if(obj){for(n in obj)if(v===false)break;x=5;}else for(;i<l;)if(nv===false)break;'), 331 332 333 # No dangling 'else' - remove braces 334 (""" 335 if ( obj ) { 336 for ( n in obj ) { 337 if ( v === false) { 338 break; 339 } else { 340 n = 3; 341 } 342 } 343 } else { 344 for ( ; i < l; ) { 345 if ( nv === false ) { 346 break; 347 } 348 } 349 } 350 """, 351 'if(obj)for(n in obj)if(v===false)break;else n=3;else for(;i<l;)if(nv===false)break;'), 352 353 # foo["bar"] --> foo.bar 354 ('foo["bar"];', 'foo.bar;'), 355 ("foo['bar'];", 'foo.bar;'), 356 ("""foo['bar"']=42;""", """foo['bar"']=42;"""), 357 ("""foo["bar'"]=42;""", """foo["bar'"]=42;"""), 358 ('foo["bar bar"];', 'foo["bar bar"];'), 359 ('foo["bar"+"bar"];', 'foo["bar"+"bar"];'), 360 # https://github.com/rspivak/slimit/issues/34 361 # test some reserved keywords 362 ('foo["for"];', 'foo["for"];'), 363 ('foo["class"];', 'foo["class"];'), 364 365 366 # https://github.com/rspivak/slimit/issues/21 367 # c||(c=393,a=323,b=2321); --> c||c=393,a=323,b=2321; ERROR 368 ('c||(c=393);', 'c||(c=393);'), 369 ('c||(c=393,a=323,b=2321);', 'c||(c=393,a=323,b=2321);'), 370 371 # https://github.com/rspivak/slimit/issues/25 372 ('for(a?b:c;d;)e=1;', 'for(a?b:c;d;)e=1;'), 373 374 # https://github.com/rspivak/slimit/issues/26 375 ('"begin"+ ++a+"end";', '"begin"+ ++a+"end";'), 376 377 # https://github.com/rspivak/slimit/issues/28 378 (""" 379 (function($) { 380 $.hello = 'world'; 381 }(jQuery)); 382 """, 383 "(function($){$.hello='world';}(jQuery));"), 384 385 # function call in FOR init 386 ('for(o(); i < 3; i++) {}', 'for(o();i<3;i++){}'), 387 388 # unary increment operator in FOR init 389 ('for(i++; i < 3; i++) {}', 'for(i++;i<3;i++){}'), 390 391 # unary decrement operator in FOR init 392 ('for(i--; i < 3; i++) {}', 'for(i--;i<3;i++){}'), 393 394 # issue-37, simple identifier in FOR init 395 ('for(i; i < 3; i++) {}', 'for(i;i<3;i++){}'), 396 397 # https://github.com/rspivak/slimit/issues/32 398 (""" 399 Name.prototype = { 400 getPageProp: function Page_getPageProp(key) { 401 return this.pageDict.get(key); 402 }, 403 404 get fullName() { 405 return this.first + " " + this.last; 406 }, 407 408 set fullName(name) { 409 var names = name.split(" "); 410 this.first = names[0]; 411 this.last = names[1]; 412 } 413 }; 414 """, 415 ('Name.prototype={getPageProp:function Page_getPageProp(key){' 416 'return this.pageDict.get(key);},' 417 'get fullName(){return this.first+" "+this.last;},' 418 'set fullName(name){var names=name.split(" ");this.first=names[0];' 419 'this.last=names[1];}};') 420 ), 421 ] 422 423 424def make_test_function(input, expected): 425 426 def test_func(self): 427 self.assertMinified(input, expected) 428 429 return test_func 430 431for index, (input, expected) in enumerate(MinifierTestCase.TEST_CASES): 432 func = make_test_function(input, expected) 433 setattr(MinifierTestCase, 'test_case_%d' % index, func) 434