Skip to content

Commit

Permalink
Handle functions/classes with name reserved word [fix]
Browse files Browse the repository at this point in the history
Fixes #52.
  • Loading branch information
overlookmotel committed Mar 23, 2021
1 parent a0b2785 commit ec88340
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 21 deletions.
6 changes: 3 additions & 3 deletions lib/serialize/parseFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const {identifierIsVariable, replaceWith} = require('../shared/functions.js'),
EVAL_VAR_NAME_BODY, EVAL_COMMENT
} = require('../shared/constants.js'),
{createUnmangledVarNameTransform} = require('./varNames.js'),
{isJsIdentifier, isNumberKey} = require('./utils.js'),
{isJsIdentifier, isReservedWord, isNumberKey} = require('./utils.js'),
{transpiledFiles} = require('../internal.js'),
assertBug = require('../shared/assertBug.js');

Expand Down Expand Up @@ -627,7 +627,7 @@ module.exports = function parseFunction(
const originalName = node.id.name;
if (name !== originalName) {
if (
name && isJsIdentifier(name) && !containsEval
name && isJsIdentifier(name) && !isReservedWord(name) && !containsEval
&& !globalVarNames.has(name) && !functionNames.has(name)
) {
// Rename all references to name
Expand All @@ -644,7 +644,7 @@ module.exports = function parseFunction(
}
}
}
} else if (name && (!isJsIdentifier(name) || globalVarNames.has(name))) {
} else if (name && (!isJsIdentifier(name) || isReservedWord(name) || globalVarNames.has(name))) {
name = '';
}

Expand Down
16 changes: 15 additions & 1 deletion lib/serialize/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
'use strict';

// Modules
const t = require('@babel/types');
const checkReservedWord = require('reserved-words').check,
t = require('@babel/types');

// Imports
const assertBug = require('../shared/assertBug.js');
Expand All @@ -15,6 +16,7 @@ const assertBug = require('../shared/assertBug.js');

module.exports = {
isJsIdentifier,
isReservedWord,
isNumberKey,
isIntegerKey,
toJsIdentifier,
Expand All @@ -37,6 +39,18 @@ function isJsIdentifier(name) {

const JS_ID_REGEX = /^[A-Za-z$_][A-Za-z0-9$_]*$/u;

/**
* Determine if var name is a JS reserved word e.g. 'break', 'class'.
* 'arguments' + 'eval' are additionally treated as a reserved words
* as they cannot be used as var names in strict mode.
* @param {string} name - Variable name
* @returns {boolean} - `true` if reserved word, `false` if not
*/
function isReservedWord(name) {
if (name === 'arguments' || name === 'eval') return true;
return checkReservedWord(name, 'es6', true);
}

/**
* Determine if string is valid number object key.
* e.g. `{0: 'a'}`, `{22: 'a'}`
Expand Down
20 changes: 3 additions & 17 deletions lib/serialize/varNames.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

'use strict';

// Modules
const checkReservedWord = require('reserved-words').check;
// Imports
const {isReservedWord} = require('./utils.js');

// Exports

Expand All @@ -29,12 +29,7 @@ class VarNameFactory {
if (reservedNames && reservedNames.has(name)) return true;

// Check if JS reserved word
if (isReservedWord(name)) return true;

// Treat 'arguments' as a reserved word (NB 'this' is already caught by `checkReservedWord()`)
if (name === 'arguments') return true;

return false;
return isReservedWord(name);
}
}

Expand Down Expand Up @@ -131,12 +126,3 @@ module.exports = {
MangledVarNameFactory,
UnmangledVarNameFactory
};

/**
* Determine if var name is a JS reserved word e.g. 'break', 'class'.
* @param {string} name - Variable name
* @returns {boolean} - `true` if reserved word, `false` if not
*/
function isReservedWord(name) {
return checkReservedWord(name, 'es6', true);
}
64 changes: 64 additions & 0 deletions test/classes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2994,6 +2994,70 @@ describe('Classes', () => {
});
});

describe('name maintained where', () => {
itSerializes('unnamed class as object property', {
in: () => ({a: (0, class {})}),
out: '{a:(0,class{})}',
validate(obj) {
expect(obj).toBeObject();
expect(obj).toContainAllKeys(['a']);
const Klass = obj.a;
expect(Klass).toBeFunction();
expect(Klass.name).toBe('');
}
});

itSerializes('not valid JS identifier', {
in: () => ({'0a': class {}}['0a']),
out: 'Object.defineProperties(class{},{name:{value:"0a",configurable:true}})',
validate(Klass) {
expect(Klass).toHaveOwnPropertyNames(['length', 'prototype', 'name']);
expect(Klass.name).toBe('0a');
expect(Klass).toHaveDescriptorModifiersFor('name', false, false, true);
}
});

itSerializes('reserved word', {
in: () => ({export: class {}}.export),
out: 'Object.defineProperties(class{},{name:{value:"export",configurable:true}})',
validate(Klass) {
expect(Klass).toHaveOwnPropertyNames(['length', 'prototype', 'name']);
expect(Klass.name).toBe('export');
expect(Klass).toHaveDescriptorModifiersFor('name', false, false, true);
}
});

itSerializes('arguments', {
in: () => ({arguments: class {}}.arguments),
out: 'Object.defineProperties(class{},{name:{value:"arguments",configurable:true}})',
validate(Klass) {
expect(Klass).toHaveOwnPropertyNames(['length', 'prototype', 'name']);
expect(Klass.name).toBe('arguments');
expect(Klass).toHaveDescriptorModifiersFor('name', false, false, true);
}
});

itSerializes('eval', {
in: () => ({eval: class {}}.eval),
out: 'Object.defineProperties(class{},{name:{value:"eval",configurable:true}})',
validate(Klass) {
expect(Klass).toHaveOwnPropertyNames(['length', 'prototype', 'name']);
expect(Klass.name).toBe('eval');
expect(Klass).toHaveDescriptorModifiersFor('name', false, false, true);
}
});

itSerializes('not valid JS identifier with static method', {
in: () => ({'0a': class {static foo() {}}}['0a']),
out: 'Object.defineProperties(class{},{foo:{value:{foo(){}}.foo,writable:true,configurable:true},name:{value:"0a",configurable:true}})',
validate(Klass) {
expectClassToHaveOwnPropertyNames(Klass, ['length', 'prototype', 'foo', 'name']);
expect(Klass.name).toBe('0a');
expect(Klass).toHaveDescriptorModifiersFor('name', false, false, true);
}
});
});

describe('classes nested in functions left untouched', () => {
describe('not extending another class', () => {
itSerializes('empty', {
Expand Down
36 changes: 36 additions & 0 deletions test/functions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4695,6 +4695,42 @@ describe('Functions', () => {
}
});

itSerializes('reserved word', {
in: () => ({export: function() {}}.export), // eslint-disable-line object-shorthand
out: 'Object.defineProperties(function(){},{name:{value:"export"}})',
validate(fn) {
expect(
Object.getOwnPropertyNames(fn).filter(key => key !== 'arguments' && key !== 'caller')
).toEqual(['length', 'name', 'prototype']);
expect(fn.name).toBe('export');
expect(fn).toHaveDescriptorModifiersFor('name', false, false, true);
}
});

itSerializes('arguments', {
in: () => ({arguments: function() {}}.arguments), // eslint-disable-line object-shorthand
out: 'Object.defineProperties(function(){},{name:{value:"arguments"}})',
validate(fn) {
expect(
Object.getOwnPropertyNames(fn).filter(key => key !== 'arguments' && key !== 'caller')
).toEqual(['length', 'name', 'prototype']);
expect(fn.name).toBe('arguments');
expect(fn).toHaveDescriptorModifiersFor('name', false, false, true);
}
});

itSerializes('eval', {
in: () => ({eval: function() {}}.eval), // eslint-disable-line object-shorthand
out: 'Object.defineProperties(function(){},{name:{value:"eval"}})',
validate(fn) {
expect(
Object.getOwnPropertyNames(fn).filter(key => key !== 'arguments' && key !== 'caller')
).toEqual(['length', 'name', 'prototype']);
expect(fn.name).toBe('eval');
expect(fn).toHaveDescriptorModifiersFor('name', false, false, true);
}
});

describe('descriptor altered', () => {
itSerializes('value altered', {
in() {
Expand Down

0 comments on commit ec88340

Please sign in to comment.