/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ /* * Copyright 2013 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.md or: * http://opensource.org/licenses/BSD-2-Clause */ import { prettyFast } from "../pretty-fast"; import { SourceMapConsumer } from "devtools/client/shared/vendor/source-map/source-map"; const cases = [ { name: "Simple function", input: "function foo() { bar(); }", }, { name: "Nested function", input: "function foo() { function bar() { debugger; } bar(); }", }, { name: "Immediately invoked function expression", input: "(function(){thingy()}())", }, { name: "Single line comment", input: ` // Comment function foo() { bar(); }`, }, { name: "Multi line comment", input: ` /* Comment more comment */ function foo() { bar(); } `, }, { name: "Null assignment", input: "var i=null;" }, { name: "Undefined assignment", input: "var i=undefined;" }, { name: "Void 0 assignment", input: "var i=void 0;" }, { name: "This property access", input: "var foo=this.foo;\n", }, { name: "True assignment", input: "var foo=true;\n", }, { name: "False assignment", input: "var foo=false;\n", }, { name: "For loop", input: "for (var i = 0; i < n; i++) { console.log(i); }", }, { name: "for..of loop", input: "for (const x of [1,2,3]) { console.log(x) }", }, { name: "String with semicolon", input: "var foo = ';';\n", }, { name: "String with quote", input: 'var foo = "\'";\n', }, { name: "Function calls", input: "var result=func(a,b,c,d);", }, { name: "Regexp", input: "var r=/foobar/g;", }, { name: "In operator", input: "if(foo in bar){doThing()}", output: "if (foo in bar) {\n" + " doThing()\n" + "}\n", }, { name: "With statement", input: "with(obj){crock()}", }, { name: "New expression", input: "var foo=new Foo();", }, { name: "Continue/break statements", input: "while(1){if(x){continue}if(y){break}if(z){break foo}}", }, { name: "Instanceof", input: "var a=x instanceof y;", }, { name: "Binary operators", input: "var a=5*30;var b=5>>3;", }, { name: "Delete", input: "delete obj.prop;", }, { name: "Try/catch/finally statement", input: "try{dangerous()}catch(e){handle(e)}finally{cleanup()}", }, { name: "If/else statement", input: "if(c){then()}else{other()}", }, { name: "If/else without curlies", input: "if(c) a else b", }, { name: "Objects", input: ` var o={a:1, b:2};`, }, { name: "Do/while loop", input: "do{x}while(y)", }, { name: "Arrays", input: "var a=[1,2,3];", }, { name: "Arrays and spread operator", input: "var a=[1,...[2,3],...[], 4];", }, { name: "Empty object/array literals", input: `let a=[];const b={};c={...{},d: 42};for(let x of []){for(let y in {}){}}`, }, { name: "Code that relies on ASI", input: ` var foo = 10 var bar = 20 function g() { a() b() }`, }, { name: "Ternary operator", input: "bar?baz:bang;", }, { name: "Switch statements", input: "switch(x){case a:foo();break;default:bar()}", }, { name: "Multiple single line comments", input: `function f() { // a // b // c }`, }, { name: "Indented multiline comment", input: `function foo() { /** * java doc style comment * more comment */ bar(); }`, }, { name: "ASI return", input: `function f() { return {} }`, }, { name: "Non-ASI property access", input: `[1,2,3] [0]`, }, { name: "Non-ASI in", input: `'x' in foo`, }, { name: "Non-ASI function call", input: `f ()`, }, { name: "Non-ASI new", input: `new F()`, }, { name: "Getter and setter literals", input: "var obj={get foo(){return this._foo},set foo(v){this._foo=v}}", }, { name: "Escaping backslashes in strings", input: "'\\\\'\n", }, { name: "Escaping carriage return in strings", input: "'\\r'\n", }, { name: "Escaping tab in strings", input: "'\\t'\n", }, { name: "Escaping vertical tab in strings", input: "'\\v'\n", }, { name: "Escaping form feed in strings", input: "'\\f'\n", }, { name: "Escaping null character in strings", input: "'\\0'\n", }, { name: "Escaping single quote character in strings", input: "'\\''\n", }, { name: "Escaping backslashes in template strings", input: "`\\\\`\n", }, { name: "Escaping carriage return in template strings", input: "`\\r`\n", }, { name: "Escaped line feed in template strings", input: "`\\n`\n", }, { name: "Raw line feed in template strings", input: "`\n`\n", }, { name: "Escaping tab in template strings", input: "`\\t`\n", }, { name: "Escaping vertical tab in template strings", input: "`\\v`\n", }, { name: "Escaping form feed in template strings", input: "`\\f`\n", }, { name: "Escaping null character in template strings", input: "`\\0`\n", }, { name: "Escaping backtick character in template strings", input: "`\\``\n", }, { name: "Bug 977082 - space between grouping operator and dot notation", input: `JSON.stringify(3).length; ([1,2,3]).length; (new Date()).toLocaleString();`, }, { name: "Bug 975477 don't move end of line comments to next line", input: `switch (request.action) { case 'show': //$NON-NLS-0$ if (localStorage.hideicon !== 'true') { //$NON-NLS-0$ chrome.pageAction.show(sender.tab.id); } break; case 'hide': /*Multiline Comment */ break; default: console.warn('unknown request'); //$NON-NLS-0$ // don't respond if you don't understand the message. return; }`, }, { name: "Const handling", input: "const d = 'yes';\n", }, { name: "Let handling without value", input: "let d;\n", }, { name: "Let handling with value", input: "let d = 'yes';\n", }, { name: "Template literals", // issue in acorn input: "`abc${JSON.stringify({clas: 'testing'})}def`;{a();}", }, { name: "Class handling", input: "class Class{constructor(){}}", }, { name: "Subclass handling", input: "class Class extends Base{constructor(){}}", }, { name: "Unnamed class handling", input: "let unnamed=class{constructor(){}}", }, { name: "Named class handling", input: "let unnamed=class Class{constructor(){}}", }, { name: "Class extension within a function", input: "(function() { class X extends Y { constructor() {} } })()", }, { name: "Bug 1261971 - indentation after switch statement", input: "{switch(x){}if(y){}done();}", }, { name: "Bug 1206633 - spaces in for of", input: "for (let tab of tabs) {}", }, { name: "Bug pretty-sure-3 - escaping line and paragraph separators", input: "x = '\\u2029\\u2028';", }, { name: "Bug pretty-sure-4 - escaping null character before digit", input: "x = '\\u00001';", }, { name: "Bug pretty-sure-5 - empty multiline comment shouldn't throw exception", input: `{ /* */ return; }`, }, { name: "Bug pretty-sure-6 - inline comment shouldn't move parenthesis to next line", input: `return /* inline comment */ ( 1+1);`, }, { name: "Bug pretty-sure-7 - accessing a literal number property requires a space", input: "0..toString()+x.toString();", }, { name: "Bug pretty-sure-8 - return and yield only accept arguments when on the same line", input: `{ return (x) yield (x) yield *x }`, }, { name: "Bug pretty-sure-9 - accept unary operator at start of file", input: "+ 0", }, { name: "Stack-keyword property access", input: "foo.a=1.1;foo.do.switch.case.default=2.2;foo.b=3.3;\n", }, { name: "Dot handling with let which is identifier name", input: "y.let.let = 1.23;\n", }, { name: "Dot handling with keywords which are identifier name", input: "y.await.break.const.delete.else.return.new.yield = 1.23;\n", }, { name: "Optional chaining parsing support", input: "x?.y?.z?.['a']?.check();\n", }, { name: "Private fields parsing support", input: ` class MyClass { constructor(a) { this.#a = a;this.#b = Math.random();this.ab = this.#getAB(); } #a #b = "default value" static #someStaticPrivate #getA() { return this.#a; } #getAB() { return this.#getA()+this. #b } } `, }, { name: "Long parenthesis", input: ` if (thisIsAVeryLongVariable && thisIsAnotherOne || yetAnotherVeryLongVariable) { (thisIsAnotherOne = thisMayReturnNull() || "hi", thisIsAVeryLongVariable = 42, yetAnotherVeryLongVariable && doSomething(true /* do it well */,thisIsAVeryLongVariable, thisIsAnotherOne, yetAnotherVeryLongVariable)) } for (let thisIsAnotherVeryLongVariable = 0; i < thisIsAnotherVeryLongVariable.length; thisIsAnotherVeryLongVariable++) {} const x = ({thisIsAnotherVeryLongPropertyName: "but should not cause the paren to be a line delimiter"}) `, }, { name: "Fat arrow function", input: ` const a = () => 42 addEventListener("click", e => { return false }); const sum = (c,d) => c+d `, }, ]; const includesOnly = cases.find(({ only }) => only); for (const { name, input, only, skip } of cases) { if ((includesOnly && !only) || skip) { continue; } // Wrapping name into a string to avoid jest/valid-title failures test(`${name}`, async () => { const actual = prettyFast(input, { indent: " ", url: "test.js", }); expect(actual.code).toMatchSnapshot(); const smc = await new SourceMapConsumer(actual.map.toJSON()); const mappings = []; smc.eachMapping( ({ generatedColumn, generatedLine, originalColumn, originalLine }) => { mappings.push( `(${originalLine}, ${originalColumn}) -> (${generatedLine}, ${generatedColumn})` ); } ); expect(mappings).toMatchSnapshot(); }); }