prettyFast.spec.js (11078B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 /* 6 * Copyright 2013 Mozilla Foundation and contributors 7 * Licensed under the New BSD license. See LICENSE.md or: 8 * http://opensource.org/licenses/BSD-2-Clause 9 */ 10 import { prettyFast } from "../pretty-fast"; 11 import { SourceMapConsumer } from "devtools/client/shared/vendor/source-map/source-map"; 12 13 const cases = [ 14 { 15 name: "Simple function", 16 input: "function foo() { bar(); }", 17 }, 18 { 19 name: "Nested function", 20 input: "function foo() { function bar() { debugger; } bar(); }", 21 }, 22 { 23 name: "Immediately invoked function expression", 24 input: "(function(){thingy()}())", 25 }, 26 { 27 name: "Single line comment", 28 input: ` 29 // Comment 30 function foo() { bar(); }`, 31 }, 32 { 33 name: "Multi line comment", 34 input: ` 35 /* Comment 36 more comment */ 37 function foo() { bar(); } 38 `, 39 }, 40 { name: "Null assignment", input: "var i=null;" }, 41 { name: "Undefined assignment", input: "var i=undefined;" }, 42 { name: "Void 0 assignment", input: "var i=void 0;" }, 43 { 44 name: "This property access", 45 input: "var foo=this.foo;\n", 46 }, 47 48 { 49 name: "True assignment", 50 input: "var foo=true;\n", 51 }, 52 53 { 54 name: "False assignment", 55 input: "var foo=false;\n", 56 }, 57 58 { 59 name: "For loop", 60 input: "for (var i = 0; i < n; i++) { console.log(i); }", 61 }, 62 63 { 64 name: "for..of loop", 65 input: "for (const x of [1,2,3]) { console.log(x) }", 66 }, 67 68 { 69 name: "String with semicolon", 70 input: "var foo = ';';\n", 71 }, 72 73 { 74 name: "String with quote", 75 input: 'var foo = "\'";\n', 76 }, 77 78 { 79 name: "Function calls", 80 input: "var result=func(a,b,c,d);", 81 }, 82 83 { 84 name: "Regexp", 85 input: "var r=/foobar/g;", 86 }, 87 88 { 89 name: "In operator", 90 input: "if(foo in bar){doThing()}", 91 output: "if (foo in bar) {\n" + " doThing()\n" + "}\n", 92 }, 93 { 94 name: "With statement", 95 input: "with(obj){crock()}", 96 }, 97 { 98 name: "New expression", 99 input: "var foo=new Foo();", 100 }, 101 { 102 name: "Continue/break statements", 103 input: "while(1){if(x){continue}if(y){break}if(z){break foo}}", 104 }, 105 { 106 name: "Instanceof", 107 input: "var a=x instanceof y;", 108 }, 109 { 110 name: "Binary operators", 111 input: "var a=5*30;var b=5>>3;", 112 }, 113 { 114 name: "Delete", 115 input: "delete obj.prop;", 116 }, 117 118 { 119 name: "Try/catch/finally statement", 120 input: "try{dangerous()}catch(e){handle(e)}finally{cleanup()}", 121 }, 122 { 123 name: "If/else statement", 124 input: "if(c){then()}else{other()}", 125 }, 126 { 127 name: "If/else without curlies", 128 input: "if(c) a else b", 129 }, 130 { 131 name: "Objects", 132 input: ` 133 var o={a:1, 134 b:2};`, 135 }, 136 { 137 name: "Do/while loop", 138 input: "do{x}while(y)", 139 }, 140 { 141 name: "Arrays", 142 input: "var a=[1,2,3];", 143 }, 144 { 145 name: "Arrays and spread operator", 146 input: "var a=[1,...[2,3],...[], 4];", 147 }, 148 { 149 name: "Empty object/array literals", 150 input: `let a=[];const b={};c={...{},d: 42};for(let x of []){for(let y in {}){}}`, 151 }, 152 { 153 name: "Code that relies on ASI", 154 input: ` 155 var foo = 10 156 var bar = 20 157 function g() { 158 a() 159 b() 160 }`, 161 }, 162 { 163 name: "Ternary operator", 164 input: "bar?baz:bang;", 165 }, 166 { 167 name: "Switch statements", 168 input: "switch(x){case a:foo();break;default:bar()}", 169 }, 170 171 { 172 name: "Multiple single line comments", 173 input: `function f() { 174 // a 175 // b 176 // c 177 }`, 178 }, 179 { 180 name: "Indented multiline comment", 181 input: `function foo() { 182 /** 183 * java doc style comment 184 * more comment 185 */ 186 bar(); 187 }`, 188 }, 189 { 190 name: "ASI return", 191 input: `function f() { 192 return 193 {} 194 }`, 195 }, 196 { 197 name: "Non-ASI property access", 198 input: `[1,2,3] 199 [0]`, 200 }, 201 { 202 name: "Non-ASI in", 203 input: `'x' 204 in foo`, 205 }, 206 207 { 208 name: "Non-ASI function call", 209 input: `f 210 ()`, 211 }, 212 { 213 name: "Non-ASI new", 214 input: `new 215 F()`, 216 }, 217 { 218 name: "Getter and setter literals", 219 input: "var obj={get foo(){return this._foo},set foo(v){this._foo=v}}", 220 }, 221 { 222 name: "Escaping backslashes in strings", 223 input: "'\\\\'\n", 224 }, 225 { 226 name: "Escaping carriage return in strings", 227 input: "'\\r'\n", 228 }, 229 { 230 name: "Escaping tab in strings", 231 input: "'\\t'\n", 232 }, 233 { 234 name: "Escaping vertical tab in strings", 235 input: "'\\v'\n", 236 }, 237 { 238 name: "Escaping form feed in strings", 239 input: "'\\f'\n", 240 }, 241 { 242 name: "Escaping null character in strings", 243 input: "'\\0'\n", 244 }, 245 { 246 name: "Escaping single quote character in strings", 247 input: "'\\''\n", 248 }, 249 { 250 name: "Escaping backslashes in template strings", 251 input: "`\\\\`\n", 252 }, 253 { 254 name: "Escaping carriage return in template strings", 255 input: "`\\r`\n", 256 }, 257 { 258 name: "Escaped line feed in template strings", 259 input: "`\\n`\n", 260 }, 261 { 262 name: "Raw line feed in template strings", 263 input: "`\n`\n", 264 }, 265 { 266 name: "Escaping tab in template strings", 267 input: "`\\t`\n", 268 }, 269 { 270 name: "Escaping vertical tab in template strings", 271 input: "`\\v`\n", 272 }, 273 { 274 name: "Escaping form feed in template strings", 275 input: "`\\f`\n", 276 }, 277 { 278 name: "Escaping null character in template strings", 279 input: "`\\0`\n", 280 }, 281 { 282 name: "Escaping backtick character in template strings", 283 input: "`\\``\n", 284 }, 285 { 286 name: "Bug 977082 - space between grouping operator and dot notation", 287 input: `JSON.stringify(3).length; 288 ([1,2,3]).length; 289 (new Date()).toLocaleString();`, 290 }, 291 { 292 name: "Bug 975477 don't move end of line comments to next line", 293 input: `switch (request.action) { 294 case 'show': //$NON-NLS-0$ 295 if (localStorage.hideicon !== 'true') { //$NON-NLS-0$ 296 chrome.pageAction.show(sender.tab.id); 297 } 298 break; 299 case 'hide': /*Multiline 300 Comment */ 301 break; 302 default: 303 console.warn('unknown request'); //$NON-NLS-0$ 304 // don't respond if you don't understand the message. 305 return; 306 }`, 307 }, 308 { 309 name: "Const handling", 310 input: "const d = 'yes';\n", 311 }, 312 { 313 name: "Let handling without value", 314 input: "let d;\n", 315 }, 316 { 317 name: "Let handling with value", 318 input: "let d = 'yes';\n", 319 }, 320 { 321 name: "Template literals", 322 // issue in acorn 323 input: "`abc${JSON.stringify({clas: 'testing'})}def`;{a();}", 324 }, 325 { 326 name: "Class handling", 327 input: "class Class{constructor(){}}", 328 }, 329 { 330 name: "Subclass handling", 331 input: "class Class extends Base{constructor(){}}", 332 }, 333 { 334 name: "Unnamed class handling", 335 input: "let unnamed=class{constructor(){}}", 336 }, 337 { 338 name: "Named class handling", 339 input: "let unnamed=class Class{constructor(){}}", 340 }, 341 { 342 name: "Class extension within a function", 343 input: "(function() { class X extends Y { constructor() {} } })()", 344 }, 345 { 346 name: "Bug 1261971 - indentation after switch statement", 347 input: "{switch(x){}if(y){}done();}", 348 }, 349 { 350 name: "Bug 1206633 - spaces in for of", 351 input: "for (let tab of tabs) {}", 352 }, 353 { 354 name: "Bug pretty-sure-3 - escaping line and paragraph separators", 355 input: "x = '\\u2029\\u2028';", 356 }, 357 { 358 name: "Bug pretty-sure-4 - escaping null character before digit", 359 input: "x = '\\u00001';", 360 }, 361 { 362 name: "Bug pretty-sure-5 - empty multiline comment shouldn't throw exception", 363 input: `{ 364 /* 365 */ 366 return; 367 }`, 368 }, 369 { 370 name: "Bug pretty-sure-6 - inline comment shouldn't move parenthesis to next line", 371 input: `return /* inline comment */ ( 372 1+1);`, 373 }, 374 { 375 name: "Bug pretty-sure-7 - accessing a literal number property requires a space", 376 input: "0..toString()+x.toString();", 377 }, 378 { 379 name: "Bug pretty-sure-8 - return and yield only accept arguments when on the same line", 380 input: `{ 381 return 382 (x) 383 yield 384 (x) 385 yield 386 *x 387 }`, 388 }, 389 { 390 name: "Bug pretty-sure-9 - accept unary operator at start of file", 391 input: "+ 0", 392 }, 393 { 394 name: "Stack-keyword property access", 395 input: "foo.a=1.1;foo.do.switch.case.default=2.2;foo.b=3.3;\n", 396 }, 397 { 398 name: "Dot handling with let which is identifier name", 399 input: "y.let.let = 1.23;\n", 400 }, 401 { 402 name: "Dot handling with keywords which are identifier name", 403 input: "y.await.break.const.delete.else.return.new.yield = 1.23;\n", 404 }, 405 { 406 name: "Optional chaining parsing support", 407 input: "x?.y?.z?.['a']?.check();\n", 408 }, 409 { 410 name: "Private fields parsing support", 411 input: ` 412 class MyClass { 413 constructor(a) { 414 this.#a = a;this.#b = Math.random();this.ab = this.#getAB(); 415 } 416 #a 417 #b = "default value" 418 static #someStaticPrivate 419 #getA() { 420 return this.#a; 421 } 422 #getAB() { 423 return this.#getA()+this. 424 #b 425 } 426 } 427 `, 428 }, 429 { 430 name: "Long parenthesis", 431 input: ` 432 if (thisIsAVeryLongVariable && thisIsAnotherOne || yetAnotherVeryLongVariable) { 433 (thisIsAnotherOne = thisMayReturnNull() || "hi", thisIsAVeryLongVariable = 42, yetAnotherVeryLongVariable && doSomething(true /* do it well */,thisIsAVeryLongVariable, thisIsAnotherOne, yetAnotherVeryLongVariable)) 434 } 435 for (let thisIsAnotherVeryLongVariable = 0; i < thisIsAnotherVeryLongVariable.length; thisIsAnotherVeryLongVariable++) {} 436 const x = ({thisIsAnotherVeryLongPropertyName: "but should not cause the paren to be a line delimiter"}) 437 `, 438 }, 439 { 440 name: "Fat arrow function", 441 input: ` 442 const a = () => 42 443 addEventListener("click", e => { return false }); 444 const sum = (c,d) => c+d 445 `, 446 }, 447 ]; 448 449 const includesOnly = cases.find(({ only }) => only); 450 451 for (const { name, input, only, skip } of cases) { 452 if ((includesOnly && !only) || skip) { 453 continue; 454 } 455 // Wrapping name into a string to avoid jest/valid-title failures 456 test(`${name}`, async () => { 457 const actual = prettyFast(input, { 458 indent: " ", 459 url: "test.js", 460 }); 461 462 expect(actual.code).toMatchSnapshot(); 463 464 const smc = await new SourceMapConsumer(actual.map.toJSON()); 465 const mappings = []; 466 smc.eachMapping( 467 ({ generatedColumn, generatedLine, originalColumn, originalLine }) => { 468 mappings.push( 469 `(${originalLine}, ${originalColumn}) -> (${generatedLine}, ${generatedColumn})` 470 ); 471 } 472 ); 473 expect(mappings).toMatchSnapshot(); 474 }); 475 }