chrome-devtools-frontend
Version:
Chrome DevTools UI
790 lines (705 loc) • 22.9 kB
text/typescript
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as FormatterWorker from './formatter_worker.js';
function formatJavaScript(text: string): string {
return FormatterWorker.FormatterWorker.format('text/javascript', text, ' ').content;
}
describe('JavaScriptFormatter', () => {
it('formats await expressions correctly', () => {
const formattedCode =
formatJavaScript('(async () => { await someFunctionThatNeedsAwaiting(); callSomeOtherFunction(); })();');
assert.strictEqual(
formattedCode,
'(async () => {\n await someFunctionThatNeedsAwaiting();\n callSomeOtherFunction();\n}\n)();\n');
});
it('formats async-function expressions correctly', () => {
const formattedCode = formatJavaScript('async function foo() {return await Promise.resolve(1);}');
assert.strictEqual(formattedCode, `async function foo() {
return await Promise.resolve(1);
}
`);
});
it('formats top-level await correctly', () => {
const formattedCode = formatJavaScript('const myFile=await import(\n"my-file.mjs");');
assert.strictEqual(formattedCode, 'const myFile = await import("my-file.mjs");\n');
});
it('formats identifiers containing escaped characters correctly', () => {
const formattedCode = formatJavaScript(String.raw`const x=42;let \u0275_escaped;`);
assert.strictEqual(formattedCode, 'const x = 42;\nlet \\u0275_escaped;\n');
});
it('formats nullish coalescing expressions correctly', () => {
const formattedCode = formatJavaScript('false??true');
assert.strictEqual(formattedCode, 'false ?? true\n');
});
it('formats optional chaining expressions correctly', () => {
const formattedCode = formatJavaScript('var x=a?.b;');
assert.strictEqual(formattedCode, 'var x = a?.b;\n');
});
it('formats logical assignment expressions correctly', () => {
const formattedCode = formatJavaScript('x||=1;');
assert.strictEqual(formattedCode, 'x ||= 1;\n');
});
it('formats numeric separators correctly', () => {
const formattedCode = formatJavaScript('x=1_000;');
assert.strictEqual(formattedCode, 'x = 1_000;\n');
});
it('formats do-while loops correctly', () => {
const formattedCode = formatJavaScript(`function demo() {
do {} while (false);
if (true) {}
}
function demo() {do {} while (false);if (true) {}}`);
assert.strictEqual(formattedCode, `function demo() {
do {} while (false);
if (true) {}
}
function demo() {
do {} while (false);
if (true) {}
}
`);
});
it('formats while loops correctly', () => {
const formattedCode = formatJavaScript('while(true){print(\'infinity\');}');
assert.strictEqual(formattedCode, `while (true) {
print(\'infinity\');
}
`);
});
it('formats function statements correctly', () => {
const formattedCode = formatJavaScript('function test(a,b,c){a*=b;return c+a;}');
assert.strictEqual(formattedCode, `function test(a, b, c) {
a *= b;
return c + a;
}
`);
});
it('formats variable statements correctly', () => {
const formattedCode = formatJavaScript('var a=1,b={},c=2,d="hello world";var a,b,c,d=2,e,f=3;var a={};');
assert.strictEqual(formattedCode, `var a = 1
, b = {}
, c = 2
, d = "hello world";
var a, b, c, d = 2, e, f = 3;
var a = {};
`);
});
it('formats array-literals correctly', () => {
const formattedCode = formatJavaScript('var arr=[3,2,1,0]');
assert.strictEqual(formattedCode, `var arr = [3, 2, 1, 0]
`);
});
it('formats ternary expressions correctly', () => {
const formattedCode = formatJavaScript('a>b?a:b');
assert.strictEqual(formattedCode, `a > b ? a : b
`);
});
it('formats labeled statements correctly', () => {
const formattedCode = formatJavaScript('firstLoop:while(true){break firstLoop;continue firstLoop;}');
assert.strictEqual(formattedCode, `firstLoop: while (true) {
break firstLoop;
continue firstLoop;
}
`);
});
it('formats multiple statements on the same line correctly', () => {
const formattedCode = formatJavaScript('rebuild(),show(),hasNew?refresh():noop();');
assert.strictEqual(formattedCode, `rebuild(),
show(),
hasNew ? refresh() : noop();
`);
});
it('formats if-statements correctly', () => {
const formattedCode = formatJavaScript(
'if(a<b)log(a);else log(b);if(a<b){log(a)}else{log(b);}if(a===b)log(\'equals\');if(a!==b){log(\'non-eq\');}if(a>b&&b>c){print(a);print(b);}');
assert.strictEqual(formattedCode, `if (a < b)
log(a);
else
log(b);
if (a < b) {
log(a)
} else {
log(b);
}
if (a === b)
log('equals');
if (a !== b) {
log('non-eq');
}
if (a > b && b > c) {
print(a);
print(b);
}
`);
});
it('formats break- and continue-statements in for-loops correctly', () => {
const formattedCode = formatJavaScript(`for(var i in set)if(i%2===0)break;else continue;
function foo(){while(1){if (a)continue;test();}}`);
assert.strictEqual(formattedCode, `for (var i in set)
if (i % 2 === 0)
break;
else
continue;
function foo() {
while (1) {
if (a)
continue;
test();
}
}
`);
});
it('formats null-keyword correctly', () => {
const formattedCode = formatJavaScript('1||null;');
assert.strictEqual(formattedCode, `1 || null;
`);
});
it('formats exponential operator correctly', () => {
const formattedCode = formatJavaScript('2**3');
assert.strictEqual(formattedCode, `2 ** 3
`);
});
it('formats for-loops correctly', () => {
const formattedCode = formatJavaScript(
'for(var value of map)if (value.length%3===0)console.log(value);for(var key in myMap)print(key);for(var value of myMap)print(value);');
assert.strictEqual(formattedCode, `for (var value of map)
if (value.length % 3 === 0)
console.log(value);
for (var key in myMap)
print(key);
for (var value of myMap)
print(value);
`);
});
it('formats chained and nested if-statements correctly', () => {
const formattedCode = formatJavaScript(
'if(a%7===0)b=1;else if(a%9===1) b = 2;else if(a%5===3){b=a/2;b++;} else b= 3;{if (a>b){a();pretty();}else if (a+b)e();reset();}');
assert.strictEqual(formattedCode, `if (a % 7 === 0)
b = 1;
else if (a % 9 === 1)
b = 2;
else if (a % 5 === 3) {
b = a / 2;
b++;
} else
b = 3;
{
if (a > b) {
a();
pretty();
} else if (a + b)
e();
reset();
}
`);
});
it('formats try-catch statements correctly', () => {
const formattedCode = formatJavaScript('try{a(b());}catch(e){f()}finally{f();}');
assert.strictEqual(formattedCode, `try {
a(b());
} catch (e) {
f()
} finally {
f();
}
`);
});
it('formats object-spreads correctly', () => {
const formattedCode = formatJavaScript('const a = {a:4,...{a: 5,b: 42}};');
assert.strictEqual(formattedCode, `const a = {
a: 4,
...{
a: 5,
b: 42
}
};
`);
});
it('formats object destructuring correctly', () => {
const formattedCode = formatJavaScript('let{x,y}=getXYFromTouchOrPointer(e);var test = function({x,y}){foo(x,y);}');
assert.strictEqual(formattedCode, `let {x, y} = getXYFromTouchOrPointer(e);
var test = function({x, y}) {
foo(x, y);
}
`);
});
it('formats let declaration for "$" correctly', () => {
const formattedCode = formatJavaScript('let $=1;');
assert.strictEqual(formattedCode, 'let $ = 1;\n');
});
it('formats let declaration for "_" correctly', () => {
const formattedCode = formatJavaScript('let _=1;');
assert.strictEqual(formattedCode, 'let _ = 1;\n');
});
it('formats let declaration for unicode name correctly', () => {
const formattedCode = formatJavaScript('let \u00e1=1;');
assert.strictEqual(formattedCode, 'let \u00e1 = 1;\n');
});
it('formats const declaration for unicode name correctly', () => {
const formattedCode = formatJavaScript('const \u00e1=1;');
assert.strictEqual(formattedCode, 'const \u00e1 = 1;\n');
});
it('formats yield <number> correctly', () => {
const formattedCode = formatJavaScript('function *one() {yield 1;}');
assert.strictEqual(formattedCode, `function *one() {
yield 1;
}
`);
});
it('formats object-expressions correctly', () => {
const formattedCode = formatJavaScript(
'var mapping={original:[1,2,3],formatted:[],count:0};var obj={\'foo\':1,bar:"2",cat:{dog:\'1989\'}}');
assert.strictEqual(formattedCode, `var mapping = {
original: [1, 2, 3],
formatted: [],
count: 0
};
var obj = {
'foo': 1,
bar: "2",
cat: {
dog: '1989'
}
}
`);
});
it('formats block-statements correctly', () => {
const formattedCode = formatJavaScript('{ print(1); print(2); }');
assert.strictEqual(formattedCode, `{
print(1);
print(2);
}
`);
});
it('formats assignment expressions correctly', () => {
const formattedCode = formatJavaScript('var exp=\'a string\';c=+a+(0>a?b:0);c=(1);var a=(1);');
assert.strictEqual(formattedCode, `var exp = 'a string';
c = +a + (0 > a ? b : 0);
c = (1);
var a = (1);
`);
});
it('formats with-statements correctly', () => {
const formattedCode = formatJavaScript('with(obj)log(\'first\');with(nice){log(1);log(2);}done();');
assert.strictEqual(formattedCode, `with (obj)
log(\'first\');
with (nice) {
log(1);
log(2);
}
done();
`);
});
it('formats switch-statements correctly', () => {
const formattedCode = formatJavaScript(
'switch (a) { case 1, 3: log("odd");break;case 2:log("even");break;case 42:case 89: log(a);default:log("interesting");log(a);}log("done");');
assert.strictEqual(formattedCode, `switch (a) {
case 1, 3:
log("odd");
break;
case 2:
log("even");
break;
case 42:
case 89:
log(a);
default:
log("interesting");
log(a);
}
log("done");
`);
});
it('formats generator-expressions correctly', () => {
const formattedCode =
formatJavaScript('function *max(){var a=yield;var b=yield 10;if(a>b)return a;else return b;}');
assert.strictEqual(formattedCode, `function *max() {
var a = yield;
var b = yield 10;
if (a > b)
return a;
else
return b;
}
`);
});
it('formats block-comments correctly', () => {
const formattedCode = formatJavaScript(`/** this
* is
* block
* comment
*/
var a = 10;`);
assert.strictEqual(formattedCode, `/** this
* is
* block
* comment
*/
var a = 10;
`);
});
it('formats let-assignments correctly', () => {
const formattedCode = formatJavaScript('for(var i=0;i<names.length;++i){let name=names[i];let person=persons[i];}');
assert.strictEqual(formattedCode, `for (var i = 0; i < names.length; ++i) {
let name = names[i];
let person = persons[i];
}
`);
});
it('formats anonymous functions correctly', () => {
const formattedCode =
formatJavaScript(`setTimeout(function(){alert(1);},2000); function test(arg){console.log(arg);}test(a=>a+2);
var onClick = function() { console.log(\'click!\'); };console.log(\'done\');var onStart = function() { a(); }, onFinish = function() { b(); };
var onStart = function() {}, delay=1000, belay=document.activeElement;`);
assert.strictEqual(formattedCode, `setTimeout(function() {
alert(1);
}, 2000);
function test(arg) {
console.log(arg);
}
test(a => a + 2);
var onClick = function() {
console.log('click!');
};
console.log('done');
var onStart = function() {
a();
}
, onFinish = function() {
b();
};
var onStart = function() {}
, delay = 1000
, belay = document.activeElement;
`);
});
it('formats arrow functions correctly', () => {
const formattedCode1 = formatJavaScript('const double=x=>x*2;');
const formattedCode2 = formatJavaScript('const sum=(a,b)=>a+b;');
const formattedCode3 = formatJavaScript('const double=x=>{return x*2;}');
const formattedCode4 = formatJavaScript('const sum=(a,b,c)=>{const val=a+b+c;return val;}');
assert.strictEqual(formattedCode1, `const double = x => x * 2;
`);
assert.strictEqual(formattedCode2, `const sum = (a, b) => a + b;
`);
assert.strictEqual(formattedCode3, `const double = x => {
return x * 2;
}
`);
assert.strictEqual(formattedCode4, `const sum = (a, b, c) => {
const val = a + b + c;
return val;
}
`);
});
describe('formats files with comments', () => {
it('handles 1 leading comment correctly', () => {
const formattedCode = formatJavaScript(`// This is a starting comment
console.log('5');`);
assert.strictEqual(formattedCode, `// This is a starting comment
console.log('5');
`);
});
it('handles leading hashbang correctly', () => {
const formattedCode = formatJavaScript(`#! hashbang
{{{console.log(1)}}}`);
assert.strictEqual(formattedCode, `#! hashbang
{
{
{
console.log(1)
}
}
}
`);
});
it('handles 1 trailing comment correctly', () => {
const formattedCode = formatJavaScript('console.log(\'5\'); // This is a trailing comment');
assert.strictEqual(formattedCode, `console.log('5');
// This is a trailing comment
`);
});
it('handles 1 trailing comment on a new line correctly', () => {
const formattedCode = formatJavaScript(`console.log('5');
// This is a new line comment`);
assert.strictEqual(formattedCode, `console.log('5');
// This is a new line comment
`);
});
it('handles 2 leading comments', () => {
const formattedCode = formatJavaScript(`// This is a starting line comment
/* This is a starting block comment */
console.log('5');`);
assert.strictEqual(formattedCode, `// This is a starting line comment
/* This is a starting block comment */
console.log('5');
`);
});
it('handles 2 trailing comments correctly', () => {
const formattedCode = formatJavaScript(`console.log('5'); // This is a trailing comment same line
// This is a trailing new line comment`);
assert.strictEqual(formattedCode, `console.log('5');
// This is a trailing comment same line
// This is a trailing new line comment
`);
});
it('handles leading and trailing comments correctly', () => {
const formattedCode = formatJavaScript(`// This is a starting line comment
/* This is a starting block comment */
console.log('5'); // This is a trailing comment same line
// This is a trailing new line comment`);
assert.strictEqual(formattedCode, `// This is a starting line comment
/* This is a starting block comment */
console.log('5');
// This is a trailing comment same line
// This is a trailing new line comment
`);
});
it('handles a hashbang, leading and trailing comments correctly', () => {
const formattedCode = formatJavaScript(`#! hashbang
// This is a starting line comment
/* This is a starting block comment */
console.log('5'); // This is a trailing comment same line
// This is a trailing new line comment`);
assert.strictEqual(formattedCode, `#! hashbang
// This is a starting line comment
/* This is a starting block comment */
console.log('5');
// This is a trailing comment same line
// This is a trailing new line comment
`);
});
});
describe('formats files with classes', () => {
it('handles an empty class correctly', () => {
const formattedCode = formatJavaScript('class Test{}');
assert.strictEqual(formattedCode, `class Test {
}
`);
});
it('handles an empty constructor correctly', () => {
const formattedCode = formatJavaScript('class Test{constructor(){}}');
assert.strictEqual(formattedCode, `class Test {
constructor() {}
}
`);
});
it('handles a single method correctly', () => {
const formattedCode = formatJavaScript('class Test{constructor(){this.bar=10;}givemebar(){return this.bar;}}');
assert.strictEqual(formattedCode, `class Test {
constructor() {
this.bar = 10;
}
givemebar() {
return this.bar;
}
}
`);
});
it('handles extending a super class correctly', () => {
const formattedCode =
formatJavaScript('class Foo extends Bar{constructor(name){super(name);}getName(){return super.getName();}}');
assert.strictEqual(formattedCode, `class Foo extends Bar {
constructor(name) {
super(name);
}
getName() {
return super.getName();
}
}
`);
});
it('handles consecutive class declarations correctly', () => {
const formattedCode = formatJavaScript('class A{}class B extends A{constructor(){super();}}');
assert.strictEqual(formattedCode, `class A {
}
class B extends A {
constructor() {
super();
}
}
`);
});
it('handles static methods correctly', () => {
const formattedCode = formatJavaScript(
'class Employer{static count(){this._counter = (this._counter || 0) + 1; return this._counter;}}');
assert.strictEqual(formattedCode, `class Employer {
static count() {
this._counter = (this._counter || 0) + 1;
return this._counter;
}
}
`);
});
it('handles class expressions correctly', () => {
const formattedCode = formatJavaScript('new(class{constructor(){debugger}})');
assert.strictEqual(formattedCode, `new (class {
constructor() {
debugger
}
}
)
`);
});
});
describe('formats files with template literals', () => {
it('handles simple template literal correctly', () => {
const formattedCode = formatJavaScript('var foo = `bar`;');
assert.strictEqual(formattedCode, `var foo = \`bar\`;
`);
});
it('handles multi-line template literals correctly', () => {
const formattedCode = formatJavaScript(`var foo = \`this
bar\`;`);
assert.strictEqual(formattedCode, `var foo = \`this
bar\`;
`);
});
it('handles string substitution in template literals correctly', () => {
const formattedCode = formatJavaScript(`var a=\`I have \${credit+cash}$\`;
var a=\`\${name} has \${credit+cash}\${currency?currency:"$"}\`;`);
assert.strictEqual(formattedCode, `var a = \`I have \${credit + cash}$\`;
var a = \`\${name} has \${credit + cash}\${currency ? currency : "$"}\`;
`);
});
it('handles tagged template literals correctly', () => {
const formattedCode = formatJavaScript('escapeHtml`<div class=${classnName} width=${a+b}/>`;');
assert.strictEqual(formattedCode, `escapeHtml\`<div class=\${classnName} width=\${a + b}/>\`;
`);
});
});
it('removes consecutive new lines', () => {
const formattedCode = formatJavaScript(`a();
b();`);
assert.strictEqual(formattedCode, `a();
b();
`);
});
it('formats expressions in parentheses correctly', () => {
const formattedCode = formatJavaScript('if((a))((b));else (c);');
assert.strictEqual(formattedCode, `if ((a))
((b));
else
(c);
`);
});
it('formats obfuscated code', () => {
const formattedCode = formatJavaScript(`function formatted1()
{
var variable1 = 0;
}
function withComments()
{
// comment
return "functionWithComments";
}
try{onmessage=function(event){var source=event.data;var formattedSource=beautify(source);var mapping=buildMapping(source,formattedSource);postMessage({formattedSource:formattedSource,mapping:mapping})};function beautify(source){var ast=parse.parse(source);var beautifyOptions=
{indent_level:4,indent_start:0,quote_keys:false,space_colon:false};return process.gen_code(ast,beautifyOptions)}function buildMapping(source,formattedSource){var mapping={original:[],formatted:[]};var lastPosition=0;var regexp=/(^|[^\\])\b((?=\D)[\$\.\w]+)\b/g;while(true)
{var match=regexp.exec(formattedSource);if(!match)break;var position=source.indexOf(match[2],lastPosition);if(position===-1)throw"No match found in original source for "+match[2];mapping.original.push(position);mapping.formatted.push(match.index+match[1].length);
lastPosition=position+match[2].length}return mapping}function require(){return parse}var exports={};importScripts("UglifyJS/parse-js.js");var parse=exports;var exports={};importScripts("UglifyJS/process.js");var process=exports;}catch(e){}
function formatted2()
{
var variable2 = 0;
}`);
assert.strictEqual(formattedCode, `function formatted1() {
var variable1 = 0;
}
function withComments() {
// comment
return "functionWithComments";
}
try {
onmessage = function(event) {
var source = event.data;
var formattedSource = beautify(source);
var mapping = buildMapping(source, formattedSource);
postMessage({
formattedSource: formattedSource,
mapping: mapping
})
}
;
function beautify(source) {
var ast = parse.parse(source);
var beautifyOptions = {
indent_level: 4,
indent_start: 0,
quote_keys: false,
space_colon: false
};
return process.gen_code(ast, beautifyOptions)
}
function buildMapping(source, formattedSource) {
var mapping = {
original: [],
formatted: []
};
var lastPosition = 0;
var regexp = /(^|[^\\])\b((?=D)[$.w]+)\b/g;
while (true) {
var match = regexp.exec(formattedSource);
if (!match)
break;
var position = source.indexOf(match[2], lastPosition);
if (position === -1)
throw "No match found in original source for " + match[2];
mapping.original.push(position);
mapping.formatted.push(match.index + match[1].length);
lastPosition = position + match[2].length
}
return mapping
}
function require() {
return parse
}
var exports = {};
importScripts("UglifyJS/parse-js.js");
var parse = exports;
var exports = {};
importScripts("UglifyJS/process.js");
var process = exports;
} catch (e) {}
function formatted2() {
var variable2 = 0;
}
`);
});
it('formats functions using `import.meta.url` correctly', () => {
const formattedCode = formatJavaScript('function foo(){console.log(import.meta.url);}');
assert.strictEqual(formattedCode, `function foo() {
console.log(import.meta.url);
}
`);
});
it('formats class fields correctly', () => {
const formattedCode = formatJavaScript('class Clazz {map=new Map();someMethod(){console.log(42);}map2=new Map();}');
assert.strictEqual(formattedCode, `class Clazz {
map = new Map();
someMethod() {
console.log(42);
}
map2 = new Map();
}
`);
});
it('formats template literals correctly', () => {
const formattedCode = formatJavaScript('`foo${bar}foo${bar}`');
assert.strictEqual(formattedCode, '`foo${bar}foo${bar}`\n');
});
it('formats template literals with nested JS expressions correctly', () => {
const formattedCode = formatJavaScript('`${function(){let a}}`');
assert.strictEqual(
formattedCode,
'`${function() {\n' +
' let a\n' +
'}\n' +
'}`\n');
});
it('formats methods on literals correctly', () => {
const formattedCode = formatJavaScript('num=1 .toString();str="abc" . toUpperCase();');
assert.strictEqual(formattedCode, `num = 1 .toString();
str = "abc".toUpperCase();
`);
});
});