chrome-devtools-frontend
Version:
Chrome DevTools UI
180 lines (151 loc) • 8.3 kB
text/typescript
// Copyright 2022 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';
const mapping = new Map<string, string|null>([
['varX', 'x'],
['varY', 'y'],
['varZ', 'z'],
['this', 'this_1'],
['rename_to_this', 'this'],
['rename_to_arguments', 'arguments'],
['varQ', null],
]);
function substitute(expression: string): string {
return FormatterWorker.Substitute.substituteExpression(expression, mapping);
}
describe('Substitute', () => {
it('Preserves unrelated variable', () => {
assert.strictEqual(substitute('x'), 'x');
});
it('Preserves `import.meta`', () => {
assert.strictEqual(substitute('import.meta'), 'import.meta');
});
it('Substitutes single variable', () => {
assert.strictEqual(substitute('varY'), 'y');
});
it('Substitutes await', () => {
assert.strictEqual(substitute('await varX'), 'await x');
});
it('Substitutes array elements', () => {
assert.strictEqual(substitute('[0, varY, varZ]'), '[0, y, z]');
assert.strictEqual(substitute('[0, varY, ...varZ]'), '[0, y, ...z]');
});
it('Substitutes binary expressions, assignments, sequences', () => {
assert.strictEqual(substitute('varX + varY'), 'x + y');
assert.strictEqual(substitute('varX, varY'), 'x, y');
assert.strictEqual(substitute('varX = varZ'), 'x = z');
assert.strictEqual(substitute('varX += varZ'), 'x += z');
});
it('Substitutes arrow functions', () => {
assert.strictEqual(substitute('(a) => a + varX'), '(a) => a + x');
assert.strictEqual(substitute('(varX) => a + varX'), '(varX) => a + varX');
// Capture avoiding substitution.
assert.strictEqual(substitute('(x) => x + varX'), '(x_1) => x_1 + x');
assert.strictEqual(substitute('(x) => (x) => x + varX'), '(x_2) => (x_1) => x_1 + x');
assert.strictEqual(substitute('(x) => (x_1) => x + varX'), '(x_2) => (x_1) => x_2 + x');
});
it('Substitutes in blocks', () => {
assert.strictEqual(substitute('{console.log(varY)}'), '{console.log(y)}');
assert.strictEqual(substitute('{var y; console.log(varY + y)}'), '{var y_1; console.log(y + y_1)}');
// Technically it would be correct to rename all instances of y to y_1,
// but let us check we treat the let variables independently.
assert.strictEqual(
substitute('var y; {let y; console.log(varY + y)}; y'), 'var y_2; {let y_1; console.log(y + y_1)}; y_2');
// Var hoisting out of blocks.
assert.strictEqual(substitute('{var y; console.log(varY + y)}; y'), '{var y_1; console.log(y + y_1)}; y_1');
assert.strictEqual(
substitute('{let varY; console.log(varX + varY)}; varY'), '{let varY; console.log(x + varY)}; y');
assert.strictEqual(
substitute('{const varY = 1; console.log(varX + varY)}; varY'), '{const varY = 1; console.log(x + varY)}; y');
assert.strictEqual(
substitute('{var varY; console.log(varX + varY)}; varY'), '{var varY; console.log(x + varY)}; varY');
});
it('Substitutes in member expressions', () => {
assert.strictEqual(substitute('varY.varX?.varZ'), 'y.varX?.varZ');
});
it('Substitutes in conditionals', () => {
assert.strictEqual(substitute('varX ? varY : varZ'), 'x ? y : z');
assert.strictEqual(substitute('if (varX) varY; else varZ;'), 'if (x) y; else z;');
});
it('Substitutes in loops', () => {
assert.strictEqual(substitute('do { console.log(varX) } while (varY);'), 'do { console.log(x) } while (y);');
assert.strictEqual(substitute('for (varX in varY) console.log(varZ);'), 'for (x in y) console.log(z);');
assert.strictEqual(
substitute('for (let varX in varY) console.log(varZ + varX);'), 'for (let varX in y) console.log(z + varX);');
assert.strictEqual(
substitute('for (let varX of varY) console.log(varZ + varX);'), 'for (let varX of y) console.log(z + varX);');
assert.strictEqual(
substitute('for (let x of varY) console.log(x + varX);'), 'for (let x_1 of y) console.log(x_1 + x);');
assert.strictEqual(
substitute('for (varX = 1; varX < 100; varX++) console.log(varX);'),
'for (x = 1; x < 100; x++) console.log(x);');
assert.strictEqual(
substitute('for (var varX = 1; varX < 100; varX++) console.log(varX);'),
'for (var varX = 1; varX < 100; varX++) console.log(varX);');
assert.strictEqual(substitute('while (varX) { varY; }'), 'while (x) { y; }');
});
it('Substitutes in functions', () => {
assert.strictEqual(substitute('function f(a) { a + varX; }'), 'function f(a) { a + x; }');
assert.strictEqual(substitute('function f(varX) { a + varX; }'), 'function f(varX) { a + varX; }');
// Capture avoiding substitution.
assert.strictEqual(
substitute('function f(x) { return (x_1) => x + varX; }'), 'function f(x_2) { return (x_1) => x_2 + x; }');
assert.strictEqual(substitute('function f(...varX) { varX[0] }'), 'function f(...varX) { varX[0] }');
assert.strictEqual(substitute('function f(...x) { varX[0] }'), 'function f(...x_1) { x[0] }');
assert.strictEqual(substitute('function f() { this; }; this'), 'function f() { this; }; this_1');
});
it('Substitutes in object literals', () => {
assert.strictEqual(substitute('{ varX: varX }'), '{ varX: x }');
assert.strictEqual(substitute('({ [varY]: varX })'), '({ [y]: x })');
assert.strictEqual(substitute('({ ...varX })'), '({ ...x })');
assert.strictEqual(substitute('({ varX })'), '({ varX: x })');
});
it('Substitutes in switch', () => {
assert.strictEqual(substitute('switch (varX) { case varY: varZ; }'), 'switch (x) { case y: z; }');
assert.strictEqual(
substitute('switch (varY) { case varZ: varX; case 2: let x = 1; }'),
'switch (y) { case z: x; case 2: let x_1 = 1; }');
});
it('Substitutes in generators', () => {
assert.strictEqual(substitute('function* f(x) { yield varX + x; }'), 'function* f(x_1) { yield x + x_1; }');
});
it('Substitutes in template literals', () => {
assert.strictEqual(substitute('`${varX}`'), '`${x}`');
assert.strictEqual(substitute('varY`${varX} varZ`'), 'y`${x} varZ`');
});
it('Substitutes in patterns', () => {
assert.strictEqual(substitute('[varX, varY] = [varZ, 1]'), '[x, y] = [z, 1]');
assert.strictEqual(substitute('[varX[varY]] = [3]'), '[x[y]] = [3]');
assert.strictEqual(substitute('[varX.varY] = [3]'), '[x.varY] = [3]');
assert.strictEqual(substitute('({varX: varZ} = {varX: 3})'), '({varX: z} = {varX: 3})');
assert.strictEqual(substitute('[varX = varY] = []'), '[x = y] = []');
assert.strictEqual(substitute('({varX: varZ.x} = {varX: 3})'), '({varX: z.x} = {varX: 3})');
assert.strictEqual(substitute('let [varX, varY] = [varZ, 1]; varX'), 'let [varX, varY] = [z, 1]; varX');
assert.strictEqual(substitute('let [varX = varY] = []'), 'let [varX = y] = []');
assert.strictEqual(substitute('var {varX: varZ} = {varX: 3}; varZ'), 'var {varX: varZ} = {varX: 3}; varZ');
// Avoid captures.
assert.strictEqual(substitute('let [y, x] = [varX, varY]; x'), 'let [y_1, x_1] = [x, y]; x_1');
assert.strictEqual(substitute('var {varX: x} = {varX}; x'), 'var {varX: x_1} = {varX: x}; x_1');
});
it('Does not substitute in strings', () => {
assert.strictEqual(substitute('\'varX\''), '\'varX\'');
assert.strictEqual(substitute('\"varX\"'), '\"varX\"');
assert.strictEqual(substitute('\`varX\`'), '\`varX\`');
});
it('Does not substitute in comments', () => {
assert.strictEqual(substitute('/* varX */'), '/* varX */');
assert.strictEqual(substitute('// varX'), '// varX');
});
it('Throws on to-be-captured "this" and "arguments" bound by a function', () => {
assert.throws(() => substitute('function f() { return rename_to_this; }'), 'Cannot avoid capture of \'this\'');
assert.throws(
() => substitute('function f() { return rename_to_arguments; }'), 'Cannot avoid capture of \'arguments\'');
});
it('Throws on parse error', () => {
assert.throws(() => substitute('('), SyntaxError);
});
it('Throws if the renamed variable is unavailable', () => {
assert.throws(() => substitute('varQ'), Error);
});
});