Home Reference Source Repository

src/mangle-identifier.js

/**
Convert a name to a valid JavaScript identifier.
@param {String} name Can be any string.
@return {String}
*/
export default function mangleIdentifier(name) {
	return forbiddenNames.has(name) ?
		`_${name}` :
		name.replace(/[^a-zA-Z0-9$_]/g, _ => `_${_.charCodeAt(0)}`)
}

/** `false` iff `name` is a valid JavaScript identifier. */
export function needsMangle(name) {
	return forbiddenNames.has(name) || !propertyNameOk(name)
}

/** `true` iff `name` can be used as a property name using dot syntax (`a.b`). */
export function propertyNameOk(name) {
	return name.search(/[^a-zA-Z0-9$_]/) === -1
}

/** Undoes {@link mangleIdentifier}. */
export function unmangle(name) {
	if (name[0] === '_') {
		const rest = name.slice(1)
		if (forbiddenNames.has(rest))
			return rest
	}
	return name.replace(/_\d+/g, match => {
		const charCode = match.slice(1)
		const n = Number.parseInt(charCode)
		const ch = String.fromCharCode(n)
		return ch === '\0' ? match : ch
	})
}

/** Set of JavaScript keywords. */
export const forbiddenNames = new Set([
	'abstract',
	'arguments',
	'boolean',
	'break',
	'byte',
	'case',
	'catch',
	'char',
	'class',
	'comment',
	'const',
	'continue',
	'debugger',
	'default',
	'delete',
	'do',
	'double',
	'else',
	'enum',
	'eval',
	'export',
	'extends',
	'false',
	'final',
	'finally',
	'float',
	'for',
	'function',
	'function*',
	'goto',
	'if',
	'implements',
	'import',
	'in',
	'instanceOf',
	'int',
	'interface',
	'label',
	'long',
	'module',
	'native',
	'new',
	'null',
	'package',
	'private',
	'protected',
	'public',
	'return',
	'short',
	'static',
	'super',
	'switch',
	'synchronized',
	'this',
	'throw',
	'throws',
	'transient',
	'true',
	'try',
	'typeof',
	'var',
	'void',
	'while',
	'with',
	'yield'
])