"use strict"; const assert = require("assert"); const acceptableFirstCharacters = /^[a-zA-Z_]$/; const acceptableSubsequentCharacters = /^[0-9a-zA-Z_]$/; const reservedWords = new Set([ "import", "const", "let", "var" ]); // This function deterministically and statelessly translates a name such that it is guaranteed to be valid in all identifier positions in JS. // TODO: This can probably be made more performant... function mangleCharacter(character) { return (character === "$") ? "$$" : `$${character.codePointAt(0)}`; } module.exports = function mangleName(name) { assert(name.length > 0); if (reservedWords.has(name)) { return "$" + name; } else if (name.startsWith("$")) { // FIXME: Tag an identifier of this type with an internal property instead return name; } else { let completedFirstCharacter = false; return Array.from(name) .map((character) => { if (!completedFirstCharacter) { completedFirstCharacter = true; return (acceptableFirstCharacters.test(character)) ? character : mangleCharacter(character); } else { return (acceptableSubsequentCharacters.test(character)) ? character : mangleCharacter(character); } }) .join(""); } };