[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\n# cancel in-progress runs on new commits to same PR (github.event.number)\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.number || github.sha }}\n  cancel-in-progress: true\n\njobs:\n  Tests:\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 30\n    strategy:\n      fail-fast: false\n      matrix:\n        node-version: [16]\n        os: [ubuntu-latest]\n    steps:\n      - run: git config --global core.autocrlf false\n      - uses: actions/checkout@v3\n      - uses: pnpm/action-setup@v2.2.2\n      - uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: pnpm\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm test\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\n/types\ndist\ntest/**/_actual.*\ntest/fuzz"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n\t\"useTabs\": true,\n\t\"singleQuote\": true,\n\t\"trailingComma\": \"none\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# code-red changelog\n\n## 1.0.4\n\n- Add `types` to `pkg.exports` ([#83](https://github.com/Rich-Harris/code-red/pull/83))\n\n## 1.0.3\n\n- Fix inline comments in function arguments ([#79](https://github.com/Rich-Harris/code-red/pull/79))\n\n## 1.0.2\n\n- Use `dts-buddy` to generate type declarations ([#78](https://github.com/Rich-Harris/code-red/pull/78))\n\n## 1.0.1\n\n- Remove `dist` directory from package ([#77](https://github.com/Rich-Harris/code-red/pull/77))\n- Fix position of inline comments ([#76](https://github.com/Rich-Harris/code-red/pull/76))\n\n## 1.0.0\n\n- Breaking: remove CJS build, remove `main` and `module` ([#75](https://github.com/Rich-Harris/code-red/pull/75))\n\n## 0.2.7\n\n- Update to `es2022` ([#73](https://github.com/Rich-Harris/code-red/pull/73))\n\n## 0.2.6\n\n- Replace `sourcemap-codec` with `@jridgewell/sourcemap-codec` ([#74](https://github.com/Rich-Harris/code-red/pull/74))\n\n## 0.2.5\n\n- Prevent stack overflow with very large ASTs ([#71](https://github.com/Rich-Harris/code-red/pull/71))\n\n## 0.2.4\n\n- Fix output for arrow functions where body is an object destructuring assignment ([#70](https://github.com/Rich-Harris/code-red/pull/70))\n\n## 0.2.3\n\n- Fix output with comments within parenthesized `return` statement ([#36](https://github.com/Rich-Harris/code-red/issues/36))\n- Fix output for identifier at root of AST ([#37](https://github.com/Rich-Harris/code-red/issues/37))\n- Fix output for statements with empty bodies ([#65](https://github.com/Rich-Harris/code-red/issues/65))\n\n## 0.2.2\n\n- Update dependencies ([#63](https://github.com/Rich-Harris/code-red/pull/63))\n\n## 0.2.1\n\n- Fix handling of string literal raw values ([#61](https://github.com/Rich-Harris/code-red/pull/61))\n\n## 0.2.0\n\n- Rewrite in JavaScript\n\n## 0.1.7\n\n- Include dependencies ([#56](https://github.com/Rich-Harris/code-red/pull/56))\n- Add `sourceMapEncodeMappings` option ([#51](https://github.com/Rich-Harris/code-red/pull/51))\n\n## 0.1.6\n\n- Only use shorthand for non-computed properties ([#58](https://github.com/Rich-Harris/code-red/pull/58))\n\n## 0.1.5\n\n- Use `node.raw` where possible ([#55](https://github.com/Rich-Harris/code-red/pull/55))\n- Support BigInt (([#54](https://github.com/Rich-Harris/code-red/issues/54)))\n\n## 0.1.4\n\n- Fix rendering of nullish coalescing operator alongside other logical operators ([#52](https://github.com/Rich-Harris/code-red/issues/52))\n\n## 0.1.3\n\n- Support nullish coalescing operator ([#42](https://github.com/Rich-Harris/code-red/issues/42))\n- Support optional chaining ([#43](https://github.com/Rich-Harris/code-red/issues/43))\n\n## 0.1.2\n\n- Don't crash when using an arrow function as a statement ([#38](https://github.com/Rich-Harris/code-red/issues/38))\n\n## 0.1.1\n\n- Wrap arrow functions in parens as appropriate ([#31](https://github.com/Rich-Harris/code-red/issues/31))\n- Throw on invalid expressions ([#31](https://github.com/Rich-Harris/code-red/issues/31))\n\n## 0.1.0\n\n- Throw on unhandled sigils ([#30](https://github.com/Rich-Harris/code-red/pull/30))\n\n## 0.0.32\n\n- Prevent syntax errors when combining comments ([#28](https://github.com/Rich-Harris/code-red/issues/28))\n\n## 0.0.31\n\n- Expose wrapped versions of Acorn methods to facilitate comment preservation ([#26](https://github.com/Rich-Harris/code-red/issues/26))\n\n## 0.0.30\n\n- Wrap `await` argument in parens if necessary ([#24](https://github.com/Rich-Harris/code-red/issues/24))\n\n## 0.0.29\n\n- Handle sigils in comments ([#21](https://github.com/Rich-Harris/code-red/issues/21))\n\n## 0.0.28\n\n- Add `toString` and `toUrl` methods on sourcemap objects ([#22](https://github.com/Rich-Harris/code-red/pull/22))\n\n## 0.0.27\n\n- Handle parenthesized expressions\n\n## 0.0.26\n\n- Always replace comment values ([#20](https://github.com/Rich-Harris/code-red/pull/20))\n\n## 0.0.25\n\n- Fix async/generator functions in object methods ([#18](https://github.com/Rich-Harris/code-red/issues/18))\n\n## 0.0.24\n\n- Determine shorthand eligibility after stringification ([#17](https://github.com/Rich-Harris/code-red/pull/17))\n\n## 0.0.23\n\n- Unescape sigils in literals ([#16](https://github.com/Rich-Harris/code-red/pull/16))\n\n## 0.0.22\n\n- Prevent erroneous object shorthand when key is an identifier ([#14](https://github.com/Rich-Harris/code-red/issues/14))\n\n## 0.0.21\n\n- Deconflict #-identifiers in function names ([#10](https://github.com/Rich-Harris/code-red/issues/10))\n- Fix object expression with string literal key matching value ([#9](https://github.com/Rich-Harris/code-red/pull/9))\n\n## 0.0.20\n\n- Update deps\n\n## 0.0.19\n\n- Attach comments\n\n## 0.0.18\n\n- Handle mixed named/default imports ([#3](https://github.com/Rich-Harris/code-red/issues/3))\n- Update dependencies ([#4](https://github.com/Rich-Harris/code-red/issues/4))\n\n## 0.0.17\n\n- Fixes and additions\n\n## 0.0.16\n\n- Improve some aspects of generated code\n\n## 0.0.15\n\n- Flatten patterns\n\n## 0.0.13-14\n\n- Sourcemaps\n\n## 0.0.12\n\n- Flatten object properties\n\n## 0.0.11\n\n- Handle deconfliction edge case\n\n## 0.0.10\n\n- Tweak some TypeScript stuff\n\n## 0.0.9\n\n- Adopt estree types\n- Add a `p` function for creating properties\n\n## 0.0.8\n\n- Allow strings to be treated as identifiers\n\n## 0.0.7\n\n- Various\n\n## 0.0.6\n\n- Flatten arguments and parameters\n\n## 0.0.5\n\n- Omit missing statements\n- Flatten arrays of statements\n\n## 0.0.4\n\n- Use fork of astring\n\n## 0.0.3\n\n- Allow return outside function\n- Print code on syntax error\n\n## 0.0.2\n\n- Support `@`-prefixed names (replaceable globals)\n- Support `#`-prefixed names (automatically deconflicted)\n\n## 0.0.1\n\n- First experimental release\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2019 Rich Harris\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# code-red\n\nExperimental toolkit for writing x-to-JavaScript compilers. It is used in [Svelte](https://svelte.dev).\n\n\n## API\n\nThe `code-red` package exposes three core functions — `b`, `x` and `print`.\n\n`b` and `x` take a template literal and return an [ESTree](https://github.com/estree/estree) program body, or a single node:\n\n```js\nimport { b, x } from 'code-red';\n\nconst expression = x`i + j`;\n\nassert.equal(expression.type, 'AssignmentExpression');\nassert.equal(expression.operator, '+');\nassert.equal(expression.left.name, 'i');\nassert.equal(expression.right.name, 'j');\n\nconst body = b`\n\tconst i = 1;\n\tconst j = 2;\n\tconst k = i + j;\n`;\n\nassert.equal(body.length, 3);\nassert.equal(body[0].type, 'VariableDeclaration');\n```\n\nExpressions in template literals correspond to replacement nodes — so you could express the above like so:\n\n```js\nconst i = x`i`;\nconst j = x`j`;\nconst expression = x`${i} + ${j}`;\n\nconst body = b`\n\tconst ${i} = 1;\n\tconst ${j} = 2;\n\tconst k = ${expression};\n`;\n```\n\nThe `print` function takes a node and turns it into a `{code, map}` object:\n\n```js\nconst add = x`\n\tfunction add(${i}, ${j}) {\n\t\treturn ${expression};\n\t}\n`;\n\nprint(add).code;\n/*\nfunction add(i, j) {\n\treturn i + j;\n}\n*/\n\ni.name = 'foo';\nj.name = 'bar';\n\nprint(add).code;\n/*\nfunction add(foo, bar) {\n\treturn foo + bar;\n}\n*/\n```\n\n## Prefixes\n\n### `@`-prefixed names (replaceable globals)\n\nSo that you can use globals in your code. In Svelte, we use this to insert utility functions.\n\n```js\n// input\nimport { x } from 'code-red';\nx`@foo(bar)`\n\n// output\nFOO(bar)\n```\n\n### `#`-prefixed names (automatically deconflicted names)\n\nSo that you can insert variables in your code without worrying if they clash with existing variable names.\n\n\n`bar` used in user code and in inserted code gets a `$1` suffix:\n\n```js\n// input\nimport { x } from 'code-red';\nx`\nfunction foo(#bar) {\n\treturn #bar * bar;\n}`;\n\n// output\nfunction foo(bar$1) {\n\treturn bar$1 * bar;\n}\n```\n\nWithout conflicts, no `$1` suffix:\n\n```js\n// input\nimport { b } from 'code-red';\nb`const foo = #bar => #bar * 2`;\n\n// output\nconst foo = bar => bar * 2;\n```\n\n## Optimiser\n\nTODO add an optimiser that e.g. collapses consecutive identical if blocks\n\n\n## Compiler\n\nTODO add a `code-red/compiler` module that replaces template literals with the nodes they evaluate to, so that there's nothing to parse at runtime.\n\n\n## Sourcemaps\n\nTODO support source mappings for inserted nodes with location information.\n\n\n## License\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"code-red\",\n\t\"description\": \"code-red\",\n\t\"version\": \"1.0.4\",\n\t\"repository\": \"Rich-Harris/code-red\",\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"types\": \"./types/index.d.ts\",\n\t\t\t\"import\": \"./src/index.js\"\n\t\t}\n\t},\n\t\"type\": \"module\",\n\t\"types\": \"types/index.d.ts\",\n\t\"files\": [\n\t\t\"src\",\n\t\t\"types\"\n\t],\n\t\"scripts\": {\n\t\t\"build\": \"dts-buddy\",\n\t\t\"test\": \"uvu test test.js\",\n\t\t\"prepublishOnly\": \"npm test && npm run build\",\n\t\t\"repl\": \"node -e \\\"import('./src/index.js').then(mod => { x = mod.x; b = mod.b; print = mod.print });\\\" -i\"\n\t},\n\t\"license\": \"MIT\",\n\t\"dependencies\": {\n\t\t\"@jridgewell/sourcemap-codec\": \"^1.4.15\",\n\t\t\"@types/estree\": \"^1.0.1\",\n\t\t\"acorn\": \"^8.10.0\",\n\t\t\"estree-walker\": \"^3.0.3\",\n\t\t\"periscopic\": \"^3.1.0\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@types/node\": \"^20.4.10\",\n\t\t\"dts-buddy\": \"^0.1.9\",\n\t\t\"eslump\": \"^3.0.0\",\n\t\t\"uvu\": \"^0.5.6\"\n\t},\n\t\"packageManager\": \"pnpm@8.6.0\"\n}"
  },
  {
    "path": "src/index.js",
    "content": "import * as acorn from 'acorn';\nimport { walk } from 'estree-walker';\nimport { id, re } from './utils/id.js';\nimport { get_comment_handlers } from './utils/comments.js';\n\n/** @typedef {import('estree').Expression} Expression */\n/** @typedef {import('estree').Node} Node */\n/** @typedef {import('estree').ObjectExpression} ObjectExpression */\n/** @typedef {import('estree').Property} Property */\n/** @typedef {import('estree').SpreadElement} SpreadElement */\n\n/** @typedef {import('./utils/comments').CommentWithLocation} CommentWithLocation */\n\n/** @type {Record<string, string>} */\nconst sigils = {\n\t'@': 'AT',\n\t'#': 'HASH'\n};\n\n/** @param {TemplateStringsArray} strings */\nconst join = (strings) => {\n\tlet str = strings[0];\n\tfor (let i = 1; i < strings.length; i += 1) {\n\t\tstr += `_${id}_${i - 1}_${strings[i]}`;\n\t}\n\treturn str.replace(\n\t\t/([@#])(\\w+)/g,\n\t\t(_m, sigil, name) => `_${id}_${sigils[sigil]}_${name}`\n\t);\n};\n\n/**\n * @param {any[]} array\n * @param {any[]} target\n */\nconst flatten_body = (array, target) => {\n\tfor (let i = 0; i < array.length; i += 1) {\n\t\tconst statement = array[i];\n\t\tif (Array.isArray(statement)) {\n\t\t\tflatten_body(statement, target);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (statement.type === 'ExpressionStatement') {\n\t\t\tif (statement.expression === EMPTY) continue;\n\n\t\t\tif (Array.isArray(statement.expression)) {\n\t\t\t\t// TODO this is hacktacular\n\t\t\t\tlet node = statement.expression[0];\n\t\t\t\twhile (Array.isArray(node)) node = node[0];\n\t\t\t\tif (node) node.leadingComments = statement.leadingComments;\n\n\t\t\t\tflatten_body(statement.expression, target);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (/(Expression|Literal)$/.test(statement.expression.type)) {\n\t\t\t\ttarget.push(statement);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (statement.leadingComments)\n\t\t\t\tstatement.expression.leadingComments = statement.leadingComments;\n\t\t\tif (statement.trailingComments)\n\t\t\t\tstatement.expression.trailingComments = statement.trailingComments;\n\n\t\t\ttarget.push(statement.expression);\n\t\t\tcontinue;\n\t\t}\n\n\t\ttarget.push(statement);\n\t}\n\n\treturn target;\n};\n\n/**\n * @param {any[]} array\n * @param {any[]} target\n */\nconst flatten_properties = (array, target) => {\n\tfor (let i = 0; i < array.length; i += 1) {\n\t\tconst property = array[i];\n\n\t\tif (property.value === EMPTY) continue;\n\n\t\tif (property.key === property.value && Array.isArray(property.key)) {\n\t\t\tflatten_properties(property.key, target);\n\t\t\tcontinue;\n\t\t}\n\n\t\ttarget.push(property);\n\t}\n\n\treturn target;\n};\n\n/**\n * @param {any[]} nodes\n * @param {any[]} target\n */\nconst flatten = (nodes, target) => {\n\tfor (let i = 0; i < nodes.length; i += 1) {\n\t\tconst node = nodes[i];\n\n\t\tif (node === EMPTY) continue;\n\n\t\tif (Array.isArray(node)) {\n\t\t\tflatten(node, target);\n\t\t\tcontinue;\n\t\t}\n\n\t\ttarget.push(node);\n\t}\n\n\treturn target;\n};\n\nconst EMPTY = { type: 'Empty' };\n\n/**\n *\n * @param {CommentWithLocation[]} comments\n * @param {string} raw\n * @returns {any}\n */\nconst acorn_opts = (comments, raw) => {\n\tconst { onComment } = get_comment_handlers(comments, raw);\n\treturn {\n\t\tecmaVersion: 2022,\n\t\tsourceType: 'module',\n\t\tallowAwaitOutsideFunction: true,\n\t\tallowImportExportEverywhere: true,\n\t\tallowReturnOutsideFunction: true,\n\t\tonComment\n\t};\n};\n\n/**\n * @param {string} raw\n * @param {Node} node\n * @param {any[]} values\n * @param {CommentWithLocation[]} comments\n */\nconst inject = (raw, node, values, comments) => {\n\tcomments.forEach((comment) => {\n\t\tcomment.value = comment.value.replace(re, (m, i) =>\n\t\t\t+i in values ? values[+i] : m\n\t\t);\n\t});\n\n\tconst { enter, leave } = get_comment_handlers(comments, raw);\n\n\treturn walk(node, {\n\t\tenter,\n\n\t\t/** @param {any} node */\n\t\tleave(node) {\n\t\t\tif (node.type === 'Identifier') {\n\t\t\t\tre.lastIndex = 0;\n\t\t\t\tconst match = re.exec(node.name);\n\n\t\t\t\tif (match) {\n\t\t\t\t\tif (match[1]) {\n\t\t\t\t\t\tif (+match[1] in values) {\n\t\t\t\t\t\t\tlet value = values[+match[1]];\n\n\t\t\t\t\t\t\tif (typeof value === 'string') {\n\t\t\t\t\t\t\t\tvalue = {\n\t\t\t\t\t\t\t\t\ttype: 'Identifier',\n\t\t\t\t\t\t\t\t\tname: value,\n\t\t\t\t\t\t\t\t\tleadingComments: node.leadingComments,\n\t\t\t\t\t\t\t\t\ttrailingComments: node.trailingComments\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else if (typeof value === 'number') {\n\t\t\t\t\t\t\t\tvalue = {\n\t\t\t\t\t\t\t\t\ttype: 'Literal',\n\t\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t\t\tleadingComments: node.leadingComments,\n\t\t\t\t\t\t\t\t\ttrailingComments: node.trailingComments\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.replace(value || EMPTY);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnode.name = `${match[2] ? `@` : `#`}${match[4]}`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (node.type === 'Literal') {\n\t\t\t\tif (typeof node.value === 'string') {\n\t\t\t\t\tre.lastIndex = 0;\n\t\t\t\t\tconst new_value = /** @type {string} */ (node.value).replace(\n\t\t\t\t\t\tre,\n\t\t\t\t\t\t(m, i) => (+i in values ? values[+i] : m)\n\t\t\t\t\t);\n\t\t\t\t\tconst has_changed = new_value !== node.value;\n\t\t\t\t\tnode.value = new_value;\n\t\t\t\t\tif (has_changed && node.raw) {\n\t\t\t\t\t\t// preserve the quotes\n\t\t\t\t\t\tnode.raw = `${node.raw[0]}${JSON.stringify(node.value).slice(\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t-1\n\t\t\t\t\t\t)}${node.raw[node.raw.length - 1]}`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (node.type === 'TemplateElement') {\n\t\t\t\tre.lastIndex = 0;\n\t\t\t\tnode.value.raw = /** @type {string} */ (node.value.raw).replace(\n\t\t\t\t\tre,\n\t\t\t\t\t(m, i) => (+i in values ? values[+i] : m)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (node.type === 'Program' || node.type === 'BlockStatement') {\n\t\t\t\tnode.body = flatten_body(node.body, []);\n\t\t\t}\n\n\t\t\tif (node.type === 'ObjectExpression' || node.type === 'ObjectPattern') {\n\t\t\t\tnode.properties = flatten_properties(node.properties, []);\n\t\t\t}\n\n\t\t\tif (node.type === 'ArrayExpression' || node.type === 'ArrayPattern') {\n\t\t\t\tnode.elements = flatten(node.elements, []);\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tnode.type === 'FunctionExpression' ||\n\t\t\t\tnode.type === 'FunctionDeclaration' ||\n\t\t\t\tnode.type === 'ArrowFunctionExpression'\n\t\t\t) {\n\t\t\t\tnode.params = flatten(node.params, []);\n\t\t\t}\n\n\t\t\tif (node.type === 'CallExpression' || node.type === 'NewExpression') {\n\t\t\t\tnode.arguments = flatten(node.arguments, []);\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tnode.type === 'ImportDeclaration' ||\n\t\t\t\tnode.type === 'ExportNamedDeclaration'\n\t\t\t) {\n\t\t\t\tnode.specifiers = flatten(node.specifiers, []);\n\t\t\t}\n\n\t\t\tif (node.type === 'ForStatement') {\n\t\t\t\tnode.init = node.init === EMPTY ? null : node.init;\n\t\t\t\tnode.test = node.test === EMPTY ? null : node.test;\n\t\t\t\tnode.update = node.update === EMPTY ? null : node.update;\n\t\t\t}\n\n\t\t\tleave(node);\n\t\t}\n\t});\n};\n\n/**\n *\n * @param {TemplateStringsArray} strings\n * @param  {any[]} values\n * @returns {Node[]}\n */\nexport function b(strings, ...values) {\n\tconst str = join(strings);\n\n\t/** @type {CommentWithLocation[]} */\n\tconst comments = [];\n\n\ttry {\n\t\tlet ast = /** @type {any} */ (acorn.parse(str, acorn_opts(comments, str)));\n\n\t\tast = inject(str, ast, values, comments);\n\n\t\treturn ast.body;\n\t} catch (err) {\n\t\thandle_error(str, err);\n\t}\n}\n\n/**\n *\n * @param {TemplateStringsArray} strings\n * @param  {any[]} values\n * @returns {Expression & { start: Number, end: number }}\n */\nexport function x(strings, ...values) {\n\tconst str = join(strings);\n\n\t/** @type {CommentWithLocation[]} */\n\tconst comments = [];\n\n\ttry {\n\t\tlet expression =\n\t\t\t/** @type {Expression & { start: Number, end: number }} */ (\n\t\t\t\tacorn.parseExpressionAt(str, 0, acorn_opts(comments, str))\n\t\t\t);\n\t\tconst match = /\\S+/.exec(str.slice(expression.end));\n\t\tif (match) {\n\t\t\tthrow new Error(`Unexpected token '${match[0]}'`);\n\t\t}\n\n\t\texpression = /** @type {Expression & { start: Number, end: number }} */ (\n\t\t\tinject(str, expression, values, comments)\n\t\t);\n\n\t\treturn expression;\n\t} catch (err) {\n\t\thandle_error(str, err);\n\t}\n}\n\n/**\n *\n * @param {TemplateStringsArray} strings\n * @param  {any[]} values\n * @returns {(Property | SpreadElement) & { start: Number, end: number }}\n */\nexport function p(strings, ...values) {\n\tconst str = `{${join(strings)}}`;\n\n\t/** @type {CommentWithLocation[]} */\n\tconst comments = [];\n\n\ttry {\n\t\tlet expression = /** @type {any} */ (\n\t\t\tacorn.parseExpressionAt(str, 0, acorn_opts(comments, str))\n\t\t);\n\n\t\texpression = inject(str, expression, values, comments);\n\n\t\treturn expression.properties[0];\n\t} catch (err) {\n\t\thandle_error(str, err);\n\t}\n}\n\n/**\n * @param {string} str\n * @param {Error} err\n */\nfunction handle_error(str, err) {\n\t// TODO location/code frame\n\n\tre.lastIndex = 0;\n\n\tstr = str.replace(re, (m, i, at, hash, name) => {\n\t\tif (at) return `@${name}`;\n\t\tif (hash) return `#${name}`;\n\n\t\treturn '${...}';\n\t});\n\n\tconsole.log(`failed to parse:\\n${str}`);\n\tthrow err;\n}\n\nexport { print } from './print/index.js';\n\n/**\n * @param {string} source\n * @param {any} opts\n */\nexport const parse = (source, opts) => {\n\t/** @type {CommentWithLocation[]} */\n\tconst comments = [];\n\tconst { onComment, enter, leave } = get_comment_handlers(comments, source);\n\tconst ast = /** @type {any} */ (acorn.parse(source, { onComment, ...opts }));\n\twalk(ast, { enter, leave });\n\treturn ast;\n};\n\n/**\n * @param {string} source\n * @param {number} index\n * @param {any} opts\n */\nexport const parseExpressionAt = (source, index, opts) => {\n\t/** @type {CommentWithLocation[]} */\n\tconst comments = [];\n\tconst { onComment, enter, leave } = get_comment_handlers(comments, source);\n\tconst ast = /** @type {any} */ (\n\t\tacorn.parseExpressionAt(source, index, { onComment, ...opts })\n\t);\n\twalk(ast, { enter, leave });\n\treturn ast;\n};\n"
  },
  {
    "path": "src/print/handlers.js",
    "content": "// heavily based on https://github.com/davidbonnet/astring\n// released under MIT license https://github.com/davidbonnet/astring/blob/master/LICENSE\n\nimport { re } from '../utils/id.js';\nimport { push_array } from '../utils/push_array.js';\n\n/** @typedef {import('estree').ArrowFunctionExpression} ArrowFunctionExpression */\n/** @typedef {import('estree').BinaryExpression} BinaryExpression */\n/** @typedef {import('estree').CallExpression} CallExpression */\n/** @typedef {import('estree').Comment} Comment */\n/** @typedef {import('estree').ExportSpecifier} ExportSpecifier */\n/** @typedef {import('estree').Expression} Expression */\n/** @typedef {import('estree').FunctionDeclaration} FunctionDeclaration */\n/** @typedef {import('estree').ImportDeclaration} ImportDeclaration */\n/** @typedef {import('estree').ImportSpecifier} ImportSpecifier */\n/** @typedef {import('estree').Literal} Literal */\n/** @typedef {import('estree').LogicalExpression} LogicalExpression */\n/** @typedef {import('estree').NewExpression} NewExpression */\n/** @typedef {import('estree').Node} Node */\n/** @typedef {import('estree').ObjectExpression} ObjectExpression */\n/** @typedef {import('estree').Pattern} Pattern */\n/** @typedef {import('estree').Property} Property */\n/** @typedef {import('estree').PropertyDefinition} PropertyDefinition */\n/** @typedef {import('estree').SequenceExpression} SequenceExpression */\n/** @typedef {import('estree').SimpleCallExpression} SimpleCallExpression */\n/** @typedef {import('estree').SwitchStatement} SwitchStatement */\n/** @typedef {import('estree').VariableDeclaration} VariableDeclaration */\n/** @typedef {import('estree').StaticBlock} StaticBlock */\n/** @typedef {import('estree').PrivateIdentifier} PrivateIdenifier*/\n\n/**\n * @typedef {{\n *   content: string;\n *   loc?: {\n *     start: { line: number; column: number; };\n *     end: { line: number; column: number; };\n *   };\n *   has_newline: boolean;\n * }} Chunk\n */\n\n/**\n * @typedef {(node: any, state: State) => Chunk[]} Handler\n */\n\n/**\n * @typedef {{\n *   indent: string;\n *   scope: any; // TODO import from periscopic\n *   scope_map: WeakMap<Node, any>;\n *   getName: (name: string) => string;\n *   deconflicted: WeakMap<Node, Map<string, string>>;\n *   comments: Comment[];\n * }} State\n */\n\n/**\n * @param {Node} node\n * @param {State} state\n * @returns {Chunk[]}\n */\nexport function handle(node, state) {\n\tconst handler = handlers[node.type];\n\n\tif (!handler) {\n\t\tthrow new Error(`Not implemented ${node.type}`);\n\t}\n\n\tconst result = handler(node, state);\n\n\tif (node.leadingComments) {\n\t\tresult.unshift(\n\t\t\tc(\n\t\t\t\tnode.leadingComments\n\t\t\t\t\t.map((comment) =>\n\t\t\t\t\t\tcomment.type === 'Block'\n\t\t\t\t\t\t\t? `/*${comment.value}*/${\n\t\t\t\t\t\t\t\t\t/** @type {any} */ (comment).has_trailing_newline\n\t\t\t\t\t\t\t\t\t\t? `\\n${state.indent}`\n\t\t\t\t\t\t\t\t\t\t: ` `\n\t\t\t\t\t\t\t  }`\n\t\t\t\t\t\t\t: `//${comment.value}${\n\t\t\t\t\t\t\t\t\t/** @type {any} */ (comment).has_trailing_newline\n\t\t\t\t\t\t\t\t\t\t? `\\n${state.indent}`\n\t\t\t\t\t\t\t\t\t\t: ` `\n\t\t\t\t\t\t\t  }`\n\t\t\t\t\t)\n\t\t\t\t\t.join(``)\n\t\t\t)\n\t\t);\n\t}\n\n\tif (node.trailingComments) {\n\t\tstate.comments.push(node.trailingComments[0]); // there is only ever one\n\t}\n\n\treturn result;\n}\n\n/**\n * @param {string} content\n * @param {Node} [node]\n * @returns {Chunk}\n */\nfunction c(content, node) {\n\treturn {\n\t\tcontent,\n\t\tloc: node && node.loc,\n\t\thas_newline: /\\n/.test(content)\n\t};\n}\n\nconst OPERATOR_PRECEDENCE = {\n\t'||': 2,\n\t'&&': 3,\n\t'??': 4,\n\t'|': 5,\n\t'^': 6,\n\t'&': 7,\n\t'==': 8,\n\t'!=': 8,\n\t'===': 8,\n\t'!==': 8,\n\t'<': 9,\n\t'>': 9,\n\t'<=': 9,\n\t'>=': 9,\n\tin: 9,\n\tinstanceof: 9,\n\t'<<': 10,\n\t'>>': 10,\n\t'>>>': 10,\n\t'+': 11,\n\t'-': 11,\n\t'*': 12,\n\t'%': 12,\n\t'/': 12,\n\t'**': 13\n};\n\n/** @type {Record<string, number>} */\nconst EXPRESSIONS_PRECEDENCE = {\n\tArrayExpression: 20,\n\tTaggedTemplateExpression: 20,\n\tThisExpression: 20,\n\tIdentifier: 20,\n\tLiteral: 18,\n\tTemplateLiteral: 20,\n\tSuper: 20,\n\tSequenceExpression: 20,\n\tMemberExpression: 19,\n\tCallExpression: 19,\n\tNewExpression: 19,\n\tAwaitExpression: 17,\n\tClassExpression: 17,\n\tFunctionExpression: 17,\n\tObjectExpression: 17,\n\tUpdateExpression: 16,\n\tUnaryExpression: 15,\n\tBinaryExpression: 14,\n\tLogicalExpression: 13,\n\tConditionalExpression: 4,\n\tArrowFunctionExpression: 3,\n\tAssignmentExpression: 3,\n\tYieldExpression: 2,\n\tRestElement: 1\n};\n\n/**\n *\n * @param {Expression} node\n * @param {BinaryExpression | LogicalExpression} parent\n * @param {boolean} is_right\n * @returns\n */\nfunction needs_parens(node, parent, is_right) {\n\t// special case where logical expressions and coalesce expressions cannot be mixed,\n\t// either of them need to be wrapped with parentheses\n\tif (\n\t\tnode.type === 'LogicalExpression' &&\n\t\tparent.type === 'LogicalExpression' &&\n\t\t((parent.operator === '??' && node.operator !== '??') ||\n\t\t\t(parent.operator !== '??' && node.operator === '??'))\n\t) {\n\t\treturn true;\n\t}\n\n\tconst precedence = EXPRESSIONS_PRECEDENCE[node.type];\n\tconst parent_precedence = EXPRESSIONS_PRECEDENCE[parent.type];\n\n\tif (precedence !== parent_precedence) {\n\t\t// Different node types\n\t\treturn (\n\t\t\t(!is_right &&\n\t\t\t\tprecedence === 15 &&\n\t\t\t\tparent_precedence === 14 &&\n\t\t\t\tparent.operator === '**') ||\n\t\t\tprecedence < parent_precedence\n\t\t);\n\t}\n\n\tif (precedence !== 13 && precedence !== 14) {\n\t\t// Not a `LogicalExpression` or `BinaryExpression`\n\t\treturn false;\n\t}\n\n\tif (\n\t\t/** @type {BinaryExpression} */ (node).operator === '**' &&\n\t\tparent.operator === '**'\n\t) {\n\t\t// Exponentiation operator has right-to-left associativity\n\t\treturn !is_right;\n\t}\n\n\tif (is_right) {\n\t\t// Parenthesis are used if both operators have the same precedence\n\t\treturn (\n\t\t\tOPERATOR_PRECEDENCE[/** @type {BinaryExpression} */ (node).operator] <=\n\t\t\tOPERATOR_PRECEDENCE[parent.operator]\n\t\t);\n\t}\n\n\treturn (\n\t\tOPERATOR_PRECEDENCE[/** @type {BinaryExpression} */ (node).operator] <\n\t\tOPERATOR_PRECEDENCE[parent.operator]\n\t);\n}\n\n/** @param {Node} node */\nfunction has_call_expression(node) {\n\twhile (node) {\n\t\tif (node.type[0] === 'CallExpression') {\n\t\t\treturn true;\n\t\t} else if (node.type === 'MemberExpression') {\n\t\t\tnode = node.object;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n\n/** @param {Chunk[]} chunks */\nconst has_newline = (chunks) => {\n\tfor (let i = 0; i < chunks.length; i += 1) {\n\t\tif (chunks[i].has_newline) return true;\n\t}\n\treturn false;\n};\n\n/** @param {Chunk[]} chunks */\nconst get_length = (chunks) => {\n\tlet total = 0;\n\tfor (let i = 0; i < chunks.length; i += 1) {\n\t\ttotal += chunks[i].content.length;\n\t}\n\treturn total;\n};\n\n/**\n * @param {number} a\n * @param {number} b\n */\nconst sum = (a, b) => a + b;\n\n/**\n * @param {Chunk[][]} nodes\n * @param {Chunk} separator\n * @returns {Chunk[]}\n */\nconst join = (nodes, separator) => {\n\tif (nodes.length === 0) return [];\n\n\tconst joined = [...nodes[0]];\n\tfor (let i = 1; i < nodes.length; i += 1) {\n\t\tjoined.push(separator);\n\t\tpush_array(joined, nodes[i]);\n\t}\n\treturn joined;\n};\n\n/**\n * @param {(node: any, state: State) => Chunk[]} fn\n */\nconst scoped = (fn) => {\n\t/**\n\t * @param {any} node\n\t * @param {State} state\n\t */\n\tconst scoped_fn = (node, state) => {\n\t\treturn fn(node, {\n\t\t\t...state,\n\t\t\tscope: state.scope_map.get(node)\n\t\t});\n\t};\n\n\treturn scoped_fn;\n};\n\n/**\n * @param {string} name\n * @param {Set<string>} names\n */\nconst deconflict = (name, names) => {\n\tconst original = name;\n\tlet i = 1;\n\n\twhile (names.has(name)) {\n\t\tname = `${original}$${i++}`;\n\t}\n\n\treturn name;\n};\n\n/**\n * @param {Node[]} nodes\n * @param {State} state\n */\nconst handle_body = (nodes, state) => {\n\tconst chunks = [];\n\n\tconst body = nodes.map((statement) => {\n\t\tconst chunks = handle(statement, {\n\t\t\t...state,\n\t\t\tindent: state.indent\n\t\t});\n\n\t\tlet add_newline = false;\n\n\t\twhile (state.comments.length) {\n\t\t\tconst comment = state.comments.shift();\n\t\t\tconst prefix = add_newline ? `\\n${state.indent}` : ` `;\n\n\t\t\tchunks.push(\n\t\t\t\tc(\n\t\t\t\t\tcomment.type === 'Block'\n\t\t\t\t\t\t? `${prefix}/*${comment.value}*/`\n\t\t\t\t\t\t: `${prefix}//${comment.value}`\n\t\t\t\t)\n\t\t\t);\n\n\t\t\tadd_newline = comment.type === 'Line';\n\t\t}\n\n\t\treturn chunks;\n\t});\n\n\tlet needed_padding = false;\n\n\tfor (let i = 0; i < body.length; i += 1) {\n\t\tconst needs_padding = has_newline(body[i]);\n\n\t\tif (i > 0) {\n\t\t\tchunks.push(\n\t\t\t\tc(\n\t\t\t\t\tneeds_padding || needed_padding\n\t\t\t\t\t\t? `\\n\\n${state.indent}`\n\t\t\t\t\t\t: `\\n${state.indent}`\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\tpush_array(chunks, body[i]);\n\n\t\tneeded_padding = needs_padding;\n\t}\n\n\treturn chunks;\n};\n\n/**\n * @param {VariableDeclaration} node\n * @param {State} state\n */\nconst handle_var_declaration = (node, state) => {\n\tconst chunks = [c(`${node.kind} `)];\n\n\tconst declarators = node.declarations.map((d) =>\n\t\thandle(d, {\n\t\t\t...state,\n\t\t\tindent: state.indent + (node.declarations.length === 1 ? '' : '\\t')\n\t\t})\n\t);\n\n\tconst multiple_lines =\n\t\tdeclarators.some(has_newline) ||\n\t\tdeclarators.map(get_length).reduce(sum, 0) +\n\t\t\t(state.indent.length + declarators.length - 1) * 2 >\n\t\t\t80;\n\n\tconst separator = c(multiple_lines ? `,\\n${state.indent}\\t` : ', ');\n\n\tpush_array(chunks, join(declarators, separator));\n\n\treturn chunks;\n};\n\n/** @type {Record<string, Handler>} */\nconst handlers = {\n\tProgram(node, state) {\n\t\treturn handle_body(node.body, state);\n\t},\n\n\tBlockStatement: scoped((node, state) => {\n\t\treturn [\n\t\t\tc(`{\\n${state.indent}\\t`),\n\t\t\t...handle_body(node.body, { ...state, indent: state.indent + '\\t' }),\n\t\t\tc(`\\n${state.indent}}`)\n\t\t];\n\t}),\n\n\tEmptyStatement(node, state) {\n\t\treturn [c(';')];\n\t},\n\n\tParenthesizedExpression(node, state) {\n\t\treturn handle(node.expression, state);\n\t},\n\n\tExpressionStatement(node, state) {\n\t\tif (\n\t\t\tnode.expression.type === 'AssignmentExpression' &&\n\t\t\tnode.expression.left.type === 'ObjectPattern'\n\t\t) {\n\t\t\t// is an AssignmentExpression to an ObjectPattern\n\t\t\treturn [c('('), ...handle(node.expression, state), c(');')];\n\t\t}\n\n\t\treturn [...handle(node.expression, state), c(';')];\n\t},\n\n\tIfStatement(node, state) {\n\t\tconst chunks = [\n\t\t\tc('if ('),\n\t\t\t...handle(node.test, state),\n\t\t\tc(') '),\n\t\t\t...handle(node.consequent, state)\n\t\t];\n\n\t\tif (node.alternate) {\n\t\t\tchunks.push(c(' else '));\n\t\t\tpush_array(chunks, handle(node.alternate, state));\n\t\t}\n\n\t\treturn chunks;\n\t},\n\n\tLabeledStatement(node, state) {\n\t\treturn [...handle(node.label, state), c(': '), ...handle(node.body, state)];\n\t},\n\n\tBreakStatement(node, state) {\n\t\treturn node.label\n\t\t\t? [c('break '), ...handle(node.label, state), c(';')]\n\t\t\t: [c('break;')];\n\t},\n\n\tContinueStatement(node, state) {\n\t\treturn node.label\n\t\t\t? [c('continue '), ...handle(node.label, state), c(';')]\n\t\t\t: [c('continue;')];\n\t},\n\n\tWithStatement(node, state) {\n\t\treturn [\n\t\t\tc('with ('),\n\t\t\t...handle(node.object, state),\n\t\t\tc(') '),\n\t\t\t...handle(node.body, state)\n\t\t];\n\t},\n\n\tSwitchStatement(/** @type {SwitchStatement} */ node, state) {\n\t\tconst chunks = [\n\t\t\tc('switch ('),\n\t\t\t...handle(node.discriminant, state),\n\t\t\tc(') {')\n\t\t];\n\n\t\tnode.cases.forEach((block) => {\n\t\t\tif (block.test) {\n\t\t\t\tchunks.push(c(`\\n${state.indent}\\tcase `));\n\t\t\t\tpush_array(\n\t\t\t\t\tchunks,\n\t\t\t\t\thandle(block.test, { ...state, indent: `${state.indent}\\t` })\n\t\t\t\t);\n\t\t\t\tchunks.push(c(':'));\n\t\t\t} else {\n\t\t\t\tchunks.push(c(`\\n${state.indent}\\tdefault:`));\n\t\t\t}\n\n\t\t\tblock.consequent.forEach((statement) => {\n\t\t\t\tchunks.push(c(`\\n${state.indent}\\t\\t`));\n\t\t\t\tpush_array(\n\t\t\t\t\tchunks,\n\t\t\t\t\thandle(statement, { ...state, indent: `${state.indent}\\t\\t` })\n\t\t\t\t);\n\t\t\t});\n\t\t});\n\n\t\tchunks.push(c(`\\n${state.indent}}`));\n\n\t\treturn chunks;\n\t},\n\n\tReturnStatement(node, state) {\n\t\tif (node.argument) {\n\t\t\tconst contains_comment =\n\t\t\t\tnode.argument.leadingComments &&\n\t\t\t\tnode.argument.leadingComments.some(\n\t\t\t\t\t(\n\t\t\t\t\t\t/** @type import('../utils/comments.js').CommentWithLocation */ comment\n\t\t\t\t\t) => comment.has_trailing_newline\n\t\t\t\t);\n\t\t\treturn [\n\t\t\t\tc(contains_comment ? 'return (' : 'return '),\n\t\t\t\t...handle(node.argument, state),\n\t\t\t\tc(contains_comment ? ');' : ';')\n\t\t\t];\n\t\t} else {\n\t\t\treturn [c('return;')];\n\t\t}\n\t},\n\n\tThrowStatement(node, state) {\n\t\treturn [c('throw '), ...handle(node.argument, state), c(';')];\n\t},\n\n\tTryStatement(node, state) {\n\t\tconst chunks = [c('try '), ...handle(node.block, state)];\n\n\t\tif (node.handler) {\n\t\t\tif (node.handler.param) {\n\t\t\t\tchunks.push(c(' catch('));\n\t\t\t\tpush_array(chunks, handle(node.handler.param, state));\n\t\t\t\tchunks.push(c(') '));\n\t\t\t} else {\n\t\t\t\tchunks.push(c(' catch '));\n\t\t\t}\n\n\t\t\tpush_array(chunks, handle(node.handler.body, state));\n\t\t}\n\n\t\tif (node.finalizer) {\n\t\t\tchunks.push(c(' finally '));\n\t\t\tpush_array(chunks, handle(node.finalizer, state));\n\t\t}\n\n\t\treturn chunks;\n\t},\n\n\tWhileStatement(node, state) {\n\t\treturn [\n\t\t\tc('while ('),\n\t\t\t...handle(node.test, state),\n\t\t\tc(') '),\n\t\t\t...handle(node.body, state)\n\t\t];\n\t},\n\n\tDoWhileStatement(node, state) {\n\t\treturn [\n\t\t\tc('do '),\n\t\t\t...handle(node.body, state),\n\t\t\tc(' while ('),\n\t\t\t...handle(node.test, state),\n\t\t\tc(');')\n\t\t];\n\t},\n\n\tForStatement: scoped((node, state) => {\n\t\tconst chunks = [c('for (')];\n\n\t\tif (node.init) {\n\t\t\tif (node.init.type === 'VariableDeclaration') {\n\t\t\t\tpush_array(chunks, handle_var_declaration(node.init, state));\n\t\t\t} else {\n\t\t\t\tpush_array(chunks, handle(node.init, state));\n\t\t\t}\n\t\t}\n\n\t\tchunks.push(c('; '));\n\t\tif (node.test) push_array(chunks, handle(node.test, state));\n\t\tchunks.push(c('; '));\n\t\tif (node.update) push_array(chunks, handle(node.update, state));\n\n\t\tchunks.push(c(') '));\n\t\tpush_array(chunks, handle(node.body, state));\n\n\t\treturn chunks;\n\t}),\n\n\tForInStatement: scoped((node, state) => {\n\t\tconst chunks = [c(`for ${node.await ? 'await ' : ''}(`)];\n\n\t\tif (node.left.type === 'VariableDeclaration') {\n\t\t\tpush_array(chunks, handle_var_declaration(node.left, state));\n\t\t} else {\n\t\t\tpush_array(chunks, handle(node.left, state));\n\t\t}\n\n\t\tchunks.push(c(node.type === 'ForInStatement' ? ` in ` : ` of `));\n\t\tpush_array(chunks, handle(node.right, state));\n\t\tchunks.push(c(') '));\n\t\tpush_array(chunks, handle(node.body, state));\n\n\t\treturn chunks;\n\t}),\n\n\tDebuggerStatement(node, state) {\n\t\treturn [c('debugger', node), c(';')];\n\t},\n\n\tFunctionDeclaration: scoped(\n\t\t(/** @type {FunctionDeclaration} */ node, state) => {\n\t\t\tconst chunks = [];\n\n\t\t\tif (node.async) chunks.push(c('async '));\n\t\t\tchunks.push(c(node.generator ? 'function* ' : 'function '));\n\t\t\tif (node.id) push_array(chunks, handle(node.id, state));\n\t\t\tchunks.push(c('('));\n\n\t\t\tconst params = node.params.map((p) =>\n\t\t\t\thandle(p, {\n\t\t\t\t\t...state,\n\t\t\t\t\tindent: state.indent + '\\t'\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tconst multiple_lines =\n\t\t\t\tparams.some(has_newline) ||\n\t\t\t\tparams.map(get_length).reduce(sum, 0) +\n\t\t\t\t\t(state.indent.length + params.length - 1) * 2 >\n\t\t\t\t\t80;\n\n\t\t\tconst separator = c(multiple_lines ? `,\\n${state.indent}` : ', ');\n\n\t\t\tif (multiple_lines) {\n\t\t\t\tchunks.push(c(`\\n${state.indent}\\t`));\n\t\t\t\tpush_array(chunks, join(params, separator));\n\t\t\t\tchunks.push(c(`\\n${state.indent}`));\n\t\t\t} else {\n\t\t\t\tpush_array(chunks, join(params, separator));\n\t\t\t}\n\n\t\t\tchunks.push(c(') '));\n\t\t\tpush_array(chunks, handle(node.body, state));\n\n\t\t\treturn chunks;\n\t\t}\n\t),\n\n\tVariableDeclaration(node, state) {\n\t\treturn handle_var_declaration(node, state).concat(c(';'));\n\t},\n\n\tVariableDeclarator(node, state) {\n\t\tif (node.init) {\n\t\t\treturn [...handle(node.id, state), c(' = '), ...handle(node.init, state)];\n\t\t} else {\n\t\t\treturn handle(node.id, state);\n\t\t}\n\t},\n\n\tClassDeclaration(node, state) {\n\t\tconst chunks = [c('class ')];\n\n\t\tif (node.id) {\n\t\t\tpush_array(chunks, handle(node.id, state));\n\t\t\tchunks.push(c(' '));\n\t\t}\n\n\t\tif (node.superClass) {\n\t\t\tchunks.push(c('extends '));\n\t\t\tpush_array(chunks, handle(node.superClass, state));\n\t\t\tchunks.push(c(' '));\n\t\t}\n\n\t\tpush_array(chunks, handle(node.body, state));\n\n\t\treturn chunks;\n\t},\n\n\tImportDeclaration(/** @type {ImportDeclaration} */ node, state) {\n\t\tconst chunks = [c('import ')];\n\n\t\tconst { length } = node.specifiers;\n\t\tconst source = handle(node.source, state);\n\n\t\tif (length > 0) {\n\t\t\tlet i = 0;\n\n\t\t\twhile (i < length) {\n\t\t\t\tif (i > 0) {\n\t\t\t\t\tchunks.push(c(', '));\n\t\t\t\t}\n\n\t\t\t\tconst specifier = node.specifiers[i];\n\n\t\t\t\tif (specifier.type === 'ImportDefaultSpecifier') {\n\t\t\t\t\tchunks.push(c(specifier.local.name, specifier));\n\t\t\t\t\ti += 1;\n\t\t\t\t} else if (specifier.type === 'ImportNamespaceSpecifier') {\n\t\t\t\t\tchunks.push(c('* as ' + specifier.local.name, specifier));\n\t\t\t\t\ti += 1;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (i < length) {\n\t\t\t\t// we have named specifiers\n\t\t\t\tconst specifiers = node.specifiers\n\t\t\t\t\t.slice(i)\n\t\t\t\t\t.map((/** @type {ImportSpecifier} */ specifier) => {\n\t\t\t\t\t\tconst name = handle(specifier.imported, state)[0];\n\t\t\t\t\t\tconst as = handle(specifier.local, state)[0];\n\n\t\t\t\t\t\tif (name.content === as.content) {\n\t\t\t\t\t\t\treturn [as];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn [name, c(' as '), as];\n\t\t\t\t\t});\n\n\t\t\t\tconst width =\n\t\t\t\t\tget_length(chunks) +\n\t\t\t\t\tspecifiers.map(get_length).reduce(sum, 0) +\n\t\t\t\t\t2 * specifiers.length +\n\t\t\t\t\t6 +\n\t\t\t\t\tget_length(source);\n\n\t\t\t\tif (width > 80) {\n\t\t\t\t\tchunks.push(c(`{\\n\\t`));\n\t\t\t\t\tpush_array(chunks, join(specifiers, c(',\\n\\t')));\n\t\t\t\t\tchunks.push(c('\\n}'));\n\t\t\t\t} else {\n\t\t\t\t\tchunks.push(c(`{ `));\n\t\t\t\t\tpush_array(chunks, join(specifiers, c(', ')));\n\t\t\t\t\tchunks.push(c(' }'));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tchunks.push(c(' from '));\n\t\t}\n\n\t\tpush_array(chunks, source);\n\t\tchunks.push(c(';'));\n\n\t\treturn chunks;\n\t},\n\n\tImportExpression(node, state) {\n\t\treturn [c('import('), ...handle(node.source, state), c(')')];\n\t},\n\n\tExportDefaultDeclaration(node, state) {\n\t\tconst chunks = [c(`export default `), ...handle(node.declaration, state)];\n\n\t\tif (node.declaration.type !== 'FunctionDeclaration') {\n\t\t\tchunks.push(c(';'));\n\t\t}\n\n\t\treturn chunks;\n\t},\n\n\tExportNamedDeclaration(node, state) {\n\t\tconst chunks = [c('export ')];\n\n\t\tif (node.declaration) {\n\t\t\tpush_array(chunks, handle(node.declaration, state));\n\t\t} else {\n\t\t\tconst specifiers = node.specifiers.map(\n\t\t\t\t(/** @type {ExportSpecifier} */ specifier) => {\n\t\t\t\t\tconst name = handle(specifier.local, state)[0];\n\t\t\t\t\tconst as = handle(specifier.exported, state)[0];\n\n\t\t\t\t\tif (name.content === as.content) {\n\t\t\t\t\t\treturn [name];\n\t\t\t\t\t}\n\n\t\t\t\t\treturn [name, c(' as '), as];\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tconst width =\n\t\t\t\t7 + specifiers.map(get_length).reduce(sum, 0) + 2 * specifiers.length;\n\n\t\t\tif (width > 80) {\n\t\t\t\tchunks.push(c('{\\n\\t'));\n\t\t\t\tpush_array(chunks, join(specifiers, c(',\\n\\t')));\n\t\t\t\tchunks.push(c('\\n}'));\n\t\t\t} else {\n\t\t\t\tchunks.push(c('{ '));\n\t\t\t\tpush_array(chunks, join(specifiers, c(', ')));\n\t\t\t\tchunks.push(c(' }'));\n\t\t\t}\n\n\t\t\tif (node.source) {\n\t\t\t\tchunks.push(c(' from '));\n\t\t\t\tpush_array(chunks, handle(node.source, state));\n\t\t\t}\n\t\t}\n\n\t\tchunks.push(c(';'));\n\n\t\treturn chunks;\n\t},\n\n\tExportAllDeclaration(node, state) {\n\t\treturn [c(`export * from `), ...handle(node.source, state), c(`;`)];\n\t},\n\n\tMethodDefinition(node, state) {\n\t\tconst chunks = [];\n\n\t\tif (node.static) {\n\t\t\tchunks.push(c('static '));\n\t\t}\n\n\t\tif (node.kind === 'get' || node.kind === 'set') {\n\t\t\t// Getter or setter\n\t\t\tchunks.push(c(node.kind + ' '));\n\t\t}\n\n\t\tif (node.value.async) {\n\t\t\tchunks.push(c('async '));\n\t\t}\n\n\t\tif (node.value.generator) {\n\t\t\tchunks.push(c('*'));\n\t\t}\n\n\t\tif (node.computed) {\n\t\t\tchunks.push(c('['));\n\t\t\tpush_array(chunks, handle(node.key, state));\n\t\t\tchunks.push(c(']'));\n\t\t} else {\n\t\t\tpush_array(chunks, handle(node.key, state));\n\t\t}\n\n\t\tchunks.push(c('('));\n\n\t\tconst { params } = node.value;\n\t\tfor (let i = 0; i < params.length; i += 1) {\n\t\t\tpush_array(chunks, handle(params[i], state));\n\t\t\tif (i < params.length - 1) chunks.push(c(', '));\n\t\t}\n\n\t\tchunks.push(c(') '));\n\t\tpush_array(chunks, handle(node.value.body, state));\n\n\t\treturn chunks;\n\t},\n\n\tArrowFunctionExpression: scoped(\n\t\t(/** @type {ArrowFunctionExpression} */ node, state) => {\n\t\t\tconst chunks = [];\n\n\t\t\tif (node.async) chunks.push(c('async '));\n\n\t\t\tif (node.params.length === 1 && node.params[0].type === 'Identifier') {\n\t\t\t\tpush_array(chunks, handle(node.params[0], state));\n\t\t\t} else {\n\t\t\t\tconst params = node.params.map((param) =>\n\t\t\t\t\thandle(param, {\n\t\t\t\t\t\t...state,\n\t\t\t\t\t\tindent: state.indent + '\\t'\n\t\t\t\t\t})\n\t\t\t\t);\n\n\t\t\t\tchunks.push(c('('));\n\t\t\t\tpush_array(chunks, join(params, c(', ')));\n\t\t\t\tchunks.push(c(')'));\n\t\t\t}\n\n\t\t\tchunks.push(c(' => '));\n\n\t\t\tif (\n\t\t\t\tnode.body.type === 'ObjectExpression' ||\n\t\t\t\t(node.body.type === 'AssignmentExpression' &&\n\t\t\t\t\tnode.body.left.type === 'ObjectPattern')\n\t\t\t) {\n\t\t\t\tchunks.push(c('('));\n\t\t\t\tpush_array(chunks, handle(node.body, state));\n\t\t\t\tchunks.push(c(')'));\n\t\t\t} else {\n\t\t\t\tpush_array(chunks, handle(node.body, state));\n\t\t\t}\n\n\t\t\treturn chunks;\n\t\t}\n\t),\n\n\tThisExpression(node, state) {\n\t\treturn [c('this', node)];\n\t},\n\n\tSuper(node, state) {\n\t\treturn [c('super', node)];\n\t},\n\n\tRestElement(node, state) {\n\t\treturn [c('...'), ...handle(node.argument, state)];\n\t},\n\n\tYieldExpression(node, state) {\n\t\tif (node.argument) {\n\t\t\treturn [\n\t\t\t\tc(node.delegate ? `yield* ` : `yield `),\n\t\t\t\t...handle(node.argument, state)\n\t\t\t];\n\t\t}\n\n\t\treturn [c(node.delegate ? `yield*` : `yield`)];\n\t},\n\n\tAwaitExpression(node, state) {\n\t\tif (node.argument) {\n\t\t\tconst precedence = EXPRESSIONS_PRECEDENCE[node.argument.type];\n\n\t\t\tif (precedence && precedence < EXPRESSIONS_PRECEDENCE.AwaitExpression) {\n\t\t\t\treturn [c('await ('), ...handle(node.argument, state), c(')')];\n\t\t\t} else {\n\t\t\t\treturn [c('await '), ...handle(node.argument, state)];\n\t\t\t}\n\t\t}\n\n\t\treturn [c('await')];\n\t},\n\n\tTemplateLiteral(node, state) {\n\t\tconst chunks = [c('`')];\n\n\t\tconst { quasis, expressions } = node;\n\n\t\tfor (let i = 0; i < expressions.length; i++) {\n\t\t\tchunks.push(c(quasis[i].value.raw), c('${'));\n\t\t\tpush_array(chunks, handle(expressions[i], state));\n\t\t\tchunks.push(c('}'));\n\t\t}\n\n\t\tchunks.push(c(quasis[quasis.length - 1].value.raw), c('`'));\n\n\t\treturn chunks;\n\t},\n\n\tTaggedTemplateExpression(node, state) {\n\t\treturn handle(node.tag, state).concat(handle(node.quasi, state));\n\t},\n\n\tArrayExpression(node, state) {\n\t\tconst chunks = [c('[')];\n\n\t\t/** @type {Chunk[][]} */\n\t\tconst elements = [];\n\n\t\t/** @type {Chunk[]} */\n\t\tlet sparse_commas = [];\n\n\t\tfor (let i = 0; i < node.elements.length; i += 1) {\n\t\t\t// can't use map/forEach because of sparse arrays\n\t\t\tconst element = node.elements[i];\n\t\t\tif (element) {\n\t\t\t\telements.push([\n\t\t\t\t\t...sparse_commas,\n\t\t\t\t\t...handle(element, {\n\t\t\t\t\t\t...state,\n\t\t\t\t\t\tindent: state.indent + '\\t'\n\t\t\t\t\t})\n\t\t\t\t]);\n\t\t\t\tsparse_commas = [];\n\t\t\t} else {\n\t\t\t\tsparse_commas.push(c(','));\n\t\t\t}\n\t\t}\n\n\t\tconst multiple_lines =\n\t\t\telements.some(has_newline) ||\n\t\t\telements.map(get_length).reduce(sum, 0) +\n\t\t\t\t(state.indent.length + elements.length - 1) * 2 >\n\t\t\t\t80;\n\n\t\tif (multiple_lines) {\n\t\t\tchunks.push(c(`\\n${state.indent}\\t`));\n\t\t\tpush_array(chunks, join(elements, c(`,\\n${state.indent}\\t`)));\n\t\t\tchunks.push(c(`\\n${state.indent}`));\n\t\t\tpush_array(chunks, sparse_commas);\n\t\t} else {\n\t\t\tpush_array(chunks, join(elements, c(', ')));\n\t\t\tpush_array(chunks, sparse_commas);\n\t\t}\n\n\t\tchunks.push(c(']'));\n\n\t\treturn chunks;\n\t},\n\n\tObjectExpression(/** @type {ObjectExpression} */ node, state) {\n\t\tif (node.properties.length === 0) {\n\t\t\treturn [c('{}')];\n\t\t}\n\n\t\tlet has_inline_comment = false;\n\n\t\t/** @type {Chunk[]} */\n\t\tconst chunks = [];\n\t\tconst separator = c(', ');\n\n\t\tnode.properties.forEach((p, i) => {\n\t\t\tpush_array(\n\t\t\t\tchunks,\n\t\t\t\thandle(p, {\n\t\t\t\t\t...state,\n\t\t\t\t\tindent: state.indent + '\\t'\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tif (state.comments.length) {\n\t\t\t\t// TODO generalise this, so it works with ArrayExpressions and other things.\n\t\t\t\t// At present, stuff will just get appended to the closest statement/declaration\n\t\t\t\tchunks.push(c(', '));\n\n\t\t\t\twhile (state.comments.length) {\n\t\t\t\t\tconst comment = state.comments.shift();\n\n\t\t\t\t\tchunks.push(\n\t\t\t\t\t\tc(\n\t\t\t\t\t\t\tcomment.type === 'Block'\n\t\t\t\t\t\t\t\t? `/*${comment.value}*/\\n${state.indent}\\t`\n\t\t\t\t\t\t\t\t: `//${comment.value}\\n${state.indent}\\t`\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\n\t\t\t\t\tif (comment.type === 'Line') {\n\t\t\t\t\t\thas_inline_comment = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (i < node.properties.length - 1) {\n\t\t\t\t\tchunks.push(separator);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tconst multiple_lines =\n\t\t\thas_inline_comment || has_newline(chunks) || get_length(chunks) > 40;\n\n\t\tif (multiple_lines) {\n\t\t\tseparator.content = `,\\n${state.indent}\\t`;\n\t\t}\n\n\t\treturn [\n\t\t\tc(multiple_lines ? `{\\n${state.indent}\\t` : `{ `),\n\t\t\t...chunks,\n\t\t\tc(multiple_lines ? `\\n${state.indent}}` : ` }`)\n\t\t];\n\t},\n\n\tProperty(node, state) {\n\t\tconst value = handle(node.value, state);\n\n\t\tif (node.key === node.value) {\n\t\t\treturn value;\n\t\t}\n\n\t\t// special case\n\t\tif (\n\t\t\t!node.computed &&\n\t\t\tnode.value.type === 'AssignmentPattern' &&\n\t\t\tnode.value.left.type === 'Identifier' &&\n\t\t\tnode.value.left.name === node.key.name\n\t\t) {\n\t\t\treturn value;\n\t\t}\n\n\t\tif (\n\t\t\t!node.computed &&\n\t\t\tnode.value.type === 'Identifier' &&\n\t\t\t((node.key.type === 'Identifier' && node.key.name === value[0].content) ||\n\t\t\t\t(node.key.type === 'Literal' && node.key.value === value[0].content))\n\t\t) {\n\t\t\treturn value;\n\t\t}\n\n\t\tconst key = handle(node.key, state);\n\n\t\tif (node.value.type === 'FunctionExpression' && !node.value.id) {\n\t\t\tstate = {\n\t\t\t\t...state,\n\t\t\t\tscope: state.scope_map.get(node.value)\n\t\t\t};\n\n\t\t\tconst chunks = node.kind !== 'init' ? [c(`${node.kind} `)] : [];\n\n\t\t\tif (node.value.async) {\n\t\t\t\tchunks.push(c('async '));\n\t\t\t}\n\t\t\tif (node.value.generator) {\n\t\t\t\tchunks.push(c('*'));\n\t\t\t}\n\n\t\t\tpush_array(chunks, node.computed ? [c('['), ...key, c(']')] : key);\n\t\t\tchunks.push(c('('));\n\t\t\tpush_array(\n\t\t\t\tchunks,\n\t\t\t\tjoin(\n\t\t\t\t\tnode.value.params.map((/** @type {Pattern} */ param) =>\n\t\t\t\t\t\thandle(param, state)\n\t\t\t\t\t),\n\t\t\t\t\tc(', ')\n\t\t\t\t)\n\t\t\t);\n\t\t\tchunks.push(c(') '));\n\t\t\tpush_array(chunks, handle(node.value.body, state));\n\n\t\t\treturn chunks;\n\t\t}\n\n\t\tif (node.computed) {\n\t\t\treturn [c('['), ...key, c(']: '), ...value];\n\t\t}\n\n\t\treturn [...key, c(': '), ...value];\n\t},\n\n\tObjectPattern(node, state) {\n\t\tconst chunks = [c('{ ')];\n\n\t\tfor (let i = 0; i < node.properties.length; i += 1) {\n\t\t\tpush_array(chunks, handle(node.properties[i], state));\n\t\t\tif (i < node.properties.length - 1) chunks.push(c(', '));\n\t\t}\n\n\t\tchunks.push(c(' }'));\n\n\t\treturn chunks;\n\t},\n\n\tSequenceExpression(/** @type {SequenceExpression} */ node, state) {\n\t\tconst expressions = node.expressions.map((e) => handle(e, state));\n\n\t\treturn [c('('), ...join(expressions, c(', ')), c(')')];\n\t},\n\n\tUnaryExpression(node, state) {\n\t\tconst chunks = [c(node.operator)];\n\n\t\tif (node.operator.length > 1) {\n\t\t\tchunks.push(c(' '));\n\t\t}\n\n\t\tif (\n\t\t\tEXPRESSIONS_PRECEDENCE[node.argument.type] <\n\t\t\tEXPRESSIONS_PRECEDENCE.UnaryExpression\n\t\t) {\n\t\t\tchunks.push(c('('));\n\t\t\tpush_array(chunks, handle(node.argument, state));\n\t\t\tchunks.push(c(')'));\n\t\t} else {\n\t\t\tpush_array(chunks, handle(node.argument, state));\n\t\t}\n\n\t\treturn chunks;\n\t},\n\n\tUpdateExpression(node, state) {\n\t\treturn node.prefix\n\t\t\t? [c(node.operator), ...handle(node.argument, state)]\n\t\t\t: [...handle(node.argument, state), c(node.operator)];\n\t},\n\n\tAssignmentExpression(node, state) {\n\t\treturn [\n\t\t\t...handle(node.left, state),\n\t\t\tc(` ${node.operator || '='} `),\n\t\t\t...handle(node.right, state)\n\t\t];\n\t},\n\n\tBinaryExpression(node, state) {\n\t\t/**\n\t\t * @type any[]\n\t\t */\n\t\tconst chunks = [];\n\n\t\t// TODO\n\t\t// const is_in = node.operator === 'in';\n\t\t// if (is_in) {\n\t\t// \t// Avoids confusion in `for` loops initializers\n\t\t// \tchunks.push(c('('));\n\t\t// }\n\n\t\tif (needs_parens(node.left, node, false)) {\n\t\t\tchunks.push(c('('));\n\t\t\tpush_array(chunks, handle(node.left, state));\n\t\t\tchunks.push(c(')'));\n\t\t} else {\n\t\t\tpush_array(chunks, handle(node.left, state));\n\t\t}\n\n\t\tchunks.push(c(` ${node.operator} `));\n\n\t\tif (needs_parens(node.right, node, true)) {\n\t\t\tchunks.push(c('('));\n\t\t\tpush_array(chunks, handle(node.right, state));\n\t\t\tchunks.push(c(')'));\n\t\t} else {\n\t\t\tpush_array(chunks, handle(node.right, state));\n\t\t}\n\n\t\treturn chunks;\n\t},\n\n\tConditionalExpression(node, state) {\n\t\t/**\n\t\t * @type any[]\n\t\t */\n\t\tconst chunks = [];\n\n\t\tif (\n\t\t\tEXPRESSIONS_PRECEDENCE[node.test.type] >\n\t\t\tEXPRESSIONS_PRECEDENCE.ConditionalExpression\n\t\t) {\n\t\t\tpush_array(chunks, handle(node.test, state));\n\t\t} else {\n\t\t\tchunks.push(c('('));\n\t\t\tpush_array(chunks, handle(node.test, state));\n\t\t\tchunks.push(c(')'));\n\t\t}\n\n\t\tconst child_state = { ...state, indent: state.indent + '\\t' };\n\n\t\tconst consequent = handle(node.consequent, child_state);\n\t\tconst alternate = handle(node.alternate, child_state);\n\n\t\tconst multiple_lines =\n\t\t\thas_newline(consequent) ||\n\t\t\thas_newline(alternate) ||\n\t\t\tget_length(chunks) + get_length(consequent) + get_length(alternate) > 50;\n\n\t\tif (multiple_lines) {\n\t\t\tchunks.push(c(`\\n${state.indent}? `));\n\t\t\tpush_array(chunks, consequent);\n\t\t\tchunks.push(c(`\\n${state.indent}: `));\n\t\t\tpush_array(chunks, alternate);\n\t\t} else {\n\t\t\tchunks.push(c(` ? `));\n\t\t\tpush_array(chunks, consequent);\n\t\t\tchunks.push(c(` : `));\n\t\t\tpush_array(chunks, alternate);\n\t\t}\n\n\t\treturn chunks;\n\t},\n\n\tNewExpression(/** @type {NewExpression} */ node, state) {\n\t\tconst chunks = [c('new ')];\n\n\t\tif (\n\t\t\tEXPRESSIONS_PRECEDENCE[node.callee.type] <\n\t\t\t\tEXPRESSIONS_PRECEDENCE.CallExpression ||\n\t\t\thas_call_expression(node.callee)\n\t\t) {\n\t\t\tchunks.push(c('('));\n\t\t\tpush_array(chunks, handle(node.callee, state));\n\t\t\tchunks.push(c(')'));\n\t\t} else {\n\t\t\tpush_array(chunks, handle(node.callee, state));\n\t\t}\n\n\t\t// TODO this is copied from CallExpression — DRY it out\n\t\tconst args = node.arguments.map((arg) =>\n\t\t\thandle(arg, {\n\t\t\t\t...state,\n\t\t\t\tindent: state.indent + '\\t'\n\t\t\t})\n\t\t);\n\n\t\tconst separator = args.some(has_newline) // TODO or length exceeds 80\n\t\t\t? c(',\\n' + state.indent)\n\t\t\t: c(', ');\n\n\t\tchunks.push(c('('));\n\t\tpush_array(chunks, join(args, separator));\n\t\tchunks.push(c(')'));\n\n\t\treturn chunks;\n\t},\n\n\tChainExpression(node, state) {\n\t\treturn handle(node.expression, state);\n\t},\n\n\tCallExpression(/** @type {CallExpression} */ node, state) {\n\t\t/**\n\t\t * @type any[]\n\t\t */\n\t\tconst chunks = [];\n\n\t\tif (\n\t\t\tEXPRESSIONS_PRECEDENCE[node.callee.type] <\n\t\t\tEXPRESSIONS_PRECEDENCE.CallExpression\n\t\t) {\n\t\t\tchunks.push(c('('));\n\t\t\tpush_array(chunks, handle(node.callee, state));\n\t\t\tchunks.push(c(')'));\n\t\t} else {\n\t\t\tpush_array(chunks, handle(node.callee, state));\n\t\t}\n\n\t\tif (/** @type {SimpleCallExpression} */ (node).optional) {\n\t\t\tchunks.push(c('?.'));\n\t\t}\n\n\t\tlet has_inline_comment = false;\n\t\tlet arg_chunks = [];\n\t\touter: for (const arg of node.arguments) {\n\t\t\tconst chunks = [];\n\t\t\twhile (state.comments.length) {\n\t\t\t\tconst comment = state.comments.shift();\n\t\t\t\tif (comment.type === 'Line') {\n\t\t\t\t\thas_inline_comment = true;\n\t\t\t\t\tbreak outer;\n\t\t\t\t}\n\t\t\t\tchunks.push(\n\t\t\t\t\tc(\n\t\t\t\t\t\tcomment.type === 'Block'\n\t\t\t\t\t\t\t? `/*${comment.value}*/ `\n\t\t\t\t\t\t\t: `//${comment.value}`\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t}\n\t\t\tpush_array(chunks, handle(arg, state));\n\t\t\targ_chunks.push(chunks);\n\t\t}\n\n\t\tconst multiple_lines =\n\t\t\thas_inline_comment || arg_chunks.slice(0, -1).some(has_newline); // TODO or length exceeds 80\n\t\tif (multiple_lines) {\n\t\t\t// need to handle args again. TODO find alternative approach?\n\t\t\tconst args = node.arguments.map((arg, i) => {\n\t\t\t\tconst chunks = handle(arg, {\n\t\t\t\t\t...state,\n\t\t\t\t\tindent: `${state.indent}\\t`\n\t\t\t\t});\n\t\t\t\tif (i < node.arguments.length - 1) chunks.push(c(','));\n\t\t\t\twhile (state.comments.length) {\n\t\t\t\t\tconst comment = state.comments.shift();\n\t\t\t\t\tchunks.push(\n\t\t\t\t\t\tc(\n\t\t\t\t\t\t\tcomment.type === 'Block'\n\t\t\t\t\t\t\t\t? ` /*${comment.value}*/ `\n\t\t\t\t\t\t\t\t: ` //${comment.value}`\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn chunks;\n\t\t\t});\n\n\t\t\tchunks.push(c(`(\\n${state.indent}\\t`));\n\t\t\tpush_array(chunks, join(args, c(`\\n${state.indent}\\t`)));\n\t\t\tchunks.push(c(`\\n${state.indent})`));\n\t\t} else {\n\t\t\tchunks.push(c('('));\n\t\t\tpush_array(chunks, join(arg_chunks, c(', ')));\n\t\t\tchunks.push(c(')'));\n\t\t}\n\n\t\treturn chunks;\n\t},\n\n\tMemberExpression(node, state) {\n\t\t/**\n\t\t * @type any[]\n\t\t */\n\t\tconst chunks = [];\n\n\t\tif (\n\t\t\tEXPRESSIONS_PRECEDENCE[node.object.type] <\n\t\t\tEXPRESSIONS_PRECEDENCE.MemberExpression\n\t\t) {\n\t\t\tchunks.push(c('('));\n\t\t\tpush_array(chunks, handle(node.object, state));\n\t\t\tchunks.push(c(')'));\n\t\t} else {\n\t\t\tpush_array(chunks, handle(node.object, state));\n\t\t}\n\n\t\tif (node.computed) {\n\t\t\tif (node.optional) {\n\t\t\t\tchunks.push(c('?.'));\n\t\t\t}\n\t\t\tchunks.push(c('['));\n\t\t\tpush_array(chunks, handle(node.property, state));\n\t\t\tchunks.push(c(']'));\n\t\t} else {\n\t\t\tchunks.push(c(node.optional ? '?.' : '.'));\n\t\t\tpush_array(chunks, handle(node.property, state));\n\t\t}\n\n\t\treturn chunks;\n\t},\n\n\tMetaProperty(node, state) {\n\t\treturn [\n\t\t\t...handle(node.meta, state),\n\t\t\tc('.'),\n\t\t\t...handle(node.property, state)\n\t\t];\n\t},\n\n\tIdentifier(node, state) {\n\t\tlet name = node.name;\n\n\t\tif (name[0] === '@') {\n\t\t\tname = state.getName(name.slice(1));\n\t\t} else if (node.name[0] === '#') {\n\t\t\tconst owner = state.scope.find_owner(node.name);\n\n\t\t\tif (!owner) {\n\t\t\t\tthrow new Error(`Could not find owner for node`);\n\t\t\t}\n\n\t\t\tif (!state.deconflicted.has(owner)) {\n\t\t\t\tstate.deconflicted.set(owner, new Map());\n\t\t\t}\n\n\t\t\tconst deconflict_map = state.deconflicted.get(owner);\n\n\t\t\tif (!deconflict_map.has(node.name)) {\n\t\t\t\tdeconflict_map.set(\n\t\t\t\t\tnode.name,\n\t\t\t\t\tdeconflict(node.name.slice(1), owner.references)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tname = deconflict_map.get(node.name);\n\t\t}\n\n\t\treturn [c(name, node)];\n\t},\n\n\tLiteral(/** @type {Literal} */ node, state) {\n\t\tif (typeof node.value === 'string') {\n\t\t\treturn [\n\t\t\t\t// TODO do we need to handle weird unicode characters somehow?\n\t\t\t\t// str.replace(/\\\\u(\\d{4})/g, (m, n) => String.fromCharCode(+n))\n\t\t\t\tc(\n\t\t\t\t\t(node.raw || JSON.stringify(node.value)).replace(\n\t\t\t\t\t\tre,\n\t\t\t\t\t\t(_m, _i, at, hash, name) => {\n\t\t\t\t\t\t\tif (at) return '@' + name;\n\t\t\t\t\t\t\tif (hash) return '#' + name;\n\t\t\t\t\t\t\tthrow new Error(`this shouldn't happen`);\n\t\t\t\t\t\t}\n\t\t\t\t\t),\n\t\t\t\t\tnode\n\t\t\t\t)\n\t\t\t];\n\t\t}\n\n\t\treturn [c(node.raw || String(node.value), node)];\n\t},\n\n\tPropertyDefinition(/** @type {PropertyDefinition} */ node, state) {\n\t\tconst chunks = [];\n\n\t\tif (node.static) {\n\t\t\tchunks.push(c('static '));\n\t\t}\n\n\t\tif (node.computed) {\n\t\t\tchunks.push(c('['), ...handle(node.key, state), c(']'));\n\t\t} else {\n\t\t\tchunks.push(...handle(node.key, state));\n\t\t}\n\n\t\tif (node.value) {\n\t\t\tchunks.push(c(' = '));\n\n\t\t\tchunks.push(...handle(node.value, state));\n\t\t}\n\n\t\tchunks.push(c(';'));\n\n\t\treturn chunks;\n\t},\n\n\tStaticBlock(/** @type {StaticBlock} */ node, state) {\n\t\tconst chunks = [c('static ')];\n\n\t\tpush_array(chunks, handlers.BlockStatement(node, state));\n\n\t\treturn chunks;\n\t},\n\n\tPrivateIdentifier(/** @type {PrivateIdenifier} */ node, state) {\n\t\tconst chunks = [c('#')];\n\n\t\tpush_array(chunks, [c(node.name, node)]);\n\n\t\treturn chunks;\n\t}\n};\n\nhandlers.ForOfStatement = handlers.ForInStatement;\nhandlers.FunctionExpression = handlers.FunctionDeclaration;\nhandlers.ClassExpression = handlers.ClassDeclaration;\nhandlers.ClassBody = handlers.BlockStatement;\nhandlers.SpreadElement = handlers.RestElement;\nhandlers.ArrayPattern = handlers.ArrayExpression;\nhandlers.LogicalExpression = handlers.BinaryExpression;\nhandlers.AssignmentPattern = handlers.AssignmentExpression;\n"
  },
  {
    "path": "src/print/index.js",
    "content": "import * as perisopic from 'periscopic';\nimport { handle } from './handlers.js';\nimport { encode } from '@jridgewell/sourcemap-codec';\n\n/** @type {(str?: string) => string} str */\nlet btoa = () => {\n\tthrow new Error(\n\t\t'Unsupported environment: `window.btoa` or `Buffer` should be supported.'\n\t);\n};\n\nif (typeof window !== 'undefined' && typeof window.btoa === 'function') {\n\tbtoa = (str) => window.btoa(unescape(encodeURIComponent(str)));\n} else if (typeof Buffer === 'function') {\n\tbtoa = (str) => Buffer.from(str, 'utf-8').toString('base64');\n}\n\n/** @typedef {import('estree').Node} Node */\n\n/**\n * @typedef {{\n *   file?: string;\n *   sourceMapSource?: string;\n *   sourceMapContent?: string;\n *   sourceMapEncodeMappings?: boolean; // default true\n *   getName?: (name: string) => string;\n * }} PrintOptions\n */\n\n/**\n * @param {Node} node\n * @param {PrintOptions} opts\n * @returns {{ code: string, map: any }} // TODO\n */\nexport function print(node, opts = {}) {\n\tif (Array.isArray(node)) {\n\t\treturn print(\n\t\t\t{\n\t\t\t\ttype: 'Program',\n\t\t\t\tbody: node,\n\t\t\t\tsourceType: 'module'\n\t\t\t},\n\t\t\topts\n\t\t);\n\t}\n\n\tconst {\n\t\tgetName = /** @param {string} x */ (x) => {\n\t\t\tthrow new Error(`Unhandled sigil @${x}`);\n\t\t}\n\t} = opts;\n\n\tlet { map: scope_map, scope } = perisopic.analyze(node);\n\tconst deconflicted = new WeakMap();\n\n\tconst chunks = handle(node, {\n\t\tindent: '',\n\t\tgetName,\n\t\tscope,\n\t\tscope_map,\n\t\tdeconflicted,\n\t\tcomments: []\n\t});\n\n\t/** @typedef {[number, number, number, number]} Segment */\n\n\tlet code = '';\n\tlet current_column = 0;\n\n\t/** @type {Segment[][]} */\n\tlet mappings = [];\n\n\t/** @type {Segment[]} */\n\tlet current_line = [];\n\n\tfor (let i = 0; i < chunks.length; i += 1) {\n\t\tconst chunk = chunks[i];\n\n\t\tcode += chunk.content;\n\n\t\tif (chunk.loc) {\n\t\t\tcurrent_line.push([\n\t\t\t\tcurrent_column,\n\t\t\t\t0, // source index is always zero\n\t\t\t\tchunk.loc.start.line - 1,\n\t\t\t\tchunk.loc.start.column\n\t\t\t]);\n\t\t}\n\n\t\tfor (let i = 0; i < chunk.content.length; i += 1) {\n\t\t\tif (chunk.content[i] === '\\n') {\n\t\t\t\tmappings.push(current_line);\n\t\t\t\tcurrent_line = [];\n\t\t\t\tcurrent_column = 0;\n\t\t\t} else {\n\t\t\t\tcurrent_column += 1;\n\t\t\t}\n\t\t}\n\n\t\tif (chunk.loc) {\n\t\t\tcurrent_line.push([\n\t\t\t\tcurrent_column,\n\t\t\t\t0, // source index is always zero\n\t\t\t\tchunk.loc.end.line - 1,\n\t\t\t\tchunk.loc.end.column\n\t\t\t]);\n\t\t}\n\t}\n\n\tmappings.push(current_line);\n\n\tconst map = {\n\t\tversion: 3,\n\t\t/** @type {string[]} */\n\t\tnames: [],\n\t\tsources: [opts.sourceMapSource || null],\n\t\tsourcesContent: [opts.sourceMapContent || null],\n\t\tmappings:\n\t\t\topts.sourceMapEncodeMappings == undefined || opts.sourceMapEncodeMappings\n\t\t\t\t? encode(mappings)\n\t\t\t\t: mappings\n\t};\n\n\tObject.defineProperties(map, {\n\t\ttoString: {\n\t\t\tenumerable: false,\n\t\t\tvalue: function toString() {\n\t\t\t\treturn JSON.stringify(this);\n\t\t\t}\n\t\t},\n\t\ttoUrl: {\n\t\t\tenumerable: false,\n\t\t\tvalue: function toUrl() {\n\t\t\t\treturn (\n\t\t\t\t\t'data:application/json;charset=utf-8;base64,' + btoa(this.toString())\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t});\n\n\treturn {\n\t\tcode,\n\t\tmap\n\t};\n}\n"
  },
  {
    "path": "src/utils/comments.js",
    "content": "import { re } from './id.js';\n\n/** @typedef {import('estree').Comment} Comment */\n/** @typedef {import('estree').Node} Node */\n\n/**\n * @typedef {Node & {\n *   start: number;\n *   end: number;\n *   has_trailing_newline?: boolean\n * }} NodeWithLocation\n */\n\n/**\n * @typedef {Comment & {\n *   start: number;\n *   end: number;\n *   has_trailing_newline?: boolean\n * }} CommentWithLocation\n */\n\n/**\n * @param {CommentWithLocation[]} comments\n * @param {string} raw\n */\nexport const get_comment_handlers = (comments, raw) => ({\n\t// pass to acorn options\n\t/**\n\t * @param {boolean} block\n\t * @param {string} value\n\t * @param {number} start\n\t * @param {number} end\n\t */\n\tonComment: (block, value, start, end) => {\n\t\tif (block && /\\n/.test(value)) {\n\t\t\tlet a = start;\n\t\t\twhile (a > 0 && raw[a - 1] !== '\\n') a -= 1;\n\n\t\t\tlet b = a;\n\t\t\twhile (/[ \\t]/.test(raw[b])) b += 1;\n\n\t\t\tconst indentation = raw.slice(a, b);\n\t\t\tvalue = value.replace(new RegExp(`^${indentation}`, 'gm'), '');\n\t\t}\n\n\t\tcomments.push({ type: block ? 'Block' : 'Line', value, start, end });\n\t},\n\n\t// pass to estree-walker options\n\t/** @param {NodeWithLocation} node */\n\tenter(node) {\n\t\tlet comment;\n\n\t\twhile (comments[0] && comments[0].start < node.start) {\n\t\t\tcomment = comments.shift();\n\n\t\t\tcomment.value = comment.value.replace(\n\t\t\t\tre,\n\t\t\t\t(match, id, at, hash, value) => {\n\t\t\t\t\tif (hash) return `#${value}`;\n\t\t\t\t\tif (at) return `@${value}`;\n\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tconst next = comments[0] || node;\n\t\t\tcomment.has_trailing_newline =\n\t\t\t\tcomment.type === 'Line' ||\n\t\t\t\t/\\n/.test(raw.slice(comment.end, next.start));\n\n\t\t\t(node.leadingComments || (node.leadingComments = [])).push(comment);\n\t\t}\n\t},\n\n\t/** @param {NodeWithLocation} node */\n\tleave(node) {\n\t\tif (comments[0]) {\n\t\t\tconst slice = raw.slice(node.end, comments[0].start);\n\n\t\t\tif (/^[,) \\t]*$/.test(slice)) {\n\t\t\t\tnode.trailingComments = [comments.shift()];\n\t\t\t}\n\t\t}\n\t}\n});\n"
  },
  {
    "path": "src/utils/id.js",
    "content": "// generate an ID that is, to all intents and purposes, unique\nexport const id = Math.round(Math.random() * 1e20).toString(36);\nexport const re = new RegExp(`_${id}_(?:(\\\\d+)|(AT)|(HASH))_(\\\\w+)?`, 'g');\n"
  },
  {
    "path": "src/utils/push_array.js",
    "content": "/**\n * Does `array.push` for all `items`. Needed because `array.push(...items)` throws\n * \"Maximum call stack size exceeded\" when `items` is too big of an array.\n *\n * @param {any[]} array\n * @param {any[]} items\n */\nexport function push_array(array, items) {\n\tfor (let i = 0; i < items.length; i++) {\n\t\tarray.push(items[i]);\n\t}\n}\n"
  },
  {
    "path": "test/samples/array-expressions/expected.js",
    "content": "a = [1, 2, 3];\n\nb = [\n\t'the quick brown fox jumps over the lazy dog',\n\t'the quick brown fox jumps over the lazy dog',\n\t'the quick brown fox jumps over the lazy dog',\n\t4\n];\n\nsparse = [,,,];"
  },
  {
    "path": "test/samples/array-expressions/input.js",
    "content": "export default ({ b }) => b`\na = [1, 2, 3];\n\nb = ['the quick brown fox jumps over the lazy dog', 'the quick brown fox jumps over the lazy dog', 'the quick brown fox jumps over the lazy dog', 4];\n\nsparse = [,,,];\n`;"
  },
  {
    "path": "test/samples/arrow-function-as-statement/expected.js",
    "content": "() => {\n\n};"
  },
  {
    "path": "test/samples/arrow-function-as-statement/input.js",
    "content": "export default ({ b }) => b`() => {};`;"
  },
  {
    "path": "test/samples/arrow-function-assignment-object-pattern/expected.js",
    "content": "let a = () => ({ x } = { x: 42 });"
  },
  {
    "path": "test/samples/arrow-function-assignment-object-pattern/input.js",
    "content": "export default ({ b }) => b`let a = () => ({ x } = { x: 42 });`;\n"
  },
  {
    "path": "test/samples/arrow-function-parenthesized/expected.js",
    "content": "foo || (bar => bar);"
  },
  {
    "path": "test/samples/arrow-function-parenthesized/input.js",
    "content": "export default ({ b, x }) => b`foo || ${x`bar => bar`}`;"
  },
  {
    "path": "test/samples/at-prefix/expected.js",
    "content": "FOO(bar)"
  },
  {
    "path": "test/samples/at-prefix/input.js",
    "content": "export default ({ x }) => x`@foo(bar)`;"
  },
  {
    "path": "test/samples/await-precedence/expected.js",
    "content": "await (a || b);\nawait c;"
  },
  {
    "path": "test/samples/await-precedence/input.js",
    "content": "export default ({ b }) => b`\nawait (a || b);\nawait c;\n`;"
  },
  {
    "path": "test/samples/basic/expected.js",
    "content": "foo({ a: 1 });\n\"#foo\";\n\"@foo\";"
  },
  {
    "path": "test/samples/basic/input.js",
    "content": "export default ({ b }) => b`\nfoo({ a: 1 });\n\"#foo\";\n\"@foo\";\n`;"
  },
  {
    "path": "test/samples/bigint/expected.js",
    "content": "12345n"
  },
  {
    "path": "test/samples/bigint/input.js",
    "content": "export default ({ x }) => x`12345n`;"
  },
  {
    "path": "test/samples/break-continue/expected.js",
    "content": "x: for (let i = 0; i < 10; i += 1) {\n\tif (should_break) {\n\t\tbreak;\n\t}\n\n\tif (should_break_with_label) {\n\t\tbreak x;\n\t}\n\n\tif (should_continue) {\n\t\tcontinue;\n\t}\n\n\tif (should_continue_with_label) {\n\t\tcontinue x;\n\t}\n}"
  },
  {
    "path": "test/samples/break-continue/input.js",
    "content": "export default ({ b }) => b`\nx: for (let i = 0; i < 10; i += 1) {\n\tif (should_break) {\n\t\tbreak;\n\t}\n\n\tif (should_break_with_label) {\n\t\tbreak x;\n\t}\n\n\tif (should_continue) {\n\t\tcontinue;\n\t}\n\n\tif (should_continue_with_label) {\n\t\tcontinue x;\n\t}\n}\n`;"
  },
  {
    "path": "test/samples/call-expressions/expected.js",
    "content": "x(a, b, c);\n\nx(a, b, () => {\n\tconsole.log('c');\n});\n\nx(\n\ta,\n\t() => {\n\t\tconsole.log('b');\n\t},\n\t() => {\n\t\tconsole.log('c');\n\t}\n);"
  },
  {
    "path": "test/samples/call-expressions/input.js",
    "content": "export default ({ b }) => b`\nx(a, b, c);\n\nx(a, b, () => {\n\tconsole.log('c');\n});\n\nx(a, () => {\n\tconsole.log('b');\n}, () => {\n\tconsole.log('c');\n});\n`;"
  },
  {
    "path": "test/samples/chain-expressions/expected.js",
    "content": "foo?.bar.baz;\nx?.(a, b, c);\nx()?.();"
  },
  {
    "path": "test/samples/chain-expressions/input.js",
    "content": "export default ({ b }) => b`\nfoo?.bar.baz;\nx?.(a, b, c);\nx()?.()\n`;\n"
  },
  {
    "path": "test/samples/class-private/expected.js",
    "content": "class A {\n\t#p;\n\tstatic #p1;\n\tstatic #p2;\n\n\tstatic is(obj) {\n\t\treturn #p2 in obj;\n\t}\n\n\tget #p3() {\n\t\t\n\t}\n\n\tset #p3(v) {\n\t\t\n\t}\n\n\t#m1() {\n\t\treturn this.#p1;\n\t}\n\n\tstatic #m2() {\n\t\t\n\t}\n\n\t*#m3() {\n\t\t\n\t}\n\n\tasync #m4() {\n\t\t\n\t}\n}"
  },
  {
    "path": "test/samples/class-private/input.js",
    "content": "export default ({ parse }) => parse(`class A {\n\t#p\n\tstatic #p1\n\tstatic #p2\n\n\tstatic is(obj) {\n\t\treturn #p2 in obj\n\t}\n\n\tget #p3() {}\n\tset #p3(v) {}\n\t#m1() {\n\t\treturn this.#p1\n\t}\n\tstatic #m2() {}\n\t*#m3() {}\n\tasync #m4() {}\n}`);"
  },
  {
    "path": "test/samples/class-property/expected.js",
    "content": "class Foo {\n\thi;\n\tstatic foo = 1;\n\t[KEY] = 2;\n}"
  },
  {
    "path": "test/samples/class-property/input.js",
    "content": "export default ({ b }) => b`\nclass Foo {\n\thi\n\tstatic foo = 1;\n\t[KEY] = 2\n}\n`;\n"
  },
  {
    "path": "test/samples/class-static-block/expected.js",
    "content": "class Foo {\n\tstatic {\n\t\tthis.abc = 1;\n\t}\n}"
  },
  {
    "path": "test/samples/class-static-block/input.js",
    "content": "export default ({ b }) => b`\nclass Foo {\n    static {\n        this.abc = 1;\n    }\n}\n`;"
  },
  {
    "path": "test/samples/comment-block/expected.js",
    "content": "/* comment before a node\n * second line */\nconsole.log(1);\n\nconsole.log(2); /* comment on same line as node */\n\nconst obj = {\n\tfoo: 1, /* comment in middle of object */\n\tbar: 2\n};"
  },
  {
    "path": "test/samples/comment-block/input.js",
    "content": "export default ({ b }) => b`\n\t/* comment before a node\n\t * second line */\n\tconsole.log(1);\n\n\tconsole.log(2); /* comment on same line as node */\n\n\tconst obj = {\n\t\tfoo: 1, /* comment in middle of object */\n\t\tbar: 2\n\t};\n`;"
  },
  {
    "path": "test/samples/comment-block-with-sigil/expected.js",
    "content": "a = /* #b */ c;"
  },
  {
    "path": "test/samples/comment-block-with-sigil/input.js",
    "content": "export default ({ b }) => b`\n\ta = /* #b */ c\n`;"
  },
  {
    "path": "test/samples/comment-inline/expected.js",
    "content": "// comment before a node\n// second line\nconsole.log(1);\n\nconsole.log(2); // comment on same line as node\n\nconst obj = {\n\tfoo: 1, // comment in middle of object\n\tbar: 2\n};\n\nfunction bar() {\n\treturn /*result*/ foo;\n}"
  },
  {
    "path": "test/samples/comment-inline/input.js",
    "content": "export default ({ b }) => b`\n\t// comment before a node\n\t// second line\n\tconsole.log(1);\n\n\tconsole.log(2); // comment on same line as node\n\n\tconst obj = {\n\t\tfoo: 1, // comment in middle of object\n\t\tbar: 2\n\t};\n\n\tfunction bar () {\n\t\treturn /*result*/ foo;\n\t}\n`;"
  },
  {
    "path": "test/samples/comment-inline-inserted/expected.js",
    "content": "// comment before an inserted block\n\"use strict\";\n\n// comment before an inserted node\nimport { foo } from \"wherever\";"
  },
  {
    "path": "test/samples/comment-inline-inserted/input.js",
    "content": "export default ({ b, x }) => {\n\tconst insert = b`\"use strict\";`;\n\n\tconst node = {\n\t\ttype: 'ImportDeclaration',\n\t\tspecifiers: [{\n\t\t\ttype: 'ImportSpecifier',\n\t\t\tlocal: { type: 'Identifier', name: 'foo' },\n\t\t\timported: { type: 'Identifier', name: 'foo' }\n\t\t}],\n\t\tsource: { type: 'Literal', value: 'wherever' }\n\t};\n\n\treturn b`\n\t\t// comment before an inserted block\n\t\t${insert}\n\n\t\t// comment before an inserted node\n\t\t${node}\n\t`;\n};"
  },
  {
    "path": "test/samples/comment-interpolated/expected.js",
    "content": "a = /* the answer */ 42"
  },
  {
    "path": "test/samples/comment-interpolated/input.js",
    "content": "export default ({ x }) => x`a = /* ${\"the answer\"} */ ${42}`"
  },
  {
    "path": "test/samples/comment-mixed-trailing/expected.js",
    "content": "function foo() {\n\n} // hey1\n/*\nhey2\n*/"
  },
  {
    "path": "test/samples/comment-mixed-trailing/input.js",
    "content": "export default ({ b }) => b`\n\tfunction foo() {\n\t\t// hey1\n\t\t/*\n\t\they2\n\t\t*/\n\t}\n`;"
  },
  {
    "path": "test/samples/comment-within-call-expression/expected.js",
    "content": "console.log(null, /* xxx */ new Date(), /** zzz */ a.b.c()); // www\n\nconsole.log(null, // foo\nnew Date());\n\nconsole.log(\n\tnull, // foo\n\tnew Date()\n);\n\nconsole.log(null, /* xxx */ function (a, b) {\n\t// yyy\n\treturn a + b;\n});\n\nconsole.log(\"1\");"
  },
  {
    "path": "test/samples/comment-within-call-expression/input.js",
    "content": "export default ({ b }) => b`\n\tconsole.log(null, /* xxx */ new Date(), /** zzz */ a.b.c()); // www\n\tconsole.log(null,\n\t\t// foo\n\t\tnew Date()\n\t);\n\tconsole.log(null, // foo\n\t\tnew Date()\n\t);\n\tconsole.log(null, /* xxx */ function (a, b) {\n\t\t// yyy\n\t\treturn a + b;\n\t})\n\tconsole.log(\"1\");\n`;"
  },
  {
    "path": "test/samples/comment-within-parentheses/expected.js",
    "content": "function foo() {\n\treturn (// hey\n\tabc);\n}\n\nfunction bar() {\n\treturn (/* hey */\n\tabc);\n}"
  },
  {
    "path": "test/samples/comment-within-parentheses/input.js",
    "content": "export default ({ b }) => b`\n\tfunction foo() {\n\t\treturn (\n\t\t\t// hey\n\t\t\tabc\n\t\t)\n\t}\n\tfunction bar() {\n\t\treturn (\n\t\t\t/* hey */\n\t\t\tabc\n\t\t)\n\t}\n`;"
  },
  {
    "path": "test/samples/deconflict-let/expected.js",
    "content": "function foo() {\n\tlet i = 2;\n\n\tfor (var i$1 = 0; i$1 < 10; i$1 += 1) {\n\t\tconsole.log(i$1 * i);\n\t}\n}"
  },
  {
    "path": "test/samples/deconflict-let/input.js",
    "content": "export default ({ b }) => b`\nfunction foo() {\n\tlet i = 2;\n\n\tfor (var #i = 0; #i <10; #i += 1) {\n\t\tconsole.log(#i * i);\n\t}\n}`;"
  },
  {
    "path": "test/samples/deconflict-method/expected.js",
    "content": "obj = {\n\ta(foo) {\n\t\t\n\t}\n};"
  },
  {
    "path": "test/samples/deconflict-method/input.js",
    "content": "export default ({ b, x }) => b`\nobj = {\n\ta(#foo) {}\n};`;"
  },
  {
    "path": "test/samples/destructured-declaration/expected.js",
    "content": "const { answer = 42 } = life_the_universe_and_everything;"
  },
  {
    "path": "test/samples/destructured-declaration/input.js",
    "content": "export default ({ b }) => b`\nconst { answer = 42 } = life_the_universe_and_everything;\n`;"
  },
  {
    "path": "test/samples/empty-body/expected.js",
    "content": "while (a) ;\ndo ; while (a);\nfor (; ; ) ;\nif (a) ;\nif (a) ; else ;\nif (a) ; else if (b) ; else ;"
  },
  {
    "path": "test/samples/empty-body/input.js",
    "content": "export default ({ b }) => b`\n\twhile(a);\n\tdo;while (a);\n\tfor(;;) ;\n\tif (a) ;\n\tif (a) ; else ;\n\tif (a) ; else if (b) ; else ;\n`;"
  },
  {
    "path": "test/samples/export/expected.js",
    "content": "const foo = 42;\nexport { foo };"
  },
  {
    "path": "test/samples/export/input.js",
    "content": "export default ({ b }) => b`\nconst #foo = 42;\nexport { #foo as foo };`"
  },
  {
    "path": "test/samples/function-declaration/expected.js",
    "content": "function foo() {\n\tbar\n}\n\nfunction foo$1() {\n\tbar\n}"
  },
  {
    "path": "test/samples/function-declaration/input.js",
    "content": "export default ({ b }) => b`\nfunction foo() {\n\tbar;\n}\nfunction #foo() {\n\tbar;\n}`"
  },
  {
    "path": "test/samples/hash-prefix/expected.js",
    "content": "function foo(bar$1) {\n\treturn bar$1 * bar;\n}"
  },
  {
    "path": "test/samples/hash-prefix/input.js",
    "content": "export default ({ x }) => x`\nfunction foo(#bar) {\n\treturn #bar * bar;\n}`;"
  },
  {
    "path": "test/samples/hash-prefix-arrow/expected.js",
    "content": "const foo = bar => bar * 2;"
  },
  {
    "path": "test/samples/hash-prefix-arrow/input.js",
    "content": "export default ({ b }) => b`const foo = #bar => #bar * 2`;"
  },
  {
    "path": "test/samples/hash-prefix-for-loop-head/expected.js",
    "content": "for (let i$1 = 0; i$1 < 10; i$1 += 1) {\n\tconsole.log(i * i$1);\n}"
  },
  {
    "path": "test/samples/hash-prefix-for-loop-head/input.js",
    "content": "export default ({ x, b }) => {\n\tconst i = x`i`;\n\n\treturn b`\n\t\tfor (let #i = 0; #i < 10; #i += 1) {\n\t\t\tconsole.log(${i} * #i);\n\t\t}`;\n};"
  },
  {
    "path": "test/samples/hash-prefix-reused/expected.js",
    "content": "function foo(bar$1) {\n\tconst bar = 'x';\n\tbar$1 += 1;\n\n\treturn bar => {\n\t\tconsole.log(bar);\n\t};\n}"
  },
  {
    "path": "test/samples/hash-prefix-reused/input.js",
    "content": "export default ({ x }) => {\n\tconst bar = x`#bar`;\n\n\treturn x`\n\t\tfunction foo(#bar) {\n\t\t\tconst bar = 'x';\n\n\t\t\t${bar} += 1;\n\n\t\t\treturn (#bar) => {\n\t\t\t\tconsole.log(${bar});\n\t\t\t};\n\t\t}\n\t`;\n};"
  },
  {
    "path": "test/samples/import/expected.js",
    "content": "import { foo } from 'x';\nimport { bar } from 'y';\nimport('baz').then(blah);"
  },
  {
    "path": "test/samples/import/input.js",
    "content": "export default ({ b }) => {\n\tconst bar = b`import { bar } from 'y';`[0];\n\n\treturn b`\n\timport { foo as #foo } from 'x';\n\t${bar};\n\n\timport('baz').then(blah)`\n};"
  },
  {
    "path": "test/samples/import-as/expected.js",
    "content": "import { foo as bar } from 'x';"
  },
  {
    "path": "test/samples/import-as/input.js",
    "content": "export default ({ b }) => b`\nimport { foo as bar } from 'x';`"
  },
  {
    "path": "test/samples/import-default-and-named/expected.js",
    "content": "import a, { b, c as d } from 'x';"
  },
  {
    "path": "test/samples/import-default-and-named/input.js",
    "content": "export default ({ b }) => b`import a, { b, c as d } from 'x';`;"
  },
  {
    "path": "test/samples/import-many/expected.js",
    "content": "import {\n\tabc,\n\tbcd,\n\tcde,\n\tdef,\n\tefg,\n\tfgh,\n\tghi,\n\thij,\n\tijk,\n\tjkl,\n\tklm,\n\tlmn,\n\tmno,\n\tnop,\n\topq\n} from 'x';"
  },
  {
    "path": "test/samples/import-many/input.js",
    "content": "export default ({ b }) => b`\nimport { abc, bcd, cde, def, efg, fgh, ghi, hij, ijk, jkl, klm, lmn, mno, nop, opq } from 'x';`"
  },
  {
    "path": "test/samples/inserted-parameter/expected.js",
    "content": "function foo(bar) {\n\treturn bar * 2;\n}"
  },
  {
    "path": "test/samples/inserted-parameter/input.js",
    "content": "export default ({ x }) => {\n\tconst param = x`bar`;\n\n\treturn x`function foo(${param}) {\n\t\treturn ${param} * 2;\n\t}`;\n};"
  },
  {
    "path": "test/samples/inserted-parameters/expected.js",
    "content": "function foo(bar, baz) {\n\treturn bar * baz;\n}"
  },
  {
    "path": "test/samples/inserted-parameters/input.js",
    "content": "export default ({ x }) => {\n\tconst bar = x`bar`;\n\tconst baz = x`baz`;\n\n\tconst params = [bar, baz];\n\n\treturn x`function foo(${params}) {\n\t\treturn ${bar} * ${baz};\n\t}`;\n};"
  },
  {
    "path": "test/samples/logical-expression/expected.js",
    "content": "a ?? (b || c);\n(a ?? b) || c;"
  },
  {
    "path": "test/samples/logical-expression/input.js",
    "content": "export default ({ b }) => {\n  return b`\n    a ?? (b || c);\n    (a ?? b) || c;\n  `;\n}"
  },
  {
    "path": "test/samples/meta-property/expected.js",
    "content": "function foo() {\n\tconsole.log(new.target);\n}"
  },
  {
    "path": "test/samples/meta-property/input.js",
    "content": "export default ({ b }) => b`\nfunction foo() {\n\tconsole.log(new.target);\n}\n`;"
  },
  {
    "path": "test/samples/method/expected.js",
    "content": "obj = {\n\tfoo() {\n\t\tconsole.log('foo');\n\t},\n\tasync bar() {\n\t\tconsole.log('bar');\n\t},\n\t*baz() {\n\t\tconsole.log('baz');\n\t}\n};"
  },
  {
    "path": "test/samples/method/input.js",
    "content": "export default ({ b }) => b`obj = {\n\tfoo() {\n\t\tconsole.log('foo');\n\t},\n\tasync bar() {\n\t\tconsole.log('bar');\n\t},\n\tbaz: function* () {\n\t\tconsole.log('baz');\n\t}\n}`;"
  },
  {
    "path": "test/samples/nested-blocks/expected.js",
    "content": "console.log(one);\nconsole.log(two);\nconsole.log(three);\nconsole.log(four);"
  },
  {
    "path": "test/samples/nested-blocks/input.js",
    "content": "export default ({ b }) => {\n\tconst one = b`console.log(one);`;\n\n\tconst two = b`\n\t\t${one}\n\t\tconsole.log(two);\n\t`;\n\n\tconst three = b`\n\t\t${two}\n\t\tconsole.log(three);\n\t`;\n\n\treturn b`\n\t\t${three}\n\t\tconsole.log(four);\n\t`;\n};"
  },
  {
    "path": "test/samples/nested-blocks-b/expected.js",
    "content": "let foo = bar;\nreturn { hello: 'world' };"
  },
  {
    "path": "test/samples/nested-blocks-b/input.js",
    "content": "export default ({ b, x }) => {\n\tconst vars = [{ id: x`foo`, init: x`bar` }];\n\n\tconst return_value = x`{\n\t\thello: 'world'\n\t}`;\n\n\tconst returned = b`return ${return_value};`;\n\n\treturn b`\n\t\t${vars.map(({ id, init }) => {\n\t\t\treturn init\n\t\t\t\t? b`let ${id} = ${init}`\n\t\t\t\t: b`let ${id}`;\n\t\t})}\n\n\t\t${returned}\n\t`;\n};"
  },
  {
    "path": "test/samples/object-expressions/expected.js",
    "content": "obj = { foo, bar, baz: qux };\nobj = { \"1\": \"1\" };\nobj = { true: true };\nobj = { foo };\nobj = { [foo]: foo };\nobj = { [foo]: \"foo\" };\nlet blah;\nobj = { blah };\nobj = { blah };\nobj = { a: b };\n\nobj = {\n\tmethod() {\n\t\tconsole.log('hello');\n\t}\n};\n\nempty = {};\nopts = opts || {};\n\nobj = {\n\tget foo() {\n\t\treturn _foo;\n\t},\n\tset foo(value) {\n\t\t_foo = value;\n\t},\n\tget [foo]() {\n\t\treturn _foo;\n\t},\n\tset [foo](value) {\n\t\t_foo = value;\n\t}\n};"
  },
  {
    "path": "test/samples/object-expressions/input.js",
    "content": "export default ({ b, p }) => {\n\tconst x = p`a:b`;\n\n\treturn b`\n\t\tobj = {\n\t\t\tfoo: foo,\n\t\t\tbar,\n\t\t\tbaz: qux\n\t\t};\n\n\t\tobj = { \"1\": \"1\" };\n\n\t\tobj = { true: true };\n\n\t\tobj = { \"foo\": foo };\n\n\t\tobj = { [foo]: foo };\n\n\t\tobj = { [foo]: \"foo\" };\n\n\t\tlet #blah;\n\t\tobj = { blah: #blah };\n\t\tobj = { 'blah': #blah };\n\n\t\tobj = { ${x} }\n\n\t\tobj = {\n\t\t\tmethod: function() {\n\t\t\t\tconsole.log('hello');\n\t\t\t}\n\t\t}\n\n\t\tempty = {  }\n\n\t\topts = opts || {}\n\n\t\tobj = {\n\t\t\tget foo() {\n\t\t\t\treturn _foo;\n\t\t\t},\n\n\t\t\tset foo(value) {\n\t\t\t\t_foo = value;\n\t\t\t},\n\n\t\t\tget [foo]() {\n\t\t\t\treturn _foo;\n\t\t\t},\n\n\t\t\tset [foo](value) {\n\t\t\t\t_foo = value;\n\t\t\t}\n\t\t}`; // TODO would be nice if the {} didn't become ({})\n};\n"
  },
  {
    "path": "test/samples/parenthesized-expression/expected.js",
    "content": "a + b"
  },
  {
    "path": "test/samples/parenthesized-expression/input.js",
    "content": "export default ({ x }) => ({\n\ttype: 'ParenthesizedExpression',\n\texpression: x`a + b`\n});"
  },
  {
    "path": "test/samples/regex/expected.js",
    "content": "/(?:^\\xb1\\X\\u765F)?/"
  },
  {
    "path": "test/samples/regex/input.js",
    "content": "export default ({ x }) => x`/(?:^\\\\xb1\\\\X\\\\u765F)?/`;"
  },
  {
    "path": "test/samples/removes-parens/expected.js",
    "content": "a = b + c"
  },
  {
    "path": "test/samples/removes-parens/input.js",
    "content": "export default ({ x }) => x`\na  =  (b + c)\n`;"
  },
  {
    "path": "test/samples/sourcemap/expected.js",
    "content": "const a = 42;\n\nfunction foo(value) {\n\tconsole.log(value);\n}\n\nfoo(a);"
  },
  {
    "path": "test/samples/sourcemap/input.js",
    "content": "import * as acorn from 'acorn';\n\nexport default ({ b, x }) => {\n\tconst decl = acorn.parse(`function foo(value) {\n\t\tconsole.log(value);\n\t}`, {\n\t\tlocations: true,\n\t\tecmaVersion: 2020\n\t}).body[0];\n\n\n\treturn b`\n\t\tconst a = 42;\n\n\t\t${decl}\n\n\t\tfoo(a);\n\t`;\n}"
  },
  {
    "path": "test/samples/string-literal/expected.js",
    "content": "let a = 'foo';\nlet b = \"foo\";\nlet c = `foo`;\nlet d = '\\n\\t\\ta\\n\\t\\tb\\n\\t\\tc\\n\\t';\nlet e = \"\\n\\t\\ta\\n\\t\\tb\\n\\t\\tc\\n\\t\";\n\nlet f = `\n\t\ta\n\t\tb\n\t\tc\n\t`;"
  },
  {
    "path": "test/samples/string-literal/input.js",
    "content": "export default ({ b }) => {\n\tconst value = 'foo';\n\tconst multiline = `\n\t\ta\n\t\tb\n\t\tc\n\t`;\n\t\n\treturn b`\n\t\tlet a = '${value}';\n\t\tlet b = \"${value}\";\n\t\tlet c = \\`${value}\\`;\n\t\tlet d = '${multiline}';\n\t\tlet e = \"${multiline}\";\n\t\tlet f = \\`${multiline}\\`;\n\t`;\n};\n"
  },
  {
    "path": "test/samples/switch/expected.js",
    "content": "switch (foo) {\n\tcase 1:\n\t\tblah();\n\t\tbreak;\n\tcase 2:\n\t\tblah();\n\tdefault:\n\t\tblah();\n}"
  },
  {
    "path": "test/samples/switch/input.js",
    "content": "export default ({ b }) => b`\nswitch (foo) {\n\tcase 1:\n\t\tblah();\n\t\tbreak;\n\n\tcase 2:\n\t\tblah();\n\n\tdefault:\n\t\tblah();\n}\n`;"
  },
  {
    "path": "test/samples/tagged-template/expected.js",
    "content": "foo`bar`;"
  },
  {
    "path": "test/samples/tagged-template/input.js",
    "content": "export default ({ b }) => b`\nfoo\\`bar\\`;\n`;"
  },
  {
    "path": "test/samples/try-catch/expected.js",
    "content": "try {\n\tfoo();\n} catch {\n\tbar();\n}\n\ntry {\n\tfoo();\n} catch(e) {\n\tbar(e);\n} finally {\n\tbaz();\n}"
  },
  {
    "path": "test/samples/try-catch/input.js",
    "content": "export default ({ b }) => b`\ntry {\n\tfoo();\n} catch {\n\tbar();\n}\n\ntry {\n\tfoo();\n} catch(e) {\n\tbar(e);\n} finally {\n\tbaz();\n}\n`;"
  },
  {
    "path": "test/samples/var-declaration/expected.js",
    "content": "const obj = {\n\ta,\n\tb,\n\tc,\n\td,\n\te,\n\tf,\n\tg,\n\th,\n\ti,\n\tj,\n\tk,\n\tl,\n\tm,\n\tn,\n\to,\n\tp,\n\tq,\n\tr,\n\ts,\n\tt,\n\tu,\n\tv,\n\tw,\n\tx,\n\ty,\n\tz\n};\n\nconst obj2 = {\n\t\ta,\n\t\tb,\n\t\tc,\n\t\td,\n\t\te,\n\t\tf,\n\t\tg,\n\t\th,\n\t\ti,\n\t\tj,\n\t\tk,\n\t\tl,\n\t\tm,\n\t\tn,\n\t\to,\n\t\tp,\n\t\tq,\n\t\tr,\n\t\ts,\n\t\tt,\n\t\tu,\n\t\tv,\n\t\tw,\n\t\tx,\n\t\ty,\n\t\tz\n\t},\n\ta = 1,\n\tb = 2;"
  },
  {
    "path": "test/samples/var-declaration/input.js",
    "content": "export default ({ b }) => b`\nconst obj = { a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z };\n\nconst obj2 = { a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z }, a = 1, b = 2;\n`;"
  },
  {
    "path": "test/samples/with/expected.js",
    "content": "with (foo) bar();"
  },
  {
    "path": "test/samples/with/input.js",
    "content": "export default () => ({\n\ttype: 'WithStatement',\n\tstart: 0,\n\tend: 17,\n\tobject: {\n\t\ttype: 'Identifier',\n\t\tstart: 6,\n\t\tend: 9,\n\t\tname: 'foo'\n\t},\n\tbody: {\n\t\ttype: 'ExpressionStatement',\n\t\tstart: 11,\n\t\tend: 17,\n\t\texpression: {\n\t\t\ttype: 'CallExpression',\n\t\t\tstart: 11,\n\t\t\tend: 16,\n\t\t\tcallee: {\n\t\t\t\ttype: 'Identifier',\n\t\t\t\tstart: 11,\n\t\t\t\tend: 14,\n\t\t\t\tname: 'bar'\n\t\t\t},\n\t\t\targuments: []\n\t\t}\n\t}\n})"
  },
  {
    "path": "test/samples/yield/expected.js",
    "content": "function* foo() {\n\tyield;\n}\n\nfunction* bar() {\n\tyield* 1;\n}"
  },
  {
    "path": "test/samples/yield/input.js",
    "content": "export default ({ b }) => b`\nfunction* foo() {\n\tyield;\n}\n\nfunction* bar() {\n\tyield* 1;\n}\n`;"
  },
  {
    "path": "test/test.js",
    "content": "// @ts-check\nimport { decode } from '@jridgewell/sourcemap-codec';\nimport * as fs from 'fs';\nimport * as acorn from 'acorn';\nimport * as uvu from 'uvu';\nimport * as assert from 'assert';\nimport { generateRandomJS } from 'eslump';\nimport * as codered from '../src/index.js';\nimport { walk } from 'estree-walker';\nimport { fileURLToPath } from 'url';\n\n/** @typedef {import('estree').Identifier} Identifier */\n/** @typedef {import('estree').Node} Node */\n/** @typedef {import('estree').ObjectExpression} ObjectExpression */\n\n/** @param {string} str */\nconst d = (str) => str.replace(/^\\t{5}/gm, '').trim();\n\n// just to make the tests less messy\nconst remove_ranges = (ast) => {\n\twalk(ast, {\n\t\tenter(node) {\n\t\t\tdelete node.start;\n\t\t\tdelete node.end;\n\t\t}\n\t});\n\treturn ast;\n};\n\nconst b = (s, ...v) => remove_ranges(codered.b(s, ...v));\nconst x = (s, ...v) => remove_ranges(codered.x(s, ...v));\nconst p = (s, ...v) => remove_ranges(codered.p(s, ...v));\nconst print = codered.print;\nconst parse = (s) =>\n\tcodered.parse(s, {\n\t\tecmaVersion: 2022,\n\t\tsourceType: 'module',\n\t\tallowAwaitOutsideFunction: true,\n\t\tallowImportExportEverywhere: true,\n\t\tallowReturnOutsideFunction: true\n\t});\n\n/**\n * @param {string} name\n * @param {(test: uvu.Test) => void} fn\n */\nfunction suite(name, fn) {\n\tconst suite = uvu.suite(name);\n\tfn(suite);\n\tsuite.run();\n}\n\nsuite('b', (test) => {\n\ttest('creates a block of nodes', () => {\n\t\tassert.deepEqual(\n\t\t\tb`\n\t\t\ta = b + c;\n\t\t\td = e + f;\n\t\t`,\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\ttype: 'ExpressionStatement',\n\t\t\t\t\texpression: {\n\t\t\t\t\t\ttype: 'AssignmentExpression',\n\t\t\t\t\t\tleft: { type: 'Identifier', name: 'a' },\n\t\t\t\t\t\toperator: '=',\n\t\t\t\t\t\tright: {\n\t\t\t\t\t\t\ttype: 'BinaryExpression',\n\t\t\t\t\t\t\tleft: { type: 'Identifier', name: 'b' },\n\t\t\t\t\t\t\toperator: '+',\n\t\t\t\t\t\t\tright: { type: 'Identifier', name: 'c' }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: 'ExpressionStatement',\n\t\t\t\t\texpression: {\n\t\t\t\t\t\ttype: 'AssignmentExpression',\n\t\t\t\t\t\tleft: { type: 'Identifier', name: 'd' },\n\t\t\t\t\t\toperator: '=',\n\t\t\t\t\t\tright: {\n\t\t\t\t\t\t\ttype: 'BinaryExpression',\n\t\t\t\t\t\t\tleft: { type: 'Identifier', name: 'e' },\n\t\t\t\t\t\t\toperator: '+',\n\t\t\t\t\t\t\tright: { type: 'Identifier', name: 'f' }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t);\n\t});\n\n\ttest('ignores falsy values', () => {\n\t\tassert.deepEqual(\n\t\t\tb`\n\t\t\ta++;\n\t\t\t${false}\n\t\t\tb++\n\t\t`,\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\ttype: 'ExpressionStatement',\n\t\t\t\t\texpression: {\n\t\t\t\t\t\ttype: 'UpdateExpression',\n\t\t\t\t\t\toperator: '++',\n\t\t\t\t\t\tprefix: false,\n\t\t\t\t\t\targument: { type: 'Identifier', name: 'a' }\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: 'ExpressionStatement',\n\t\t\t\t\texpression: {\n\t\t\t\t\t\ttype: 'UpdateExpression',\n\t\t\t\t\t\toperator: '++',\n\t\t\t\t\t\tprefix: false,\n\t\t\t\t\t\targument: { type: 'Identifier', name: 'b' }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t);\n\t});\n\n\ttest('unwraps arrays', () => {\n\t\tconst vars = [x`a`, x`b`, x`c`];\n\t\tconst declarations = vars.map((v) => b`console.log(${v})`);\n\n\t\tconst fn = x`function foo() {\n\t\t\t${declarations}\n\t\t}`;\n\n\t\tconst call = (name) => ({\n\t\t\ttype: 'ExpressionStatement',\n\t\t\texpression: {\n\t\t\t\ttype: 'CallExpression',\n\t\t\t\tcallee: {\n\t\t\t\t\ttype: 'MemberExpression',\n\t\t\t\t\tobject: { type: 'Identifier', name: 'console' },\n\t\t\t\t\tproperty: { type: 'Identifier', name: 'log' },\n\t\t\t\t\toptional: false,\n\t\t\t\t\tcomputed: false\n\t\t\t\t},\n\t\t\t\targuments: [{ type: 'Identifier', name }],\n\t\t\t\toptional: false\n\t\t\t}\n\t\t});\n\n\t\tassert.deepEqual(fn.body.body, [\n\t\t\t{ leadingComments: undefined, ...call('a') },\n\t\t\tcall('b'),\n\t\t\tcall('c')\n\t\t]);\n\t});\n});\n\nsuite('x', (test) => {\n\ttest('creates a single expression', () => {\n\t\tassert.deepEqual(\n\t\t\tx`\n\t\t\ta = b + c\n\t\t`,\n\t\t\t{\n\t\t\t\ttype: 'AssignmentExpression',\n\t\t\t\tleft: { type: 'Identifier', name: 'a' },\n\t\t\t\toperator: '=',\n\t\t\t\tright: {\n\t\t\t\t\ttype: 'BinaryExpression',\n\t\t\t\t\tleft: { type: 'Identifier', name: 'b' },\n\t\t\t\t\toperator: '+',\n\t\t\t\t\tright: { type: 'Identifier', name: 'c' }\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t});\n\n\ttest('inserts values', () => {\n\t\tconst name = { x: 'name' };\n\t\tconst param = { x: 'param' };\n\n\t\tconst node = x`\n\t\t\tfunction ${name}(${param}) {\n\t\t\t\treturn ${param} * 2;\n\t\t\t}\n\t\t`;\n\n\t\tassert.deepEqual(node, {\n\t\t\ttype: 'FunctionExpression',\n\t\t\tid: name,\n\t\t\texpression: false,\n\t\t\tgenerator: false,\n\t\t\tasync: false,\n\t\t\tparams: [param],\n\t\t\tbody: {\n\t\t\t\ttype: 'BlockStatement',\n\t\t\t\tbody: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'ReturnStatement',\n\t\t\t\t\t\targument: {\n\t\t\t\t\t\t\ttype: 'BinaryExpression',\n\t\t\t\t\t\t\tleft: param,\n\t\t\t\t\t\t\toperator: '*',\n\t\t\t\t\t\t\tright: { type: 'Literal', value: 2, raw: '2' }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t});\n\t});\n\n\ttest('preserves @-prefixed names', () => {\n\t\tconst node = x`@foo(bar)`;\n\n\t\tassert.deepEqual(node, {\n\t\t\ttype: 'CallExpression',\n\t\t\tcallee: { type: 'Identifier', name: '@foo' },\n\t\t\targuments: [{ type: 'Identifier', name: 'bar' }],\n\t\t\toptional: false\n\t\t});\n\n\t\tconst id = x`@foo`;\n\n\t\tassert.deepEqual(id, {\n\t\t\ttype: 'Identifier',\n\t\t\tname: '@foo'\n\t\t});\n\t});\n\n\ttest('preserves #-prefixed names', () => {\n\t\tconst node = x`\n\t\t\tfunction foo(#bar) {\n\t\t\t\treturn #bar * bar;\n\t\t\t}\n\t\t`;\n\n\t\tassert.deepEqual(node, {\n\t\t\ttype: 'FunctionExpression',\n\t\t\tid: { type: 'Identifier', name: 'foo' },\n\t\t\texpression: false,\n\t\t\tgenerator: false,\n\t\t\tasync: false,\n\t\t\tparams: [{ type: 'Identifier', name: '#bar' }],\n\t\t\tbody: {\n\t\t\t\ttype: 'BlockStatement',\n\t\t\t\tbody: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'ReturnStatement',\n\t\t\t\t\t\targument: {\n\t\t\t\t\t\t\ttype: 'BinaryExpression',\n\t\t\t\t\t\t\tleft: { type: 'Identifier', name: '#bar' },\n\t\t\t\t\t\t\toperator: '*',\n\t\t\t\t\t\t\tright: { type: 'Identifier', name: 'bar' }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t});\n\t});\n\n\ttest('flattens parameters', () => {\n\t\tconst args = [x`a`, x`b`];\n\n\t\tconst fn = x`function (${args}) {\n\t\t\treturn a + b;\n\t\t}`;\n\n\t\tassert.deepEqual(fn, {\n\t\t\ttype: 'FunctionExpression',\n\t\t\tid: null,\n\t\t\texpression: false,\n\t\t\tgenerator: false,\n\t\t\tasync: false,\n\t\t\tparams: [\n\t\t\t\t{ type: 'Identifier', name: 'a' },\n\t\t\t\t{ type: 'Identifier', name: 'b' }\n\t\t\t],\n\t\t\tbody: {\n\t\t\t\ttype: 'BlockStatement',\n\t\t\t\tbody: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'ReturnStatement',\n\t\t\t\t\t\targument: {\n\t\t\t\t\t\t\ttype: 'BinaryExpression',\n\t\t\t\t\t\t\tleft: { type: 'Identifier', name: 'a' },\n\t\t\t\t\t\t\toperator: '+',\n\t\t\t\t\t\t\tright: { type: 'Identifier', name: 'b' }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t});\n\t});\n\n\ttest(`replaces strings`, () => {\n\t\tconst name = 'world';\n\t\tconst expression = x`hello(\"${name}\")`;\n\n\t\tassert.deepEqual(expression.arguments[0].value, 'world');\n\t});\n\n\ttest(`replaces numbers`, () => {\n\t\tconst answer = 42;\n\t\tconst expression = x`console.log(\"the answer is\", ${answer})`;\n\n\t\tassert.deepEqual(expression.arguments[1], {\n\t\t\ttype: 'Literal',\n\t\t\tvalue: 42,\n\t\t\tleadingComments: undefined,\n\t\t\ttrailingComments: undefined\n\t\t});\n\t});\n\n\ttest(`replaces identifier with no parent`, () => {\n\t\tconst answer = { type: 'Identifier', name: 'value' };\n\t\tconst expression = x`${answer}`;\n\n\t\tassert.deepEqual(expression, {\n\t\t\ttype: 'Identifier',\n\t\t\tname: 'value'\n\t\t});\n\t});\n\n\ttest(`replaces strings in template literals`, () => {\n\t\tconst foo = 'bar';\n\t\tconst expression = x`\\`${foo}\\``;\n\n\t\tassert.deepEqual(expression.quasis[0].value.raw, 'bar');\n\t});\n\n\ttest(`allows strings in place of identifiers`, () => {\n\t\tconst name = 'world';\n\t\tconst expression = x`hello(${name})`;\n\n\t\tassert.deepEqual(expression.arguments[0], {\n\t\t\ttype: 'Identifier',\n\t\t\tname: 'world',\n\t\t\tleadingComments: undefined,\n\t\t\ttrailingComments: undefined\n\t\t});\n\t});\n\n\ttest('flattens arrays', () => {\n\t\tconst vars = [x`a`, x`b`, x`c`];\n\t\tconst arr = x`[${vars}]`;\n\n\t\tassert.deepEqual(arr, {\n\t\t\ttype: 'ArrayExpression',\n\t\t\telements: ['a', 'b', 'c'].map((name) => ({\n\t\t\t\ttype: 'Identifier',\n\t\t\t\tname\n\t\t\t}))\n\t\t});\n\t});\n\n\ttest('flattens objects', () => {\n\t\tconst props = [p`a`, p`b`, p`c`];\n\t\tconst obj = x`{${props}}`;\n\n\t\tassert.deepEqual(obj, {\n\t\t\ttype: 'ObjectExpression',\n\t\t\tproperties: ['a', 'b', 'c'].map((name) => {\n\t\t\t\tconst id = { type: 'Identifier', name };\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'Property',\n\t\t\t\t\tkind: 'init',\n\t\t\t\t\tmethod: false,\n\t\t\t\t\tshorthand: true,\n\t\t\t\t\tcomputed: false,\n\t\t\t\t\tkey: id,\n\t\t\t\t\tvalue: id\n\t\t\t\t};\n\t\t\t})\n\t\t});\n\t});\n\n\ttest('flattens patterns', () => {\n\t\tconst props = [p`a`, p`b`, p`c`];\n\t\tconst declaration = b`const { ${props} } = obj;`[0];\n\n\t\tassert.deepEqual(declaration, {\n\t\t\ttype: 'VariableDeclaration',\n\t\t\tkind: 'const',\n\t\t\tdeclarations: [\n\t\t\t\t{\n\t\t\t\t\ttype: 'VariableDeclarator',\n\t\t\t\t\tid: {\n\t\t\t\t\t\ttype: 'ObjectPattern',\n\t\t\t\t\t\tproperties: ['a', 'b', 'c'].map((name) => {\n\t\t\t\t\t\t\tconst id = { type: 'Identifier', name };\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\ttype: 'Property',\n\t\t\t\t\t\t\t\tkind: 'init',\n\t\t\t\t\t\t\t\tmethod: false,\n\t\t\t\t\t\t\t\tcomputed: false,\n\t\t\t\t\t\t\t\tshorthand: true,\n\t\t\t\t\t\t\t\tkey: id,\n\t\t\t\t\t\t\t\tvalue: id\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t\tinit: {\n\t\t\t\t\t\ttype: 'Identifier',\n\t\t\t\t\t\tname: 'obj'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t});\n\t});\n\n\ttest('removes falsy properties from an object', () => {\n\t\tconst obj = x`{\n\t\t\ta: 1,\n\t\t\tb: ${false}\n\t\t}`;\n\n\t\tassert.deepEqual(obj.properties.length, 1);\n\t\tassert.deepEqual(obj.properties[0].key.name, 'a');\n\t});\n\n\ttest('preserves locations of original nodes in a sourcemap', () => {\n\t\tconst answer = {\n\t\t\ttype: 'Literal',\n\t\t\tvalue: 42,\n\t\t\traw: '42',\n\t\t\tloc: {\n\t\t\t\tstart: { line: 10, column: 5 },\n\t\t\t\tend: { line: 10, column: 7 }\n\t\t\t}\n\t\t};\n\n\t\tconst expression = x`console.log(${answer})`;\n\n\t\tconst { code, map } = print(expression, {\n\t\t\tsourceMapSource: 'input.js'\n\t\t});\n\n\t\tassert.deepEqual(code, `console.log(42)`);\n\n\t\tassert.deepEqual(map, {\n\t\t\tversion: 3,\n\t\t\tsources: ['input.js'],\n\t\t\tsourcesContent: [null],\n\t\t\tnames: [],\n\t\t\tmappings: 'YASK,EAAE'\n\t\t});\n\t});\n\n\ttest('errors on invalid expressions', () => {\n\t\tassert.throws(() => {\n\t\t\tx`this is broken`;\n\t\t}, /Unexpected token 'is'/);\n\t});\n});\n\nsuite('p', (test) => {\n\ttest('creates a regular object property', () => {\n\t\tconst obj = x`{}`;\n\t\tobj.properties.push(p`foo: 'bar'`);\n\n\t\tassert.deepEqual(obj, {\n\t\t\ttype: 'ObjectExpression',\n\t\t\tproperties: [\n\t\t\t\t{\n\t\t\t\t\ttype: 'Property',\n\t\t\t\t\tkind: 'init',\n\t\t\t\t\tmethod: false,\n\t\t\t\t\tshorthand: false,\n\t\t\t\t\tcomputed: false,\n\t\t\t\t\tkey: { type: 'Identifier', name: 'foo' },\n\t\t\t\t\tvalue: { type: 'Literal', value: 'bar', raw: \"'bar'\" }\n\t\t\t\t}\n\t\t\t]\n\t\t});\n\t});\n});\n\nsuite('print', (test) => {\n\tconst read = (file) =>\n\t\tfs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : null;\n\n\tfs.readdirSync('test/samples').forEach((dir) => {\n\t\ttest(dir, async () => {\n\t\t\tif (dir[0] === '.') return;\n\n\t\t\tconst url = new URL(`./samples/${dir}/input.js`, import.meta.url);\n\t\t\tconst mod = await import(fileURLToPath(url.href));\n\t\t\tconst input = mod.default({ b, x, p, parse });\n\n\t\t\tconst expected = {\n\t\t\t\tcode: read(`test/samples/${dir}/expected.js`),\n\t\t\t\tmap: JSON.parse(read(`test/samples/${dir}/expected.js.map`) || '{}')\n\t\t\t};\n\n\t\t\tconst actual = print(input, {\n\t\t\t\tsourceMapSource: 'input.js',\n\t\t\t\tgetName: (name) => name.toUpperCase()\n\t\t\t});\n\n\t\t\tfs.writeFileSync(`test/samples/${dir}/_actual.js`, actual.code);\n\t\t\tfs.writeFileSync(\n\t\t\t\t`test/samples/${dir}/_actual.js.map`,\n\t\t\t\tactual.map.toString()\n\t\t\t);\n\n\t\t\tassert.deepEqual(\n\t\t\t\tactual.code.replace(/\\t+$/gm, ''),\n\t\t\t\texpected.code.replace(/\\t+$/gm, '')\n\t\t\t);\n\t\t\tassert.deepEqual(actual.map, expected.map);\n\t\t});\n\t});\n\n\ttest('throws on unhandled sigils', () => {\n\t\tassert.throws(() => print(b`let foo = @bar;`), {\n\t\t\tmessage: 'Unhandled sigil @bar'\n\t\t});\n\t});\n\n\ttest('can return sourcemap with decoded mappings', async () => {\n\t\tconst url = new URL(`./samples/sourcemap/input.js`, import.meta.url);\n\t\tconst mod = await import(fileURLToPath(url.href));\n\t\tconst input = mod.default({ b, x, p });\n\n\t\tconst expected = {\n\t\t\tcode: read(`test/samples/sourcemap/expected.js`),\n\t\t\tmap: JSON.parse(read(`test/samples/sourcemap/expected.js.map`) || '{}')\n\t\t};\n\t\tif (expected.map && expected.map.mappings) {\n\t\t\texpected.map.mappings = decode(expected.map.mappings);\n\t\t}\n\n\t\tconst actual = print(input, {\n\t\t\tsourceMapSource: 'input.js',\n\t\t\tgetName: (name) => name.toUpperCase(),\n\t\t\tsourceMapEncodeMappings: false\n\t\t});\n\n\t\tassert.deepEqual(actual.map, expected.map);\n\t});\n\n\ttest.skip('passes fuzz testing', () => {\n\t\tfor (let i = 0; i < 100; i += 1) {\n\t\t\tconst js = generateRandomJS({\n\t\t\t\tsourceType: 'module',\n\t\t\t\tmaxDepth: 7,\n\t\t\t\tcomments: false\n\t\t\t});\n\n\t\t\tlet ast1;\n\t\t\ttry {\n\t\t\t\tast1 = acorn.parse(js, {\n\t\t\t\t\tsourceType: 'module',\n\t\t\t\t\tecmaVersion: 2020\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet printed;\n\n\t\t\ttry {\n\t\t\t\tprinted = print(ast1);\n\t\t\t} catch (err) {\n\t\t\t\tfs.writeFileSync(`test/fuzz/report.js`, js);\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\tconst ast2 = acorn.parse(printed.code, {\n\t\t\t\tsourceType: 'module',\n\t\t\t\tecmaVersion: 2019\n\t\t\t});\n\n\t\t\t[ast1, ast2].forEach((ast) => {\n\t\t\t\twalk(ast, {\n\t\t\t\t\tenter(node) {\n\t\t\t\t\t\tdelete node.start;\n\t\t\t\t\t\tdelete node.end;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tassert.deepEqual(ast1, ast2);\n\t\t\t} catch (err) {\n\t\t\t\tfs.writeFileSync(\n\t\t\t\t\t`test/fuzz/report.js`,\n\t\t\t\t\t`// input\\n${js}\\n\\n// output\\n${printed.code}`\n\t\t\t\t);\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"allowJs\": true,\n        \"checkJs\": true,\n        \"declaration\": true,\n        \"emitDeclarationOnly\": true,\n        \"declarationDir\": \"types\",\n        \"noImplicitAny\": true,\n        \"diagnostics\": true,\n        \"noImplicitThis\": true,\n        \"noEmitOnError\": true,\n        \"lib\": [\"es5\", \"es6\", \"dom\"]\n    },\n    \"target\": \"ES5\",\n    \"module\": \"ES6\",\n    \"include\": [\n        \"src\"\n    ],\n    \"exclude\": [\n        \"node_modules\"\n    ]\n}"
  }
]