Full Code of Rich-Harris/code-red for AI

master ecaf9717d18c cached
125 files
82.9 KB
29.5k tokens
103 symbols
1 requests
Download .txt
Repository: Rich-Harris/code-red
Branch: master
Commit: ecaf9717d18c
Files: 125
Total size: 82.9 KB

Directory structure:
gitextract_nlxlmv96/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .prettierrc.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── src/
│   ├── index.js
│   ├── print/
│   │   ├── handlers.js
│   │   └── index.js
│   └── utils/
│       ├── comments.js
│       ├── id.js
│       └── push_array.js
├── test/
│   ├── samples/
│   │   ├── array-expressions/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── arrow-function-as-statement/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── arrow-function-assignment-object-pattern/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── arrow-function-parenthesized/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── at-prefix/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── await-precedence/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── basic/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── bigint/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── break-continue/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── call-expressions/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── chain-expressions/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── class-private/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── class-property/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── class-static-block/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-block/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-block-with-sigil/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-inline/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-inline-inserted/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-interpolated/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-mixed-trailing/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-within-call-expression/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-within-parentheses/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── deconflict-let/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── deconflict-method/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── destructured-declaration/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── empty-body/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── export/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── function-declaration/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── hash-prefix/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── hash-prefix-arrow/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── hash-prefix-for-loop-head/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── hash-prefix-reused/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── import/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── import-as/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── import-default-and-named/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── import-many/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── inserted-parameter/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── inserted-parameters/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── logical-expression/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── meta-property/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── method/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── nested-blocks/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── nested-blocks-b/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── object-expressions/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── parenthesized-expression/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── regex/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── removes-parens/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── sourcemap/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── string-literal/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── switch/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── tagged-template/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── try-catch/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── var-declaration/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── with/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   └── yield/
│   │       ├── expected.js
│   │       └── input.js
│   └── test.js
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches:
      - master
  pull_request:

# cancel in-progress runs on new commits to same PR (github.event.number)
concurrency:
  group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
  cancel-in-progress: true

jobs:
  Tests:
    runs-on: ${{ matrix.os }}
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        node-version: [16]
        os: [ubuntu-latest]
    steps:
      - run: git config --global core.autocrlf false
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2.2.2
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm test


================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/types
dist
test/**/_actual.*
test/fuzz

================================================
FILE: .prettierrc.json
================================================
{
	"useTabs": true,
	"singleQuote": true,
	"trailingComma": "none"
}


================================================
FILE: CHANGELOG.md
================================================
# code-red changelog

## 1.0.4

- Add `types` to `pkg.exports` ([#83](https://github.com/Rich-Harris/code-red/pull/83))

## 1.0.3

- Fix inline comments in function arguments ([#79](https://github.com/Rich-Harris/code-red/pull/79))

## 1.0.2

- Use `dts-buddy` to generate type declarations ([#78](https://github.com/Rich-Harris/code-red/pull/78))

## 1.0.1

- Remove `dist` directory from package ([#77](https://github.com/Rich-Harris/code-red/pull/77))
- Fix position of inline comments ([#76](https://github.com/Rich-Harris/code-red/pull/76))

## 1.0.0

- Breaking: remove CJS build, remove `main` and `module` ([#75](https://github.com/Rich-Harris/code-red/pull/75))

## 0.2.7

- Update to `es2022` ([#73](https://github.com/Rich-Harris/code-red/pull/73))

## 0.2.6

- Replace `sourcemap-codec` with `@jridgewell/sourcemap-codec` ([#74](https://github.com/Rich-Harris/code-red/pull/74))

## 0.2.5

- Prevent stack overflow with very large ASTs ([#71](https://github.com/Rich-Harris/code-red/pull/71))

## 0.2.4

- Fix output for arrow functions where body is an object destructuring assignment ([#70](https://github.com/Rich-Harris/code-red/pull/70))

## 0.2.3

- Fix output with comments within parenthesized `return` statement ([#36](https://github.com/Rich-Harris/code-red/issues/36))
- Fix output for identifier at root of AST ([#37](https://github.com/Rich-Harris/code-red/issues/37))
- Fix output for statements with empty bodies ([#65](https://github.com/Rich-Harris/code-red/issues/65))

## 0.2.2

- Update dependencies ([#63](https://github.com/Rich-Harris/code-red/pull/63))

## 0.2.1

- Fix handling of string literal raw values ([#61](https://github.com/Rich-Harris/code-red/pull/61))

## 0.2.0

- Rewrite in JavaScript

## 0.1.7

- Include dependencies ([#56](https://github.com/Rich-Harris/code-red/pull/56))
- Add `sourceMapEncodeMappings` option ([#51](https://github.com/Rich-Harris/code-red/pull/51))

## 0.1.6

- Only use shorthand for non-computed properties ([#58](https://github.com/Rich-Harris/code-red/pull/58))

## 0.1.5

- Use `node.raw` where possible ([#55](https://github.com/Rich-Harris/code-red/pull/55))
- Support BigInt (([#54](https://github.com/Rich-Harris/code-red/issues/54)))

## 0.1.4

- Fix rendering of nullish coalescing operator alongside other logical operators ([#52](https://github.com/Rich-Harris/code-red/issues/52))

## 0.1.3

- Support nullish coalescing operator ([#42](https://github.com/Rich-Harris/code-red/issues/42))
- Support optional chaining ([#43](https://github.com/Rich-Harris/code-red/issues/43))

## 0.1.2

- Don't crash when using an arrow function as a statement ([#38](https://github.com/Rich-Harris/code-red/issues/38))

## 0.1.1

- Wrap arrow functions in parens as appropriate ([#31](https://github.com/Rich-Harris/code-red/issues/31))
- Throw on invalid expressions ([#31](https://github.com/Rich-Harris/code-red/issues/31))

## 0.1.0

- Throw on unhandled sigils ([#30](https://github.com/Rich-Harris/code-red/pull/30))

## 0.0.32

- Prevent syntax errors when combining comments ([#28](https://github.com/Rich-Harris/code-red/issues/28))

## 0.0.31

- Expose wrapped versions of Acorn methods to facilitate comment preservation ([#26](https://github.com/Rich-Harris/code-red/issues/26))

## 0.0.30

- Wrap `await` argument in parens if necessary ([#24](https://github.com/Rich-Harris/code-red/issues/24))

## 0.0.29

- Handle sigils in comments ([#21](https://github.com/Rich-Harris/code-red/issues/21))

## 0.0.28

- Add `toString` and `toUrl` methods on sourcemap objects ([#22](https://github.com/Rich-Harris/code-red/pull/22))

## 0.0.27

- Handle parenthesized expressions

## 0.0.26

- Always replace comment values ([#20](https://github.com/Rich-Harris/code-red/pull/20))

## 0.0.25

- Fix async/generator functions in object methods ([#18](https://github.com/Rich-Harris/code-red/issues/18))

## 0.0.24

- Determine shorthand eligibility after stringification ([#17](https://github.com/Rich-Harris/code-red/pull/17))

## 0.0.23

- Unescape sigils in literals ([#16](https://github.com/Rich-Harris/code-red/pull/16))

## 0.0.22

- Prevent erroneous object shorthand when key is an identifier ([#14](https://github.com/Rich-Harris/code-red/issues/14))

## 0.0.21

- Deconflict #-identifiers in function names ([#10](https://github.com/Rich-Harris/code-red/issues/10))
- Fix object expression with string literal key matching value ([#9](https://github.com/Rich-Harris/code-red/pull/9))

## 0.0.20

- Update deps

## 0.0.19

- Attach comments

## 0.0.18

- Handle mixed named/default imports ([#3](https://github.com/Rich-Harris/code-red/issues/3))
- Update dependencies ([#4](https://github.com/Rich-Harris/code-red/issues/4))

## 0.0.17

- Fixes and additions

## 0.0.16

- Improve some aspects of generated code

## 0.0.15

- Flatten patterns

## 0.0.13-14

- Sourcemaps

## 0.0.12

- Flatten object properties

## 0.0.11

- Handle deconfliction edge case

## 0.0.10

- Tweak some TypeScript stuff

## 0.0.9

- Adopt estree types
- Add a `p` function for creating properties

## 0.0.8

- Allow strings to be treated as identifiers

## 0.0.7

- Various

## 0.0.6

- Flatten arguments and parameters

## 0.0.5

- Omit missing statements
- Flatten arrays of statements

## 0.0.4

- Use fork of astring

## 0.0.3

- Allow return outside function
- Print code on syntax error

## 0.0.2

- Support `@`-prefixed names (replaceable globals)
- Support `#`-prefixed names (automatically deconflicted)

## 0.0.1

- First experimental release


================================================
FILE: LICENSE
================================================
Copyright (c) 2019 Rich Harris

Permission 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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE 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.


================================================
FILE: README.md
================================================
# code-red

Experimental toolkit for writing x-to-JavaScript compilers. It is used in [Svelte](https://svelte.dev).


## API

The `code-red` package exposes three core functions — `b`, `x` and `print`.

`b` and `x` take a template literal and return an [ESTree](https://github.com/estree/estree) program body, or a single node:

```js
import { b, x } from 'code-red';

const expression = x`i + j`;

assert.equal(expression.type, 'AssignmentExpression');
assert.equal(expression.operator, '+');
assert.equal(expression.left.name, 'i');
assert.equal(expression.right.name, 'j');

const body = b`
	const i = 1;
	const j = 2;
	const k = i + j;
`;

assert.equal(body.length, 3);
assert.equal(body[0].type, 'VariableDeclaration');
```

Expressions in template literals correspond to replacement nodes — so you could express the above like so:

```js
const i = x`i`;
const j = x`j`;
const expression = x`${i} + ${j}`;

const body = b`
	const ${i} = 1;
	const ${j} = 2;
	const k = ${expression};
`;
```

The `print` function takes a node and turns it into a `{code, map}` object:

```js
const add = x`
	function add(${i}, ${j}) {
		return ${expression};
	}
`;

print(add).code;
/*
function add(i, j) {
	return i + j;
}
*/

i.name = 'foo';
j.name = 'bar';

print(add).code;
/*
function add(foo, bar) {
	return foo + bar;
}
*/
```

## Prefixes

### `@`-prefixed names (replaceable globals)

So that you can use globals in your code. In Svelte, we use this to insert utility functions.

```js
// input
import { x } from 'code-red';
x`@foo(bar)`

// output
FOO(bar)
```

### `#`-prefixed names (automatically deconflicted names)

So that you can insert variables in your code without worrying if they clash with existing variable names.


`bar` used in user code and in inserted code gets a `$1` suffix:

```js
// input
import { x } from 'code-red';
x`
function foo(#bar) {
	return #bar * bar;
}`;

// output
function foo(bar$1) {
	return bar$1 * bar;
}
```

Without conflicts, no `$1` suffix:

```js
// input
import { b } from 'code-red';
b`const foo = #bar => #bar * 2`;

// output
const foo = bar => bar * 2;
```

## Optimiser

TODO add an optimiser that e.g. collapses consecutive identical if blocks


## Compiler

TODO 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.


## Sourcemaps

TODO support source mappings for inserted nodes with location information.


## License

[MIT](LICENSE)


================================================
FILE: package.json
================================================
{
	"name": "code-red",
	"description": "code-red",
	"version": "1.0.4",
	"repository": "Rich-Harris/code-red",
	"exports": {
		".": {
			"types": "./types/index.d.ts",
			"import": "./src/index.js"
		}
	},
	"type": "module",
	"types": "types/index.d.ts",
	"files": [
		"src",
		"types"
	],
	"scripts": {
		"build": "dts-buddy",
		"test": "uvu test test.js",
		"prepublishOnly": "npm test && npm run build",
		"repl": "node -e \"import('./src/index.js').then(mod => { x = mod.x; b = mod.b; print = mod.print });\" -i"
	},
	"license": "MIT",
	"dependencies": {
		"@jridgewell/sourcemap-codec": "^1.4.15",
		"@types/estree": "^1.0.1",
		"acorn": "^8.10.0",
		"estree-walker": "^3.0.3",
		"periscopic": "^3.1.0"
	},
	"devDependencies": {
		"@types/node": "^20.4.10",
		"dts-buddy": "^0.1.9",
		"eslump": "^3.0.0",
		"uvu": "^0.5.6"
	},
	"packageManager": "pnpm@8.6.0"
}

================================================
FILE: src/index.js
================================================
import * as acorn from 'acorn';
import { walk } from 'estree-walker';
import { id, re } from './utils/id.js';
import { get_comment_handlers } from './utils/comments.js';

/** @typedef {import('estree').Expression} Expression */
/** @typedef {import('estree').Node} Node */
/** @typedef {import('estree').ObjectExpression} ObjectExpression */
/** @typedef {import('estree').Property} Property */
/** @typedef {import('estree').SpreadElement} SpreadElement */

/** @typedef {import('./utils/comments').CommentWithLocation} CommentWithLocation */

/** @type {Record<string, string>} */
const sigils = {
	'@': 'AT',
	'#': 'HASH'
};

/** @param {TemplateStringsArray} strings */
const join = (strings) => {
	let str = strings[0];
	for (let i = 1; i < strings.length; i += 1) {
		str += `_${id}_${i - 1}_${strings[i]}`;
	}
	return str.replace(
		/([@#])(\w+)/g,
		(_m, sigil, name) => `_${id}_${sigils[sigil]}_${name}`
	);
};

/**
 * @param {any[]} array
 * @param {any[]} target
 */
const flatten_body = (array, target) => {
	for (let i = 0; i < array.length; i += 1) {
		const statement = array[i];
		if (Array.isArray(statement)) {
			flatten_body(statement, target);
			continue;
		}

		if (statement.type === 'ExpressionStatement') {
			if (statement.expression === EMPTY) continue;

			if (Array.isArray(statement.expression)) {
				// TODO this is hacktacular
				let node = statement.expression[0];
				while (Array.isArray(node)) node = node[0];
				if (node) node.leadingComments = statement.leadingComments;

				flatten_body(statement.expression, target);
				continue;
			}

			if (/(Expression|Literal)$/.test(statement.expression.type)) {
				target.push(statement);
				continue;
			}

			if (statement.leadingComments)
				statement.expression.leadingComments = statement.leadingComments;
			if (statement.trailingComments)
				statement.expression.trailingComments = statement.trailingComments;

			target.push(statement.expression);
			continue;
		}

		target.push(statement);
	}

	return target;
};

/**
 * @param {any[]} array
 * @param {any[]} target
 */
const flatten_properties = (array, target) => {
	for (let i = 0; i < array.length; i += 1) {
		const property = array[i];

		if (property.value === EMPTY) continue;

		if (property.key === property.value && Array.isArray(property.key)) {
			flatten_properties(property.key, target);
			continue;
		}

		target.push(property);
	}

	return target;
};

/**
 * @param {any[]} nodes
 * @param {any[]} target
 */
const flatten = (nodes, target) => {
	for (let i = 0; i < nodes.length; i += 1) {
		const node = nodes[i];

		if (node === EMPTY) continue;

		if (Array.isArray(node)) {
			flatten(node, target);
			continue;
		}

		target.push(node);
	}

	return target;
};

const EMPTY = { type: 'Empty' };

/**
 *
 * @param {CommentWithLocation[]} comments
 * @param {string} raw
 * @returns {any}
 */
const acorn_opts = (comments, raw) => {
	const { onComment } = get_comment_handlers(comments, raw);
	return {
		ecmaVersion: 2022,
		sourceType: 'module',
		allowAwaitOutsideFunction: true,
		allowImportExportEverywhere: true,
		allowReturnOutsideFunction: true,
		onComment
	};
};

/**
 * @param {string} raw
 * @param {Node} node
 * @param {any[]} values
 * @param {CommentWithLocation[]} comments
 */
const inject = (raw, node, values, comments) => {
	comments.forEach((comment) => {
		comment.value = comment.value.replace(re, (m, i) =>
			+i in values ? values[+i] : m
		);
	});

	const { enter, leave } = get_comment_handlers(comments, raw);

	return walk(node, {
		enter,

		/** @param {any} node */
		leave(node) {
			if (node.type === 'Identifier') {
				re.lastIndex = 0;
				const match = re.exec(node.name);

				if (match) {
					if (match[1]) {
						if (+match[1] in values) {
							let value = values[+match[1]];

							if (typeof value === 'string') {
								value = {
									type: 'Identifier',
									name: value,
									leadingComments: node.leadingComments,
									trailingComments: node.trailingComments
								};
							} else if (typeof value === 'number') {
								value = {
									type: 'Literal',
									value,
									leadingComments: node.leadingComments,
									trailingComments: node.trailingComments
								};
							}

							this.replace(value || EMPTY);
						}
					} else {
						node.name = `${match[2] ? `@` : `#`}${match[4]}`;
					}
				}
			}

			if (node.type === 'Literal') {
				if (typeof node.value === 'string') {
					re.lastIndex = 0;
					const new_value = /** @type {string} */ (node.value).replace(
						re,
						(m, i) => (+i in values ? values[+i] : m)
					);
					const has_changed = new_value !== node.value;
					node.value = new_value;
					if (has_changed && node.raw) {
						// preserve the quotes
						node.raw = `${node.raw[0]}${JSON.stringify(node.value).slice(
							1,
							-1
						)}${node.raw[node.raw.length - 1]}`;
					}
				}
			}

			if (node.type === 'TemplateElement') {
				re.lastIndex = 0;
				node.value.raw = /** @type {string} */ (node.value.raw).replace(
					re,
					(m, i) => (+i in values ? values[+i] : m)
				);
			}

			if (node.type === 'Program' || node.type === 'BlockStatement') {
				node.body = flatten_body(node.body, []);
			}

			if (node.type === 'ObjectExpression' || node.type === 'ObjectPattern') {
				node.properties = flatten_properties(node.properties, []);
			}

			if (node.type === 'ArrayExpression' || node.type === 'ArrayPattern') {
				node.elements = flatten(node.elements, []);
			}

			if (
				node.type === 'FunctionExpression' ||
				node.type === 'FunctionDeclaration' ||
				node.type === 'ArrowFunctionExpression'
			) {
				node.params = flatten(node.params, []);
			}

			if (node.type === 'CallExpression' || node.type === 'NewExpression') {
				node.arguments = flatten(node.arguments, []);
			}

			if (
				node.type === 'ImportDeclaration' ||
				node.type === 'ExportNamedDeclaration'
			) {
				node.specifiers = flatten(node.specifiers, []);
			}

			if (node.type === 'ForStatement') {
				node.init = node.init === EMPTY ? null : node.init;
				node.test = node.test === EMPTY ? null : node.test;
				node.update = node.update === EMPTY ? null : node.update;
			}

			leave(node);
		}
	});
};

/**
 *
 * @param {TemplateStringsArray} strings
 * @param  {any[]} values
 * @returns {Node[]}
 */
export function b(strings, ...values) {
	const str = join(strings);

	/** @type {CommentWithLocation[]} */
	const comments = [];

	try {
		let ast = /** @type {any} */ (acorn.parse(str, acorn_opts(comments, str)));

		ast = inject(str, ast, values, comments);

		return ast.body;
	} catch (err) {
		handle_error(str, err);
	}
}

/**
 *
 * @param {TemplateStringsArray} strings
 * @param  {any[]} values
 * @returns {Expression & { start: Number, end: number }}
 */
export function x(strings, ...values) {
	const str = join(strings);

	/** @type {CommentWithLocation[]} */
	const comments = [];

	try {
		let expression =
			/** @type {Expression & { start: Number, end: number }} */ (
				acorn.parseExpressionAt(str, 0, acorn_opts(comments, str))
			);
		const match = /\S+/.exec(str.slice(expression.end));
		if (match) {
			throw new Error(`Unexpected token '${match[0]}'`);
		}

		expression = /** @type {Expression & { start: Number, end: number }} */ (
			inject(str, expression, values, comments)
		);

		return expression;
	} catch (err) {
		handle_error(str, err);
	}
}

/**
 *
 * @param {TemplateStringsArray} strings
 * @param  {any[]} values
 * @returns {(Property | SpreadElement) & { start: Number, end: number }}
 */
export function p(strings, ...values) {
	const str = `{${join(strings)}}`;

	/** @type {CommentWithLocation[]} */
	const comments = [];

	try {
		let expression = /** @type {any} */ (
			acorn.parseExpressionAt(str, 0, acorn_opts(comments, str))
		);

		expression = inject(str, expression, values, comments);

		return expression.properties[0];
	} catch (err) {
		handle_error(str, err);
	}
}

/**
 * @param {string} str
 * @param {Error} err
 */
function handle_error(str, err) {
	// TODO location/code frame

	re.lastIndex = 0;

	str = str.replace(re, (m, i, at, hash, name) => {
		if (at) return `@${name}`;
		if (hash) return `#${name}`;

		return '${...}';
	});

	console.log(`failed to parse:\n${str}`);
	throw err;
}

export { print } from './print/index.js';

/**
 * @param {string} source
 * @param {any} opts
 */
export const parse = (source, opts) => {
	/** @type {CommentWithLocation[]} */
	const comments = [];
	const { onComment, enter, leave } = get_comment_handlers(comments, source);
	const ast = /** @type {any} */ (acorn.parse(source, { onComment, ...opts }));
	walk(ast, { enter, leave });
	return ast;
};

/**
 * @param {string} source
 * @param {number} index
 * @param {any} opts
 */
export const parseExpressionAt = (source, index, opts) => {
	/** @type {CommentWithLocation[]} */
	const comments = [];
	const { onComment, enter, leave } = get_comment_handlers(comments, source);
	const ast = /** @type {any} */ (
		acorn.parseExpressionAt(source, index, { onComment, ...opts })
	);
	walk(ast, { enter, leave });
	return ast;
};


================================================
FILE: src/print/handlers.js
================================================
// heavily based on https://github.com/davidbonnet/astring
// released under MIT license https://github.com/davidbonnet/astring/blob/master/LICENSE

import { re } from '../utils/id.js';
import { push_array } from '../utils/push_array.js';

/** @typedef {import('estree').ArrowFunctionExpression} ArrowFunctionExpression */
/** @typedef {import('estree').BinaryExpression} BinaryExpression */
/** @typedef {import('estree').CallExpression} CallExpression */
/** @typedef {import('estree').Comment} Comment */
/** @typedef {import('estree').ExportSpecifier} ExportSpecifier */
/** @typedef {import('estree').Expression} Expression */
/** @typedef {import('estree').FunctionDeclaration} FunctionDeclaration */
/** @typedef {import('estree').ImportDeclaration} ImportDeclaration */
/** @typedef {import('estree').ImportSpecifier} ImportSpecifier */
/** @typedef {import('estree').Literal} Literal */
/** @typedef {import('estree').LogicalExpression} LogicalExpression */
/** @typedef {import('estree').NewExpression} NewExpression */
/** @typedef {import('estree').Node} Node */
/** @typedef {import('estree').ObjectExpression} ObjectExpression */
/** @typedef {import('estree').Pattern} Pattern */
/** @typedef {import('estree').Property} Property */
/** @typedef {import('estree').PropertyDefinition} PropertyDefinition */
/** @typedef {import('estree').SequenceExpression} SequenceExpression */
/** @typedef {import('estree').SimpleCallExpression} SimpleCallExpression */
/** @typedef {import('estree').SwitchStatement} SwitchStatement */
/** @typedef {import('estree').VariableDeclaration} VariableDeclaration */
/** @typedef {import('estree').StaticBlock} StaticBlock */
/** @typedef {import('estree').PrivateIdentifier} PrivateIdenifier*/

/**
 * @typedef {{
 *   content: string;
 *   loc?: {
 *     start: { line: number; column: number; };
 *     end: { line: number; column: number; };
 *   };
 *   has_newline: boolean;
 * }} Chunk
 */

/**
 * @typedef {(node: any, state: State) => Chunk[]} Handler
 */

/**
 * @typedef {{
 *   indent: string;
 *   scope: any; // TODO import from periscopic
 *   scope_map: WeakMap<Node, any>;
 *   getName: (name: string) => string;
 *   deconflicted: WeakMap<Node, Map<string, string>>;
 *   comments: Comment[];
 * }} State
 */

/**
 * @param {Node} node
 * @param {State} state
 * @returns {Chunk[]}
 */
export function handle(node, state) {
	const handler = handlers[node.type];

	if (!handler) {
		throw new Error(`Not implemented ${node.type}`);
	}

	const result = handler(node, state);

	if (node.leadingComments) {
		result.unshift(
			c(
				node.leadingComments
					.map((comment) =>
						comment.type === 'Block'
							? `/*${comment.value}*/${
									/** @type {any} */ (comment).has_trailing_newline
										? `\n${state.indent}`
										: ` `
							  }`
							: `//${comment.value}${
									/** @type {any} */ (comment).has_trailing_newline
										? `\n${state.indent}`
										: ` `
							  }`
					)
					.join(``)
			)
		);
	}

	if (node.trailingComments) {
		state.comments.push(node.trailingComments[0]); // there is only ever one
	}

	return result;
}

/**
 * @param {string} content
 * @param {Node} [node]
 * @returns {Chunk}
 */
function c(content, node) {
	return {
		content,
		loc: node && node.loc,
		has_newline: /\n/.test(content)
	};
}

const OPERATOR_PRECEDENCE = {
	'||': 2,
	'&&': 3,
	'??': 4,
	'|': 5,
	'^': 6,
	'&': 7,
	'==': 8,
	'!=': 8,
	'===': 8,
	'!==': 8,
	'<': 9,
	'>': 9,
	'<=': 9,
	'>=': 9,
	in: 9,
	instanceof: 9,
	'<<': 10,
	'>>': 10,
	'>>>': 10,
	'+': 11,
	'-': 11,
	'*': 12,
	'%': 12,
	'/': 12,
	'**': 13
};

/** @type {Record<string, number>} */
const EXPRESSIONS_PRECEDENCE = {
	ArrayExpression: 20,
	TaggedTemplateExpression: 20,
	ThisExpression: 20,
	Identifier: 20,
	Literal: 18,
	TemplateLiteral: 20,
	Super: 20,
	SequenceExpression: 20,
	MemberExpression: 19,
	CallExpression: 19,
	NewExpression: 19,
	AwaitExpression: 17,
	ClassExpression: 17,
	FunctionExpression: 17,
	ObjectExpression: 17,
	UpdateExpression: 16,
	UnaryExpression: 15,
	BinaryExpression: 14,
	LogicalExpression: 13,
	ConditionalExpression: 4,
	ArrowFunctionExpression: 3,
	AssignmentExpression: 3,
	YieldExpression: 2,
	RestElement: 1
};

/**
 *
 * @param {Expression} node
 * @param {BinaryExpression | LogicalExpression} parent
 * @param {boolean} is_right
 * @returns
 */
function needs_parens(node, parent, is_right) {
	// special case where logical expressions and coalesce expressions cannot be mixed,
	// either of them need to be wrapped with parentheses
	if (
		node.type === 'LogicalExpression' &&
		parent.type === 'LogicalExpression' &&
		((parent.operator === '??' && node.operator !== '??') ||
			(parent.operator !== '??' && node.operator === '??'))
	) {
		return true;
	}

	const precedence = EXPRESSIONS_PRECEDENCE[node.type];
	const parent_precedence = EXPRESSIONS_PRECEDENCE[parent.type];

	if (precedence !== parent_precedence) {
		// Different node types
		return (
			(!is_right &&
				precedence === 15 &&
				parent_precedence === 14 &&
				parent.operator === '**') ||
			precedence < parent_precedence
		);
	}

	if (precedence !== 13 && precedence !== 14) {
		// Not a `LogicalExpression` or `BinaryExpression`
		return false;
	}

	if (
		/** @type {BinaryExpression} */ (node).operator === '**' &&
		parent.operator === '**'
	) {
		// Exponentiation operator has right-to-left associativity
		return !is_right;
	}

	if (is_right) {
		// Parenthesis are used if both operators have the same precedence
		return (
			OPERATOR_PRECEDENCE[/** @type {BinaryExpression} */ (node).operator] <=
			OPERATOR_PRECEDENCE[parent.operator]
		);
	}

	return (
		OPERATOR_PRECEDENCE[/** @type {BinaryExpression} */ (node).operator] <
		OPERATOR_PRECEDENCE[parent.operator]
	);
}

/** @param {Node} node */
function has_call_expression(node) {
	while (node) {
		if (node.type[0] === 'CallExpression') {
			return true;
		} else if (node.type === 'MemberExpression') {
			node = node.object;
		} else {
			return false;
		}
	}
}

/** @param {Chunk[]} chunks */
const has_newline = (chunks) => {
	for (let i = 0; i < chunks.length; i += 1) {
		if (chunks[i].has_newline) return true;
	}
	return false;
};

/** @param {Chunk[]} chunks */
const get_length = (chunks) => {
	let total = 0;
	for (let i = 0; i < chunks.length; i += 1) {
		total += chunks[i].content.length;
	}
	return total;
};

/**
 * @param {number} a
 * @param {number} b
 */
const sum = (a, b) => a + b;

/**
 * @param {Chunk[][]} nodes
 * @param {Chunk} separator
 * @returns {Chunk[]}
 */
const join = (nodes, separator) => {
	if (nodes.length === 0) return [];

	const joined = [...nodes[0]];
	for (let i = 1; i < nodes.length; i += 1) {
		joined.push(separator);
		push_array(joined, nodes[i]);
	}
	return joined;
};

/**
 * @param {(node: any, state: State) => Chunk[]} fn
 */
const scoped = (fn) => {
	/**
	 * @param {any} node
	 * @param {State} state
	 */
	const scoped_fn = (node, state) => {
		return fn(node, {
			...state,
			scope: state.scope_map.get(node)
		});
	};

	return scoped_fn;
};

/**
 * @param {string} name
 * @param {Set<string>} names
 */
const deconflict = (name, names) => {
	const original = name;
	let i = 1;

	while (names.has(name)) {
		name = `${original}$${i++}`;
	}

	return name;
};

/**
 * @param {Node[]} nodes
 * @param {State} state
 */
const handle_body = (nodes, state) => {
	const chunks = [];

	const body = nodes.map((statement) => {
		const chunks = handle(statement, {
			...state,
			indent: state.indent
		});

		let add_newline = false;

		while (state.comments.length) {
			const comment = state.comments.shift();
			const prefix = add_newline ? `\n${state.indent}` : ` `;

			chunks.push(
				c(
					comment.type === 'Block'
						? `${prefix}/*${comment.value}*/`
						: `${prefix}//${comment.value}`
				)
			);

			add_newline = comment.type === 'Line';
		}

		return chunks;
	});

	let needed_padding = false;

	for (let i = 0; i < body.length; i += 1) {
		const needs_padding = has_newline(body[i]);

		if (i > 0) {
			chunks.push(
				c(
					needs_padding || needed_padding
						? `\n\n${state.indent}`
						: `\n${state.indent}`
				)
			);
		}

		push_array(chunks, body[i]);

		needed_padding = needs_padding;
	}

	return chunks;
};

/**
 * @param {VariableDeclaration} node
 * @param {State} state
 */
const handle_var_declaration = (node, state) => {
	const chunks = [c(`${node.kind} `)];

	const declarators = node.declarations.map((d) =>
		handle(d, {
			...state,
			indent: state.indent + (node.declarations.length === 1 ? '' : '\t')
		})
	);

	const multiple_lines =
		declarators.some(has_newline) ||
		declarators.map(get_length).reduce(sum, 0) +
			(state.indent.length + declarators.length - 1) * 2 >
			80;

	const separator = c(multiple_lines ? `,\n${state.indent}\t` : ', ');

	push_array(chunks, join(declarators, separator));

	return chunks;
};

/** @type {Record<string, Handler>} */
const handlers = {
	Program(node, state) {
		return handle_body(node.body, state);
	},

	BlockStatement: scoped((node, state) => {
		return [
			c(`{\n${state.indent}\t`),
			...handle_body(node.body, { ...state, indent: state.indent + '\t' }),
			c(`\n${state.indent}}`)
		];
	}),

	EmptyStatement(node, state) {
		return [c(';')];
	},

	ParenthesizedExpression(node, state) {
		return handle(node.expression, state);
	},

	ExpressionStatement(node, state) {
		if (
			node.expression.type === 'AssignmentExpression' &&
			node.expression.left.type === 'ObjectPattern'
		) {
			// is an AssignmentExpression to an ObjectPattern
			return [c('('), ...handle(node.expression, state), c(');')];
		}

		return [...handle(node.expression, state), c(';')];
	},

	IfStatement(node, state) {
		const chunks = [
			c('if ('),
			...handle(node.test, state),
			c(') '),
			...handle(node.consequent, state)
		];

		if (node.alternate) {
			chunks.push(c(' else '));
			push_array(chunks, handle(node.alternate, state));
		}

		return chunks;
	},

	LabeledStatement(node, state) {
		return [...handle(node.label, state), c(': '), ...handle(node.body, state)];
	},

	BreakStatement(node, state) {
		return node.label
			? [c('break '), ...handle(node.label, state), c(';')]
			: [c('break;')];
	},

	ContinueStatement(node, state) {
		return node.label
			? [c('continue '), ...handle(node.label, state), c(';')]
			: [c('continue;')];
	},

	WithStatement(node, state) {
		return [
			c('with ('),
			...handle(node.object, state),
			c(') '),
			...handle(node.body, state)
		];
	},

	SwitchStatement(/** @type {SwitchStatement} */ node, state) {
		const chunks = [
			c('switch ('),
			...handle(node.discriminant, state),
			c(') {')
		];

		node.cases.forEach((block) => {
			if (block.test) {
				chunks.push(c(`\n${state.indent}\tcase `));
				push_array(
					chunks,
					handle(block.test, { ...state, indent: `${state.indent}\t` })
				);
				chunks.push(c(':'));
			} else {
				chunks.push(c(`\n${state.indent}\tdefault:`));
			}

			block.consequent.forEach((statement) => {
				chunks.push(c(`\n${state.indent}\t\t`));
				push_array(
					chunks,
					handle(statement, { ...state, indent: `${state.indent}\t\t` })
				);
			});
		});

		chunks.push(c(`\n${state.indent}}`));

		return chunks;
	},

	ReturnStatement(node, state) {
		if (node.argument) {
			const contains_comment =
				node.argument.leadingComments &&
				node.argument.leadingComments.some(
					(
						/** @type import('../utils/comments.js').CommentWithLocation */ comment
					) => comment.has_trailing_newline
				);
			return [
				c(contains_comment ? 'return (' : 'return '),
				...handle(node.argument, state),
				c(contains_comment ? ');' : ';')
			];
		} else {
			return [c('return;')];
		}
	},

	ThrowStatement(node, state) {
		return [c('throw '), ...handle(node.argument, state), c(';')];
	},

	TryStatement(node, state) {
		const chunks = [c('try '), ...handle(node.block, state)];

		if (node.handler) {
			if (node.handler.param) {
				chunks.push(c(' catch('));
				push_array(chunks, handle(node.handler.param, state));
				chunks.push(c(') '));
			} else {
				chunks.push(c(' catch '));
			}

			push_array(chunks, handle(node.handler.body, state));
		}

		if (node.finalizer) {
			chunks.push(c(' finally '));
			push_array(chunks, handle(node.finalizer, state));
		}

		return chunks;
	},

	WhileStatement(node, state) {
		return [
			c('while ('),
			...handle(node.test, state),
			c(') '),
			...handle(node.body, state)
		];
	},

	DoWhileStatement(node, state) {
		return [
			c('do '),
			...handle(node.body, state),
			c(' while ('),
			...handle(node.test, state),
			c(');')
		];
	},

	ForStatement: scoped((node, state) => {
		const chunks = [c('for (')];

		if (node.init) {
			if (node.init.type === 'VariableDeclaration') {
				push_array(chunks, handle_var_declaration(node.init, state));
			} else {
				push_array(chunks, handle(node.init, state));
			}
		}

		chunks.push(c('; '));
		if (node.test) push_array(chunks, handle(node.test, state));
		chunks.push(c('; '));
		if (node.update) push_array(chunks, handle(node.update, state));

		chunks.push(c(') '));
		push_array(chunks, handle(node.body, state));

		return chunks;
	}),

	ForInStatement: scoped((node, state) => {
		const chunks = [c(`for ${node.await ? 'await ' : ''}(`)];

		if (node.left.type === 'VariableDeclaration') {
			push_array(chunks, handle_var_declaration(node.left, state));
		} else {
			push_array(chunks, handle(node.left, state));
		}

		chunks.push(c(node.type === 'ForInStatement' ? ` in ` : ` of `));
		push_array(chunks, handle(node.right, state));
		chunks.push(c(') '));
		push_array(chunks, handle(node.body, state));

		return chunks;
	}),

	DebuggerStatement(node, state) {
		return [c('debugger', node), c(';')];
	},

	FunctionDeclaration: scoped(
		(/** @type {FunctionDeclaration} */ node, state) => {
			const chunks = [];

			if (node.async) chunks.push(c('async '));
			chunks.push(c(node.generator ? 'function* ' : 'function '));
			if (node.id) push_array(chunks, handle(node.id, state));
			chunks.push(c('('));

			const params = node.params.map((p) =>
				handle(p, {
					...state,
					indent: state.indent + '\t'
				})
			);

			const multiple_lines =
				params.some(has_newline) ||
				params.map(get_length).reduce(sum, 0) +
					(state.indent.length + params.length - 1) * 2 >
					80;

			const separator = c(multiple_lines ? `,\n${state.indent}` : ', ');

			if (multiple_lines) {
				chunks.push(c(`\n${state.indent}\t`));
				push_array(chunks, join(params, separator));
				chunks.push(c(`\n${state.indent}`));
			} else {
				push_array(chunks, join(params, separator));
			}

			chunks.push(c(') '));
			push_array(chunks, handle(node.body, state));

			return chunks;
		}
	),

	VariableDeclaration(node, state) {
		return handle_var_declaration(node, state).concat(c(';'));
	},

	VariableDeclarator(node, state) {
		if (node.init) {
			return [...handle(node.id, state), c(' = '), ...handle(node.init, state)];
		} else {
			return handle(node.id, state);
		}
	},

	ClassDeclaration(node, state) {
		const chunks = [c('class ')];

		if (node.id) {
			push_array(chunks, handle(node.id, state));
			chunks.push(c(' '));
		}

		if (node.superClass) {
			chunks.push(c('extends '));
			push_array(chunks, handle(node.superClass, state));
			chunks.push(c(' '));
		}

		push_array(chunks, handle(node.body, state));

		return chunks;
	},

	ImportDeclaration(/** @type {ImportDeclaration} */ node, state) {
		const chunks = [c('import ')];

		const { length } = node.specifiers;
		const source = handle(node.source, state);

		if (length > 0) {
			let i = 0;

			while (i < length) {
				if (i > 0) {
					chunks.push(c(', '));
				}

				const specifier = node.specifiers[i];

				if (specifier.type === 'ImportDefaultSpecifier') {
					chunks.push(c(specifier.local.name, specifier));
					i += 1;
				} else if (specifier.type === 'ImportNamespaceSpecifier') {
					chunks.push(c('* as ' + specifier.local.name, specifier));
					i += 1;
				} else {
					break;
				}
			}

			if (i < length) {
				// we have named specifiers
				const specifiers = node.specifiers
					.slice(i)
					.map((/** @type {ImportSpecifier} */ specifier) => {
						const name = handle(specifier.imported, state)[0];
						const as = handle(specifier.local, state)[0];

						if (name.content === as.content) {
							return [as];
						}

						return [name, c(' as '), as];
					});

				const width =
					get_length(chunks) +
					specifiers.map(get_length).reduce(sum, 0) +
					2 * specifiers.length +
					6 +
					get_length(source);

				if (width > 80) {
					chunks.push(c(`{\n\t`));
					push_array(chunks, join(specifiers, c(',\n\t')));
					chunks.push(c('\n}'));
				} else {
					chunks.push(c(`{ `));
					push_array(chunks, join(specifiers, c(', ')));
					chunks.push(c(' }'));
				}
			}

			chunks.push(c(' from '));
		}

		push_array(chunks, source);
		chunks.push(c(';'));

		return chunks;
	},

	ImportExpression(node, state) {
		return [c('import('), ...handle(node.source, state), c(')')];
	},

	ExportDefaultDeclaration(node, state) {
		const chunks = [c(`export default `), ...handle(node.declaration, state)];

		if (node.declaration.type !== 'FunctionDeclaration') {
			chunks.push(c(';'));
		}

		return chunks;
	},

	ExportNamedDeclaration(node, state) {
		const chunks = [c('export ')];

		if (node.declaration) {
			push_array(chunks, handle(node.declaration, state));
		} else {
			const specifiers = node.specifiers.map(
				(/** @type {ExportSpecifier} */ specifier) => {
					const name = handle(specifier.local, state)[0];
					const as = handle(specifier.exported, state)[0];

					if (name.content === as.content) {
						return [name];
					}

					return [name, c(' as '), as];
				}
			);

			const width =
				7 + specifiers.map(get_length).reduce(sum, 0) + 2 * specifiers.length;

			if (width > 80) {
				chunks.push(c('{\n\t'));
				push_array(chunks, join(specifiers, c(',\n\t')));
				chunks.push(c('\n}'));
			} else {
				chunks.push(c('{ '));
				push_array(chunks, join(specifiers, c(', ')));
				chunks.push(c(' }'));
			}

			if (node.source) {
				chunks.push(c(' from '));
				push_array(chunks, handle(node.source, state));
			}
		}

		chunks.push(c(';'));

		return chunks;
	},

	ExportAllDeclaration(node, state) {
		return [c(`export * from `), ...handle(node.source, state), c(`;`)];
	},

	MethodDefinition(node, state) {
		const chunks = [];

		if (node.static) {
			chunks.push(c('static '));
		}

		if (node.kind === 'get' || node.kind === 'set') {
			// Getter or setter
			chunks.push(c(node.kind + ' '));
		}

		if (node.value.async) {
			chunks.push(c('async '));
		}

		if (node.value.generator) {
			chunks.push(c('*'));
		}

		if (node.computed) {
			chunks.push(c('['));
			push_array(chunks, handle(node.key, state));
			chunks.push(c(']'));
		} else {
			push_array(chunks, handle(node.key, state));
		}

		chunks.push(c('('));

		const { params } = node.value;
		for (let i = 0; i < params.length; i += 1) {
			push_array(chunks, handle(params[i], state));
			if (i < params.length - 1) chunks.push(c(', '));
		}

		chunks.push(c(') '));
		push_array(chunks, handle(node.value.body, state));

		return chunks;
	},

	ArrowFunctionExpression: scoped(
		(/** @type {ArrowFunctionExpression} */ node, state) => {
			const chunks = [];

			if (node.async) chunks.push(c('async '));

			if (node.params.length === 1 && node.params[0].type === 'Identifier') {
				push_array(chunks, handle(node.params[0], state));
			} else {
				const params = node.params.map((param) =>
					handle(param, {
						...state,
						indent: state.indent + '\t'
					})
				);

				chunks.push(c('('));
				push_array(chunks, join(params, c(', ')));
				chunks.push(c(')'));
			}

			chunks.push(c(' => '));

			if (
				node.body.type === 'ObjectExpression' ||
				(node.body.type === 'AssignmentExpression' &&
					node.body.left.type === 'ObjectPattern')
			) {
				chunks.push(c('('));
				push_array(chunks, handle(node.body, state));
				chunks.push(c(')'));
			} else {
				push_array(chunks, handle(node.body, state));
			}

			return chunks;
		}
	),

	ThisExpression(node, state) {
		return [c('this', node)];
	},

	Super(node, state) {
		return [c('super', node)];
	},

	RestElement(node, state) {
		return [c('...'), ...handle(node.argument, state)];
	},

	YieldExpression(node, state) {
		if (node.argument) {
			return [
				c(node.delegate ? `yield* ` : `yield `),
				...handle(node.argument, state)
			];
		}

		return [c(node.delegate ? `yield*` : `yield`)];
	},

	AwaitExpression(node, state) {
		if (node.argument) {
			const precedence = EXPRESSIONS_PRECEDENCE[node.argument.type];

			if (precedence && precedence < EXPRESSIONS_PRECEDENCE.AwaitExpression) {
				return [c('await ('), ...handle(node.argument, state), c(')')];
			} else {
				return [c('await '), ...handle(node.argument, state)];
			}
		}

		return [c('await')];
	},

	TemplateLiteral(node, state) {
		const chunks = [c('`')];

		const { quasis, expressions } = node;

		for (let i = 0; i < expressions.length; i++) {
			chunks.push(c(quasis[i].value.raw), c('${'));
			push_array(chunks, handle(expressions[i], state));
			chunks.push(c('}'));
		}

		chunks.push(c(quasis[quasis.length - 1].value.raw), c('`'));

		return chunks;
	},

	TaggedTemplateExpression(node, state) {
		return handle(node.tag, state).concat(handle(node.quasi, state));
	},

	ArrayExpression(node, state) {
		const chunks = [c('[')];

		/** @type {Chunk[][]} */
		const elements = [];

		/** @type {Chunk[]} */
		let sparse_commas = [];

		for (let i = 0; i < node.elements.length; i += 1) {
			// can't use map/forEach because of sparse arrays
			const element = node.elements[i];
			if (element) {
				elements.push([
					...sparse_commas,
					...handle(element, {
						...state,
						indent: state.indent + '\t'
					})
				]);
				sparse_commas = [];
			} else {
				sparse_commas.push(c(','));
			}
		}

		const multiple_lines =
			elements.some(has_newline) ||
			elements.map(get_length).reduce(sum, 0) +
				(state.indent.length + elements.length - 1) * 2 >
				80;

		if (multiple_lines) {
			chunks.push(c(`\n${state.indent}\t`));
			push_array(chunks, join(elements, c(`,\n${state.indent}\t`)));
			chunks.push(c(`\n${state.indent}`));
			push_array(chunks, sparse_commas);
		} else {
			push_array(chunks, join(elements, c(', ')));
			push_array(chunks, sparse_commas);
		}

		chunks.push(c(']'));

		return chunks;
	},

	ObjectExpression(/** @type {ObjectExpression} */ node, state) {
		if (node.properties.length === 0) {
			return [c('{}')];
		}

		let has_inline_comment = false;

		/** @type {Chunk[]} */
		const chunks = [];
		const separator = c(', ');

		node.properties.forEach((p, i) => {
			push_array(
				chunks,
				handle(p, {
					...state,
					indent: state.indent + '\t'
				})
			);

			if (state.comments.length) {
				// TODO generalise this, so it works with ArrayExpressions and other things.
				// At present, stuff will just get appended to the closest statement/declaration
				chunks.push(c(', '));

				while (state.comments.length) {
					const comment = state.comments.shift();

					chunks.push(
						c(
							comment.type === 'Block'
								? `/*${comment.value}*/\n${state.indent}\t`
								: `//${comment.value}\n${state.indent}\t`
						)
					);

					if (comment.type === 'Line') {
						has_inline_comment = true;
					}
				}
			} else {
				if (i < node.properties.length - 1) {
					chunks.push(separator);
				}
			}
		});

		const multiple_lines =
			has_inline_comment || has_newline(chunks) || get_length(chunks) > 40;

		if (multiple_lines) {
			separator.content = `,\n${state.indent}\t`;
		}

		return [
			c(multiple_lines ? `{\n${state.indent}\t` : `{ `),
			...chunks,
			c(multiple_lines ? `\n${state.indent}}` : ` }`)
		];
	},

	Property(node, state) {
		const value = handle(node.value, state);

		if (node.key === node.value) {
			return value;
		}

		// special case
		if (
			!node.computed &&
			node.value.type === 'AssignmentPattern' &&
			node.value.left.type === 'Identifier' &&
			node.value.left.name === node.key.name
		) {
			return value;
		}

		if (
			!node.computed &&
			node.value.type === 'Identifier' &&
			((node.key.type === 'Identifier' && node.key.name === value[0].content) ||
				(node.key.type === 'Literal' && node.key.value === value[0].content))
		) {
			return value;
		}

		const key = handle(node.key, state);

		if (node.value.type === 'FunctionExpression' && !node.value.id) {
			state = {
				...state,
				scope: state.scope_map.get(node.value)
			};

			const chunks = node.kind !== 'init' ? [c(`${node.kind} `)] : [];

			if (node.value.async) {
				chunks.push(c('async '));
			}
			if (node.value.generator) {
				chunks.push(c('*'));
			}

			push_array(chunks, node.computed ? [c('['), ...key, c(']')] : key);
			chunks.push(c('('));
			push_array(
				chunks,
				join(
					node.value.params.map((/** @type {Pattern} */ param) =>
						handle(param, state)
					),
					c(', ')
				)
			);
			chunks.push(c(') '));
			push_array(chunks, handle(node.value.body, state));

			return chunks;
		}

		if (node.computed) {
			return [c('['), ...key, c(']: '), ...value];
		}

		return [...key, c(': '), ...value];
	},

	ObjectPattern(node, state) {
		const chunks = [c('{ ')];

		for (let i = 0; i < node.properties.length; i += 1) {
			push_array(chunks, handle(node.properties[i], state));
			if (i < node.properties.length - 1) chunks.push(c(', '));
		}

		chunks.push(c(' }'));

		return chunks;
	},

	SequenceExpression(/** @type {SequenceExpression} */ node, state) {
		const expressions = node.expressions.map((e) => handle(e, state));

		return [c('('), ...join(expressions, c(', ')), c(')')];
	},

	UnaryExpression(node, state) {
		const chunks = [c(node.operator)];

		if (node.operator.length > 1) {
			chunks.push(c(' '));
		}

		if (
			EXPRESSIONS_PRECEDENCE[node.argument.type] <
			EXPRESSIONS_PRECEDENCE.UnaryExpression
		) {
			chunks.push(c('('));
			push_array(chunks, handle(node.argument, state));
			chunks.push(c(')'));
		} else {
			push_array(chunks, handle(node.argument, state));
		}

		return chunks;
	},

	UpdateExpression(node, state) {
		return node.prefix
			? [c(node.operator), ...handle(node.argument, state)]
			: [...handle(node.argument, state), c(node.operator)];
	},

	AssignmentExpression(node, state) {
		return [
			...handle(node.left, state),
			c(` ${node.operator || '='} `),
			...handle(node.right, state)
		];
	},

	BinaryExpression(node, state) {
		/**
		 * @type any[]
		 */
		const chunks = [];

		// TODO
		// const is_in = node.operator === 'in';
		// if (is_in) {
		// 	// Avoids confusion in `for` loops initializers
		// 	chunks.push(c('('));
		// }

		if (needs_parens(node.left, node, false)) {
			chunks.push(c('('));
			push_array(chunks, handle(node.left, state));
			chunks.push(c(')'));
		} else {
			push_array(chunks, handle(node.left, state));
		}

		chunks.push(c(` ${node.operator} `));

		if (needs_parens(node.right, node, true)) {
			chunks.push(c('('));
			push_array(chunks, handle(node.right, state));
			chunks.push(c(')'));
		} else {
			push_array(chunks, handle(node.right, state));
		}

		return chunks;
	},

	ConditionalExpression(node, state) {
		/**
		 * @type any[]
		 */
		const chunks = [];

		if (
			EXPRESSIONS_PRECEDENCE[node.test.type] >
			EXPRESSIONS_PRECEDENCE.ConditionalExpression
		) {
			push_array(chunks, handle(node.test, state));
		} else {
			chunks.push(c('('));
			push_array(chunks, handle(node.test, state));
			chunks.push(c(')'));
		}

		const child_state = { ...state, indent: state.indent + '\t' };

		const consequent = handle(node.consequent, child_state);
		const alternate = handle(node.alternate, child_state);

		const multiple_lines =
			has_newline(consequent) ||
			has_newline(alternate) ||
			get_length(chunks) + get_length(consequent) + get_length(alternate) > 50;

		if (multiple_lines) {
			chunks.push(c(`\n${state.indent}? `));
			push_array(chunks, consequent);
			chunks.push(c(`\n${state.indent}: `));
			push_array(chunks, alternate);
		} else {
			chunks.push(c(` ? `));
			push_array(chunks, consequent);
			chunks.push(c(` : `));
			push_array(chunks, alternate);
		}

		return chunks;
	},

	NewExpression(/** @type {NewExpression} */ node, state) {
		const chunks = [c('new ')];

		if (
			EXPRESSIONS_PRECEDENCE[node.callee.type] <
				EXPRESSIONS_PRECEDENCE.CallExpression ||
			has_call_expression(node.callee)
		) {
			chunks.push(c('('));
			push_array(chunks, handle(node.callee, state));
			chunks.push(c(')'));
		} else {
			push_array(chunks, handle(node.callee, state));
		}

		// TODO this is copied from CallExpression — DRY it out
		const args = node.arguments.map((arg) =>
			handle(arg, {
				...state,
				indent: state.indent + '\t'
			})
		);

		const separator = args.some(has_newline) // TODO or length exceeds 80
			? c(',\n' + state.indent)
			: c(', ');

		chunks.push(c('('));
		push_array(chunks, join(args, separator));
		chunks.push(c(')'));

		return chunks;
	},

	ChainExpression(node, state) {
		return handle(node.expression, state);
	},

	CallExpression(/** @type {CallExpression} */ node, state) {
		/**
		 * @type any[]
		 */
		const chunks = [];

		if (
			EXPRESSIONS_PRECEDENCE[node.callee.type] <
			EXPRESSIONS_PRECEDENCE.CallExpression
		) {
			chunks.push(c('('));
			push_array(chunks, handle(node.callee, state));
			chunks.push(c(')'));
		} else {
			push_array(chunks, handle(node.callee, state));
		}

		if (/** @type {SimpleCallExpression} */ (node).optional) {
			chunks.push(c('?.'));
		}

		let has_inline_comment = false;
		let arg_chunks = [];
		outer: for (const arg of node.arguments) {
			const chunks = [];
			while (state.comments.length) {
				const comment = state.comments.shift();
				if (comment.type === 'Line') {
					has_inline_comment = true;
					break outer;
				}
				chunks.push(
					c(
						comment.type === 'Block'
							? `/*${comment.value}*/ `
							: `//${comment.value}`
					)
				);
			}
			push_array(chunks, handle(arg, state));
			arg_chunks.push(chunks);
		}

		const multiple_lines =
			has_inline_comment || arg_chunks.slice(0, -1).some(has_newline); // TODO or length exceeds 80
		if (multiple_lines) {
			// need to handle args again. TODO find alternative approach?
			const args = node.arguments.map((arg, i) => {
				const chunks = handle(arg, {
					...state,
					indent: `${state.indent}\t`
				});
				if (i < node.arguments.length - 1) chunks.push(c(','));
				while (state.comments.length) {
					const comment = state.comments.shift();
					chunks.push(
						c(
							comment.type === 'Block'
								? ` /*${comment.value}*/ `
								: ` //${comment.value}`
						)
					);
				}
				return chunks;
			});

			chunks.push(c(`(\n${state.indent}\t`));
			push_array(chunks, join(args, c(`\n${state.indent}\t`)));
			chunks.push(c(`\n${state.indent})`));
		} else {
			chunks.push(c('('));
			push_array(chunks, join(arg_chunks, c(', ')));
			chunks.push(c(')'));
		}

		return chunks;
	},

	MemberExpression(node, state) {
		/**
		 * @type any[]
		 */
		const chunks = [];

		if (
			EXPRESSIONS_PRECEDENCE[node.object.type] <
			EXPRESSIONS_PRECEDENCE.MemberExpression
		) {
			chunks.push(c('('));
			push_array(chunks, handle(node.object, state));
			chunks.push(c(')'));
		} else {
			push_array(chunks, handle(node.object, state));
		}

		if (node.computed) {
			if (node.optional) {
				chunks.push(c('?.'));
			}
			chunks.push(c('['));
			push_array(chunks, handle(node.property, state));
			chunks.push(c(']'));
		} else {
			chunks.push(c(node.optional ? '?.' : '.'));
			push_array(chunks, handle(node.property, state));
		}

		return chunks;
	},

	MetaProperty(node, state) {
		return [
			...handle(node.meta, state),
			c('.'),
			...handle(node.property, state)
		];
	},

	Identifier(node, state) {
		let name = node.name;

		if (name[0] === '@') {
			name = state.getName(name.slice(1));
		} else if (node.name[0] === '#') {
			const owner = state.scope.find_owner(node.name);

			if (!owner) {
				throw new Error(`Could not find owner for node`);
			}

			if (!state.deconflicted.has(owner)) {
				state.deconflicted.set(owner, new Map());
			}

			const deconflict_map = state.deconflicted.get(owner);

			if (!deconflict_map.has(node.name)) {
				deconflict_map.set(
					node.name,
					deconflict(node.name.slice(1), owner.references)
				);
			}

			name = deconflict_map.get(node.name);
		}

		return [c(name, node)];
	},

	Literal(/** @type {Literal} */ node, state) {
		if (typeof node.value === 'string') {
			return [
				// TODO do we need to handle weird unicode characters somehow?
				// str.replace(/\\u(\d{4})/g, (m, n) => String.fromCharCode(+n))
				c(
					(node.raw || JSON.stringify(node.value)).replace(
						re,
						(_m, _i, at, hash, name) => {
							if (at) return '@' + name;
							if (hash) return '#' + name;
							throw new Error(`this shouldn't happen`);
						}
					),
					node
				)
			];
		}

		return [c(node.raw || String(node.value), node)];
	},

	PropertyDefinition(/** @type {PropertyDefinition} */ node, state) {
		const chunks = [];

		if (node.static) {
			chunks.push(c('static '));
		}

		if (node.computed) {
			chunks.push(c('['), ...handle(node.key, state), c(']'));
		} else {
			chunks.push(...handle(node.key, state));
		}

		if (node.value) {
			chunks.push(c(' = '));

			chunks.push(...handle(node.value, state));
		}

		chunks.push(c(';'));

		return chunks;
	},

	StaticBlock(/** @type {StaticBlock} */ node, state) {
		const chunks = [c('static ')];

		push_array(chunks, handlers.BlockStatement(node, state));

		return chunks;
	},

	PrivateIdentifier(/** @type {PrivateIdenifier} */ node, state) {
		const chunks = [c('#')];

		push_array(chunks, [c(node.name, node)]);

		return chunks;
	}
};

handlers.ForOfStatement = handlers.ForInStatement;
handlers.FunctionExpression = handlers.FunctionDeclaration;
handlers.ClassExpression = handlers.ClassDeclaration;
handlers.ClassBody = handlers.BlockStatement;
handlers.SpreadElement = handlers.RestElement;
handlers.ArrayPattern = handlers.ArrayExpression;
handlers.LogicalExpression = handlers.BinaryExpression;
handlers.AssignmentPattern = handlers.AssignmentExpression;


================================================
FILE: src/print/index.js
================================================
import * as perisopic from 'periscopic';
import { handle } from './handlers.js';
import { encode } from '@jridgewell/sourcemap-codec';

/** @type {(str?: string) => string} str */
let btoa = () => {
	throw new Error(
		'Unsupported environment: `window.btoa` or `Buffer` should be supported.'
	);
};

if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
	btoa = (str) => window.btoa(unescape(encodeURIComponent(str)));
} else if (typeof Buffer === 'function') {
	btoa = (str) => Buffer.from(str, 'utf-8').toString('base64');
}

/** @typedef {import('estree').Node} Node */

/**
 * @typedef {{
 *   file?: string;
 *   sourceMapSource?: string;
 *   sourceMapContent?: string;
 *   sourceMapEncodeMappings?: boolean; // default true
 *   getName?: (name: string) => string;
 * }} PrintOptions
 */

/**
 * @param {Node} node
 * @param {PrintOptions} opts
 * @returns {{ code: string, map: any }} // TODO
 */
export function print(node, opts = {}) {
	if (Array.isArray(node)) {
		return print(
			{
				type: 'Program',
				body: node,
				sourceType: 'module'
			},
			opts
		);
	}

	const {
		getName = /** @param {string} x */ (x) => {
			throw new Error(`Unhandled sigil @${x}`);
		}
	} = opts;

	let { map: scope_map, scope } = perisopic.analyze(node);
	const deconflicted = new WeakMap();

	const chunks = handle(node, {
		indent: '',
		getName,
		scope,
		scope_map,
		deconflicted,
		comments: []
	});

	/** @typedef {[number, number, number, number]} Segment */

	let code = '';
	let current_column = 0;

	/** @type {Segment[][]} */
	let mappings = [];

	/** @type {Segment[]} */
	let current_line = [];

	for (let i = 0; i < chunks.length; i += 1) {
		const chunk = chunks[i];

		code += chunk.content;

		if (chunk.loc) {
			current_line.push([
				current_column,
				0, // source index is always zero
				chunk.loc.start.line - 1,
				chunk.loc.start.column
			]);
		}

		for (let i = 0; i < chunk.content.length; i += 1) {
			if (chunk.content[i] === '\n') {
				mappings.push(current_line);
				current_line = [];
				current_column = 0;
			} else {
				current_column += 1;
			}
		}

		if (chunk.loc) {
			current_line.push([
				current_column,
				0, // source index is always zero
				chunk.loc.end.line - 1,
				chunk.loc.end.column
			]);
		}
	}

	mappings.push(current_line);

	const map = {
		version: 3,
		/** @type {string[]} */
		names: [],
		sources: [opts.sourceMapSource || null],
		sourcesContent: [opts.sourceMapContent || null],
		mappings:
			opts.sourceMapEncodeMappings == undefined || opts.sourceMapEncodeMappings
				? encode(mappings)
				: mappings
	};

	Object.defineProperties(map, {
		toString: {
			enumerable: false,
			value: function toString() {
				return JSON.stringify(this);
			}
		},
		toUrl: {
			enumerable: false,
			value: function toUrl() {
				return (
					'data:application/json;charset=utf-8;base64,' + btoa(this.toString())
				);
			}
		}
	});

	return {
		code,
		map
	};
}


================================================
FILE: src/utils/comments.js
================================================
import { re } from './id.js';

/** @typedef {import('estree').Comment} Comment */
/** @typedef {import('estree').Node} Node */

/**
 * @typedef {Node & {
 *   start: number;
 *   end: number;
 *   has_trailing_newline?: boolean
 * }} NodeWithLocation
 */

/**
 * @typedef {Comment & {
 *   start: number;
 *   end: number;
 *   has_trailing_newline?: boolean
 * }} CommentWithLocation
 */

/**
 * @param {CommentWithLocation[]} comments
 * @param {string} raw
 */
export const get_comment_handlers = (comments, raw) => ({
	// pass to acorn options
	/**
	 * @param {boolean} block
	 * @param {string} value
	 * @param {number} start
	 * @param {number} end
	 */
	onComment: (block, value, start, end) => {
		if (block && /\n/.test(value)) {
			let a = start;
			while (a > 0 && raw[a - 1] !== '\n') a -= 1;

			let b = a;
			while (/[ \t]/.test(raw[b])) b += 1;

			const indentation = raw.slice(a, b);
			value = value.replace(new RegExp(`^${indentation}`, 'gm'), '');
		}

		comments.push({ type: block ? 'Block' : 'Line', value, start, end });
	},

	// pass to estree-walker options
	/** @param {NodeWithLocation} node */
	enter(node) {
		let comment;

		while (comments[0] && comments[0].start < node.start) {
			comment = comments.shift();

			comment.value = comment.value.replace(
				re,
				(match, id, at, hash, value) => {
					if (hash) return `#${value}`;
					if (at) return `@${value}`;

					return match;
				}
			);

			const next = comments[0] || node;
			comment.has_trailing_newline =
				comment.type === 'Line' ||
				/\n/.test(raw.slice(comment.end, next.start));

			(node.leadingComments || (node.leadingComments = [])).push(comment);
		}
	},

	/** @param {NodeWithLocation} node */
	leave(node) {
		if (comments[0]) {
			const slice = raw.slice(node.end, comments[0].start);

			if (/^[,) \t]*$/.test(slice)) {
				node.trailingComments = [comments.shift()];
			}
		}
	}
});


================================================
FILE: src/utils/id.js
================================================
// generate an ID that is, to all intents and purposes, unique
export const id = Math.round(Math.random() * 1e20).toString(36);
export const re = new RegExp(`_${id}_(?:(\\d+)|(AT)|(HASH))_(\\w+)?`, 'g');


================================================
FILE: src/utils/push_array.js
================================================
/**
 * Does `array.push` for all `items`. Needed because `array.push(...items)` throws
 * "Maximum call stack size exceeded" when `items` is too big of an array.
 *
 * @param {any[]} array
 * @param {any[]} items
 */
export function push_array(array, items) {
	for (let i = 0; i < items.length; i++) {
		array.push(items[i]);
	}
}


================================================
FILE: test/samples/array-expressions/expected.js
================================================
a = [1, 2, 3];

b = [
	'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
];

sparse = [,,,];

================================================
FILE: test/samples/array-expressions/input.js
================================================
export default ({ b }) => b`
a = [1, 2, 3];

b = ['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];

sparse = [,,,];
`;

================================================
FILE: test/samples/arrow-function-as-statement/expected.js
================================================
() => {

};

================================================
FILE: test/samples/arrow-function-as-statement/input.js
================================================
export default ({ b }) => b`() => {};`;

================================================
FILE: test/samples/arrow-function-assignment-object-pattern/expected.js
================================================
let a = () => ({ x } = { x: 42 });

================================================
FILE: test/samples/arrow-function-assignment-object-pattern/input.js
================================================
export default ({ b }) => b`let a = () => ({ x } = { x: 42 });`;


================================================
FILE: test/samples/arrow-function-parenthesized/expected.js
================================================
foo || (bar => bar);

================================================
FILE: test/samples/arrow-function-parenthesized/input.js
================================================
export default ({ b, x }) => b`foo || ${x`bar => bar`}`;

================================================
FILE: test/samples/at-prefix/expected.js
================================================
FOO(bar)

================================================
FILE: test/samples/at-prefix/input.js
================================================
export default ({ x }) => x`@foo(bar)`;

================================================
FILE: test/samples/await-precedence/expected.js
================================================
await (a || b);
await c;

================================================
FILE: test/samples/await-precedence/input.js
================================================
export default ({ b }) => b`
await (a || b);
await c;
`;

================================================
FILE: test/samples/basic/expected.js
================================================
foo({ a: 1 });
"#foo";
"@foo";

================================================
FILE: test/samples/basic/input.js
================================================
export default ({ b }) => b`
foo({ a: 1 });
"#foo";
"@foo";
`;

================================================
FILE: test/samples/bigint/expected.js
================================================
12345n

================================================
FILE: test/samples/bigint/input.js
================================================
export default ({ x }) => x`12345n`;

================================================
FILE: test/samples/break-continue/expected.js
================================================
x: for (let i = 0; i < 10; i += 1) {
	if (should_break) {
		break;
	}

	if (should_break_with_label) {
		break x;
	}

	if (should_continue) {
		continue;
	}

	if (should_continue_with_label) {
		continue x;
	}
}

================================================
FILE: test/samples/break-continue/input.js
================================================
export default ({ b }) => b`
x: for (let i = 0; i < 10; i += 1) {
	if (should_break) {
		break;
	}

	if (should_break_with_label) {
		break x;
	}

	if (should_continue) {
		continue;
	}

	if (should_continue_with_label) {
		continue x;
	}
}
`;

================================================
FILE: test/samples/call-expressions/expected.js
================================================
x(a, b, c);

x(a, b, () => {
	console.log('c');
});

x(
	a,
	() => {
		console.log('b');
	},
	() => {
		console.log('c');
	}
);

================================================
FILE: test/samples/call-expressions/input.js
================================================
export default ({ b }) => b`
x(a, b, c);

x(a, b, () => {
	console.log('c');
});

x(a, () => {
	console.log('b');
}, () => {
	console.log('c');
});
`;

================================================
FILE: test/samples/chain-expressions/expected.js
================================================
foo?.bar.baz;
x?.(a, b, c);
x()?.();

================================================
FILE: test/samples/chain-expressions/input.js
================================================
export default ({ b }) => b`
foo?.bar.baz;
x?.(a, b, c);
x()?.()
`;


================================================
FILE: test/samples/class-private/expected.js
================================================
class A {
	#p;
	static #p1;
	static #p2;

	static is(obj) {
		return #p2 in obj;
	}

	get #p3() {
		
	}

	set #p3(v) {
		
	}

	#m1() {
		return this.#p1;
	}

	static #m2() {
		
	}

	*#m3() {
		
	}

	async #m4() {
		
	}
}

================================================
FILE: test/samples/class-private/input.js
================================================
export default ({ parse }) => parse(`class A {
	#p
	static #p1
	static #p2

	static is(obj) {
		return #p2 in obj
	}

	get #p3() {}
	set #p3(v) {}
	#m1() {
		return this.#p1
	}
	static #m2() {}
	*#m3() {}
	async #m4() {}
}`);

================================================
FILE: test/samples/class-property/expected.js
================================================
class Foo {
	hi;
	static foo = 1;
	[KEY] = 2;
}

================================================
FILE: test/samples/class-property/input.js
================================================
export default ({ b }) => b`
class Foo {
	hi
	static foo = 1;
	[KEY] = 2
}
`;


================================================
FILE: test/samples/class-static-block/expected.js
================================================
class Foo {
	static {
		this.abc = 1;
	}
}

================================================
FILE: test/samples/class-static-block/input.js
================================================
export default ({ b }) => b`
class Foo {
    static {
        this.abc = 1;
    }
}
`;

================================================
FILE: test/samples/comment-block/expected.js
================================================
/* comment before a node
 * second line */
console.log(1);

console.log(2); /* comment on same line as node */

const obj = {
	foo: 1, /* comment in middle of object */
	bar: 2
};

================================================
FILE: test/samples/comment-block/input.js
================================================
export default ({ b }) => b`
	/* comment before a node
	 * second line */
	console.log(1);

	console.log(2); /* comment on same line as node */

	const obj = {
		foo: 1, /* comment in middle of object */
		bar: 2
	};
`;

================================================
FILE: test/samples/comment-block-with-sigil/expected.js
================================================
a = /* #b */ c;

================================================
FILE: test/samples/comment-block-with-sigil/input.js
================================================
export default ({ b }) => b`
	a = /* #b */ c
`;

================================================
FILE: test/samples/comment-inline/expected.js
================================================
// comment before a node
// second line
console.log(1);

console.log(2); // comment on same line as node

const obj = {
	foo: 1, // comment in middle of object
	bar: 2
};

function bar() {
	return /*result*/ foo;
}

================================================
FILE: test/samples/comment-inline/input.js
================================================
export default ({ b }) => b`
	// comment before a node
	// second line
	console.log(1);

	console.log(2); // comment on same line as node

	const obj = {
		foo: 1, // comment in middle of object
		bar: 2
	};

	function bar () {
		return /*result*/ foo;
	}
`;

================================================
FILE: test/samples/comment-inline-inserted/expected.js
================================================
// comment before an inserted block
"use strict";

// comment before an inserted node
import { foo } from "wherever";

================================================
FILE: test/samples/comment-inline-inserted/input.js
================================================
export default ({ b, x }) => {
	const insert = b`"use strict";`;

	const node = {
		type: 'ImportDeclaration',
		specifiers: [{
			type: 'ImportSpecifier',
			local: { type: 'Identifier', name: 'foo' },
			imported: { type: 'Identifier', name: 'foo' }
		}],
		source: { type: 'Literal', value: 'wherever' }
	};

	return b`
		// comment before an inserted block
		${insert}

		// comment before an inserted node
		${node}
	`;
};

================================================
FILE: test/samples/comment-interpolated/expected.js
================================================
a = /* the answer */ 42

================================================
FILE: test/samples/comment-interpolated/input.js
================================================
export default ({ x }) => x`a = /* ${"the answer"} */ ${42}`

================================================
FILE: test/samples/comment-mixed-trailing/expected.js
================================================
function foo() {

} // hey1
/*
hey2
*/

================================================
FILE: test/samples/comment-mixed-trailing/input.js
================================================
export default ({ b }) => b`
	function foo() {
		// hey1
		/*
		hey2
		*/
	}
`;

================================================
FILE: test/samples/comment-within-call-expression/expected.js
================================================
console.log(null, /* xxx */ new Date(), /** zzz */ a.b.c()); // www

console.log(null, // foo
new Date());

console.log(
	null, // foo
	new Date()
);

console.log(null, /* xxx */ function (a, b) {
	// yyy
	return a + b;
});

console.log("1");

================================================
FILE: test/samples/comment-within-call-expression/input.js
================================================
export default ({ b }) => b`
	console.log(null, /* xxx */ new Date(), /** zzz */ a.b.c()); // www
	console.log(null,
		// foo
		new Date()
	);
	console.log(null, // foo
		new Date()
	);
	console.log(null, /* xxx */ function (a, b) {
		// yyy
		return a + b;
	})
	console.log("1");
`;

================================================
FILE: test/samples/comment-within-parentheses/expected.js
================================================
function foo() {
	return (// hey
	abc);
}

function bar() {
	return (/* hey */
	abc);
}

================================================
FILE: test/samples/comment-within-parentheses/input.js
================================================
export default ({ b }) => b`
	function foo() {
		return (
			// hey
			abc
		)
	}
	function bar() {
		return (
			/* hey */
			abc
		)
	}
`;

================================================
FILE: test/samples/deconflict-let/expected.js
================================================
function foo() {
	let i = 2;

	for (var i$1 = 0; i$1 < 10; i$1 += 1) {
		console.log(i$1 * i);
	}
}

================================================
FILE: test/samples/deconflict-let/input.js
================================================
export default ({ b }) => b`
function foo() {
	let i = 2;

	for (var #i = 0; #i <10; #i += 1) {
		console.log(#i * i);
	}
}`;

================================================
FILE: test/samples/deconflict-method/expected.js
================================================
obj = {
	a(foo) {
		
	}
};

================================================
FILE: test/samples/deconflict-method/input.js
================================================
export default ({ b, x }) => b`
obj = {
	a(#foo) {}
};`;

================================================
FILE: test/samples/destructured-declaration/expected.js
================================================
const { answer = 42 } = life_the_universe_and_everything;

================================================
FILE: test/samples/destructured-declaration/input.js
================================================
export default ({ b }) => b`
const { answer = 42 } = life_the_universe_and_everything;
`;

================================================
FILE: test/samples/empty-body/expected.js
================================================
while (a) ;
do ; while (a);
for (; ; ) ;
if (a) ;
if (a) ; else ;
if (a) ; else if (b) ; else ;

================================================
FILE: test/samples/empty-body/input.js
================================================
export default ({ b }) => b`
	while(a);
	do;while (a);
	for(;;) ;
	if (a) ;
	if (a) ; else ;
	if (a) ; else if (b) ; else ;
`;

================================================
FILE: test/samples/export/expected.js
================================================
const foo = 42;
export { foo };

================================================
FILE: test/samples/export/input.js
================================================
export default ({ b }) => b`
const #foo = 42;
export { #foo as foo };`

================================================
FILE: test/samples/function-declaration/expected.js
================================================
function foo() {
	bar
}

function foo$1() {
	bar
}

================================================
FILE: test/samples/function-declaration/input.js
================================================
export default ({ b }) => b`
function foo() {
	bar;
}
function #foo() {
	bar;
}`

================================================
FILE: test/samples/hash-prefix/expected.js
================================================
function foo(bar$1) {
	return bar$1 * bar;
}

================================================
FILE: test/samples/hash-prefix/input.js
================================================
export default ({ x }) => x`
function foo(#bar) {
	return #bar * bar;
}`;

================================================
FILE: test/samples/hash-prefix-arrow/expected.js
================================================
const foo = bar => bar * 2;

================================================
FILE: test/samples/hash-prefix-arrow/input.js
================================================
export default ({ b }) => b`const foo = #bar => #bar * 2`;

================================================
FILE: test/samples/hash-prefix-for-loop-head/expected.js
================================================
for (let i$1 = 0; i$1 < 10; i$1 += 1) {
	console.log(i * i$1);
}

================================================
FILE: test/samples/hash-prefix-for-loop-head/input.js
================================================
export default ({ x, b }) => {
	const i = x`i`;

	return b`
		for (let #i = 0; #i < 10; #i += 1) {
			console.log(${i} * #i);
		}`;
};

================================================
FILE: test/samples/hash-prefix-reused/expected.js
================================================
function foo(bar$1) {
	const bar = 'x';
	bar$1 += 1;

	return bar => {
		console.log(bar);
	};
}

================================================
FILE: test/samples/hash-prefix-reused/input.js
================================================
export default ({ x }) => {
	const bar = x`#bar`;

	return x`
		function foo(#bar) {
			const bar = 'x';

			${bar} += 1;

			return (#bar) => {
				console.log(${bar});
			};
		}
	`;
};

================================================
FILE: test/samples/import/expected.js
================================================
import { foo } from 'x';
import { bar } from 'y';
import('baz').then(blah);

================================================
FILE: test/samples/import/input.js
================================================
export default ({ b }) => {
	const bar = b`import { bar } from 'y';`[0];

	return b`
	import { foo as #foo } from 'x';
	${bar};

	import('baz').then(blah)`
};

================================================
FILE: test/samples/import-as/expected.js
================================================
import { foo as bar } from 'x';

================================================
FILE: test/samples/import-as/input.js
================================================
export default ({ b }) => b`
import { foo as bar } from 'x';`

================================================
FILE: test/samples/import-default-and-named/expected.js
================================================
import a, { b, c as d } from 'x';

================================================
FILE: test/samples/import-default-and-named/input.js
================================================
export default ({ b }) => b`import a, { b, c as d } from 'x';`;

================================================
FILE: test/samples/import-many/expected.js
================================================
import {
	abc,
	bcd,
	cde,
	def,
	efg,
	fgh,
	ghi,
	hij,
	ijk,
	jkl,
	klm,
	lmn,
	mno,
	nop,
	opq
} from 'x';

================================================
FILE: test/samples/import-many/input.js
================================================
export default ({ b }) => b`
import { abc, bcd, cde, def, efg, fgh, ghi, hij, ijk, jkl, klm, lmn, mno, nop, opq } from 'x';`

================================================
FILE: test/samples/inserted-parameter/expected.js
================================================
function foo(bar) {
	return bar * 2;
}

================================================
FILE: test/samples/inserted-parameter/input.js
================================================
export default ({ x }) => {
	const param = x`bar`;

	return x`function foo(${param}) {
		return ${param} * 2;
	}`;
};

================================================
FILE: test/samples/inserted-parameters/expected.js
================================================
function foo(bar, baz) {
	return bar * baz;
}

================================================
FILE: test/samples/inserted-parameters/input.js
================================================
export default ({ x }) => {
	const bar = x`bar`;
	const baz = x`baz`;

	const params = [bar, baz];

	return x`function foo(${params}) {
		return ${bar} * ${baz};
	}`;
};

================================================
FILE: test/samples/logical-expression/expected.js
================================================
a ?? (b || c);
(a ?? b) || c;

================================================
FILE: test/samples/logical-expression/input.js
================================================
export default ({ b }) => {
  return b`
    a ?? (b || c);
    (a ?? b) || c;
  `;
}

================================================
FILE: test/samples/meta-property/expected.js
================================================
function foo() {
	console.log(new.target);
}

================================================
FILE: test/samples/meta-property/input.js
================================================
export default ({ b }) => b`
function foo() {
	console.log(new.target);
}
`;

================================================
FILE: test/samples/method/expected.js
================================================
obj = {
	foo() {
		console.log('foo');
	},
	async bar() {
		console.log('bar');
	},
	*baz() {
		console.log('baz');
	}
};

================================================
FILE: test/samples/method/input.js
================================================
export default ({ b }) => b`obj = {
	foo() {
		console.log('foo');
	},
	async bar() {
		console.log('bar');
	},
	baz: function* () {
		console.log('baz');
	}
}`;

================================================
FILE: test/samples/nested-blocks/expected.js
================================================
console.log(one);
console.log(two);
console.log(three);
console.log(four);

================================================
FILE: test/samples/nested-blocks/input.js
================================================
export default ({ b }) => {
	const one = b`console.log(one);`;

	const two = b`
		${one}
		console.log(two);
	`;

	const three = b`
		${two}
		console.log(three);
	`;

	return b`
		${three}
		console.log(four);
	`;
};

================================================
FILE: test/samples/nested-blocks-b/expected.js
================================================
let foo = bar;
return { hello: 'world' };

================================================
FILE: test/samples/nested-blocks-b/input.js
================================================
export default ({ b, x }) => {
	const vars = [{ id: x`foo`, init: x`bar` }];

	const return_value = x`{
		hello: 'world'
	}`;

	const returned = b`return ${return_value};`;

	return b`
		${vars.map(({ id, init }) => {
			return init
				? b`let ${id} = ${init}`
				: b`let ${id}`;
		})}

		${returned}
	`;
};

================================================
FILE: test/samples/object-expressions/expected.js
================================================
obj = { foo, bar, baz: qux };
obj = { "1": "1" };
obj = { true: true };
obj = { foo };
obj = { [foo]: foo };
obj = { [foo]: "foo" };
let blah;
obj = { blah };
obj = { blah };
obj = { a: b };

obj = {
	method() {
		console.log('hello');
	}
};

empty = {};
opts = opts || {};

obj = {
	get foo() {
		return _foo;
	},
	set foo(value) {
		_foo = value;
	},
	get [foo]() {
		return _foo;
	},
	set [foo](value) {
		_foo = value;
	}
};

================================================
FILE: test/samples/object-expressions/input.js
================================================
export default ({ b, p }) => {
	const x = p`a:b`;

	return b`
		obj = {
			foo: foo,
			bar,
			baz: qux
		};

		obj = { "1": "1" };

		obj = { true: true };

		obj = { "foo": foo };

		obj = { [foo]: foo };

		obj = { [foo]: "foo" };

		let #blah;
		obj = { blah: #blah };
		obj = { 'blah': #blah };

		obj = { ${x} }

		obj = {
			method: function() {
				console.log('hello');
			}
		}

		empty = {  }

		opts = opts || {}

		obj = {
			get foo() {
				return _foo;
			},

			set foo(value) {
				_foo = value;
			},

			get [foo]() {
				return _foo;
			},

			set [foo](value) {
				_foo = value;
			}
		}`; // TODO would be nice if the {} didn't become ({})
};


================================================
FILE: test/samples/parenthesized-expression/expected.js
================================================
a + b

================================================
FILE: test/samples/parenthesized-expression/input.js
================================================
export default ({ x }) => ({
	type: 'ParenthesizedExpression',
	expression: x`a + b`
});

================================================
FILE: test/samples/regex/expected.js
================================================
/(?:^\xb1\X\u765F)?/

================================================
FILE: test/samples/regex/input.js
================================================
export default ({ x }) => x`/(?:^\\xb1\\X\\u765F)?/`;

================================================
FILE: test/samples/removes-parens/expected.js
================================================
a = b + c

================================================
FILE: test/samples/removes-parens/input.js
================================================
export default ({ x }) => x`
a  =  (b + c)
`;

================================================
FILE: test/samples/sourcemap/expected.js
================================================
const a = 42;

function foo(value) {
	console.log(value);
}

foo(a);

================================================
FILE: test/samples/sourcemap/input.js
================================================
import * as acorn from 'acorn';

export default ({ b, x }) => {
	const decl = acorn.parse(`function foo(value) {
		console.log(value);
	}`, {
		locations: true,
		ecmaVersion: 2020
	}).body[0];


	return b`
		const a = 42;

		${decl}

		foo(a);
	`;
}

================================================
FILE: test/samples/string-literal/expected.js
================================================
let a = 'foo';
let b = "foo";
let c = `foo`;
let d = '\n\t\ta\n\t\tb\n\t\tc\n\t';
let e = "\n\t\ta\n\t\tb\n\t\tc\n\t";

let f = `
		a
		b
		c
	`;

================================================
FILE: test/samples/string-literal/input.js
================================================
export default ({ b }) => {
	const value = 'foo';
	const multiline = `
		a
		b
		c
	`;
	
	return b`
		let a = '${value}';
		let b = "${value}";
		let c = \`${value}\`;
		let d = '${multiline}';
		let e = "${multiline}";
		let f = \`${multiline}\`;
	`;
};


================================================
FILE: test/samples/switch/expected.js
================================================
switch (foo) {
	case 1:
		blah();
		break;
	case 2:
		blah();
	default:
		blah();
}

================================================
FILE: test/samples/switch/input.js
================================================
export default ({ b }) => b`
switch (foo) {
	case 1:
		blah();
		break;

	case 2:
		blah();

	default:
		blah();
}
`;

================================================
FILE: test/samples/tagged-template/expected.js
================================================
foo`bar`;

================================================
FILE: test/samples/tagged-template/input.js
================================================
export default ({ b }) => b`
foo\`bar\`;
`;

================================================
FILE: test/samples/try-catch/expected.js
================================================
try {
	foo();
} catch {
	bar();
}

try {
	foo();
} catch(e) {
	bar(e);
} finally {
	baz();
}

================================================
FILE: test/samples/try-catch/input.js
================================================
export default ({ b }) => b`
try {
	foo();
} catch {
	bar();
}

try {
	foo();
} catch(e) {
	bar(e);
} finally {
	baz();
}
`;

================================================
FILE: test/samples/var-declaration/expected.js
================================================
const 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
};

const 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;

================================================
FILE: test/samples/var-declaration/input.js
================================================
export default ({ b }) => b`
const 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 };

const 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;
`;

================================================
FILE: test/samples/with/expected.js
================================================
with (foo) bar();

================================================
FILE: test/samples/with/input.js
================================================
export default () => ({
	type: 'WithStatement',
	start: 0,
	end: 17,
	object: {
		type: 'Identifier',
		start: 6,
		end: 9,
		name: 'foo'
	},
	body: {
		type: 'ExpressionStatement',
		start: 11,
		end: 17,
		expression: {
			type: 'CallExpression',
			start: 11,
			end: 16,
			callee: {
				type: 'Identifier',
				start: 11,
				end: 14,
				name: 'bar'
			},
			arguments: []
		}
	}
})

================================================
FILE: test/samples/yield/expected.js
================================================
function* foo() {
	yield;
}

function* bar() {
	yield* 1;
}

================================================
FILE: test/samples/yield/input.js
================================================
export default ({ b }) => b`
function* foo() {
	yield;
}

function* bar() {
	yield* 1;
}
`;

================================================
FILE: test/test.js
================================================
// @ts-check
import { decode } from '@jridgewell/sourcemap-codec';
import * as fs from 'fs';
import * as acorn from 'acorn';
import * as uvu from 'uvu';
import * as assert from 'assert';
import { generateRandomJS } from 'eslump';
import * as codered from '../src/index.js';
import { walk } from 'estree-walker';
import { fileURLToPath } from 'url';

/** @typedef {import('estree').Identifier} Identifier */
/** @typedef {import('estree').Node} Node */
/** @typedef {import('estree').ObjectExpression} ObjectExpression */

/** @param {string} str */
const d = (str) => str.replace(/^\t{5}/gm, '').trim();

// just to make the tests less messy
const remove_ranges = (ast) => {
	walk(ast, {
		enter(node) {
			delete node.start;
			delete node.end;
		}
	});
	return ast;
};

const b = (s, ...v) => remove_ranges(codered.b(s, ...v));
const x = (s, ...v) => remove_ranges(codered.x(s, ...v));
const p = (s, ...v) => remove_ranges(codered.p(s, ...v));
const print = codered.print;
const parse = (s) =>
	codered.parse(s, {
		ecmaVersion: 2022,
		sourceType: 'module',
		allowAwaitOutsideFunction: true,
		allowImportExportEverywhere: true,
		allowReturnOutsideFunction: true
	});

/**
 * @param {string} name
 * @param {(test: uvu.Test) => void} fn
 */
function suite(name, fn) {
	const suite = uvu.suite(name);
	fn(suite);
	suite.run();
}

suite('b', (test) => {
	test('creates a block of nodes', () => {
		assert.deepEqual(
			b`
			a = b + c;
			d = e + f;
		`,
			[
				{
					type: 'ExpressionStatement',
					expression: {
						type: 'AssignmentExpression',
						left: { type: 'Identifier', name: 'a' },
						operator: '=',
						right: {
							type: 'BinaryExpression',
							left: { type: 'Identifier', name: 'b' },
							operator: '+',
							right: { type: 'Identifier', name: 'c' }
						}
					}
				},
				{
					type: 'ExpressionStatement',
					expression: {
						type: 'AssignmentExpression',
						left: { type: 'Identifier', name: 'd' },
						operator: '=',
						right: {
							type: 'BinaryExpression',
							left: { type: 'Identifier', name: 'e' },
							operator: '+',
							right: { type: 'Identifier', name: 'f' }
						}
					}
				}
			]
		);
	});

	test('ignores falsy values', () => {
		assert.deepEqual(
			b`
			a++;
			${false}
			b++
		`,
			[
				{
					type: 'ExpressionStatement',
					expression: {
						type: 'UpdateExpression',
						operator: '++',
						prefix: false,
						argument: { type: 'Identifier', name: 'a' }
					}
				},
				{
					type: 'ExpressionStatement',
					expression: {
						type: 'UpdateExpression',
						operator: '++',
						prefix: false,
						argument: { type: 'Identifier', name: 'b' }
					}
				}
			]
		);
	});

	test('unwraps arrays', () => {
		const vars = [x`a`, x`b`, x`c`];
		const declarations = vars.map((v) => b`console.log(${v})`);

		const fn = x`function foo() {
			${declarations}
		}`;

		const call = (name) => ({
			type: 'ExpressionStatement',
			expression: {
				type: 'CallExpression',
				callee: {
					type: 'MemberExpression',
					object: { type: 'Identifier', name: 'console' },
					property: { type: 'Identifier', name: 'log' },
					optional: false,
					computed: false
				},
				arguments: [{ type: 'Identifier', name }],
				optional: false
			}
		});

		assert.deepEqual(fn.body.body, [
			{ leadingComments: undefined, ...call('a') },
			call('b'),
			call('c')
		]);
	});
});

suite('x', (test) => {
	test('creates a single expression', () => {
		assert.deepEqual(
			x`
			a = b + c
		`,
			{
				type: 'AssignmentExpression',
				left: { type: 'Identifier', name: 'a' },
				operator: '=',
				right: {
					type: 'BinaryExpression',
					left: { type: 'Identifier', name: 'b' },
					operator: '+',
					right: { type: 'Identifier', name: 'c' }
				}
			}
		);
	});

	test('inserts values', () => {
		const name = { x: 'name' };
		const param = { x: 'param' };

		const node = x`
			function ${name}(${param}) {
				return ${param} * 2;
			}
		`;

		assert.deepEqual(node, {
			type: 'FunctionExpression',
			id: name,
			expression: false,
			generator: false,
			async: false,
			params: [param],
			body: {
				type: 'BlockStatement',
				body: [
					{
						type: 'ReturnStatement',
						argument: {
							type: 'BinaryExpression',
							left: param,
							operator: '*',
							right: { type: 'Literal', value: 2, raw: '2' }
						}
					}
				]
			}
		});
	});

	test('preserves @-prefixed names', () => {
		const node = x`@foo(bar)`;

		assert.deepEqual(node, {
			type: 'CallExpression',
			callee: { type: 'Identifier', name: '@foo' },
			arguments: [{ type: 'Identifier', name: 'bar' }],
			optional: false
		});

		const id = x`@foo`;

		assert.deepEqual(id, {
			type: 'Identifier',
			name: '@foo'
		});
	});

	test('preserves #-prefixed names', () => {
		const node = x`
			function foo(#bar) {
				return #bar * bar;
			}
		`;

		assert.deepEqual(node, {
			type: 'FunctionExpression',
			id: { type: 'Identifier', name: 'foo' },
			expression: false,
			generator: false,
			async: false,
			params: [{ type: 'Identifier', name: '#bar' }],
			body: {
				type: 'BlockStatement',
				body: [
					{
						type: 'ReturnStatement',
						argument: {
							type: 'BinaryExpression',
							left: { type: 'Identifier', name: '#bar' },
							operator: '*',
							right: { type: 'Identifier', name: 'bar' }
						}
					}
				]
			}
		});
	});

	test('flattens parameters', () => {
		const args = [x`a`, x`b`];

		const fn = x`function (${args}) {
			return a + b;
		}`;

		assert.deepEqual(fn, {
			type: 'FunctionExpression',
			id: null,
			expression: false,
			generator: false,
			async: false,
			params: [
				{ type: 'Identifier', name: 'a' },
				{ type: 'Identifier', name: 'b' }
			],
			body: {
				type: 'BlockStatement',
				body: [
					{
						type: 'ReturnStatement',
						argument: {
							type: 'BinaryExpression',
							left: { type: 'Identifier', name: 'a' },
							operator: '+',
							right: { type: 'Identifier', name: 'b' }
						}
					}
				]
			}
		});
	});

	test(`replaces strings`, () => {
		const name = 'world';
		const expression = x`hello("${name}")`;

		assert.deepEqual(expression.arguments[0].value, 'world');
	});

	test(`replaces numbers`, () => {
		const answer = 42;
		const expression = x`console.log("the answer is", ${answer})`;

		assert.deepEqual(expression.arguments[1], {
			type: 'Literal',
			value: 42,
			leadingComments: undefined,
			trailingComments: undefined
		});
	});

	test(`replaces identifier with no parent`, () => {
		const answer = { type: 'Identifier', name: 'value' };
		const expression = x`${answer}`;

		assert.deepEqual(expression, {
			type: 'Identifier',
			name: 'value'
		});
	});

	test(`replaces strings in template literals`, () => {
		const foo = 'bar';
		const expression = x`\`${foo}\``;

		assert.deepEqual(expression.quasis[0].value.raw, 'bar');
	});

	test(`allows strings in place of identifiers`, () => {
		const name = 'world';
		const expression = x`hello(${name})`;

		assert.deepEqual(expression.arguments[0], {
			type: 'Identifier',
			name: 'world',
			leadingComments: undefined,
			trailingComments: undefined
		});
	});

	test('flattens arrays', () => {
		const vars = [x`a`, x`b`, x`c`];
		const arr = x`[${vars}]`;

		assert.deepEqual(arr, {
			type: 'ArrayExpression',
			elements: ['a', 'b', 'c'].map((name) => ({
				type: 'Identifier',
				name
			}))
		});
	});

	test('flattens objects', () => {
		const props = [p`a`, p`b`, p`c`];
		const obj = x`{${props}}`;

		assert.deepEqual(obj, {
			type: 'ObjectExpression',
			properties: ['a', 'b', 'c'].map((name) => {
				const id = { type: 'Identifier', name };
				return {
					type: 'Property',
					kind: 'init',
					method: false,
					shorthand: true,
					computed: false,
					key: id,
					value: id
				};
			})
		});
	});

	test('flattens patterns', () => {
		const props = [p`a`, p`b`, p`c`];
		const declaration = b`const { ${props} } = obj;`[0];

		assert.deepEqual(declaration, {
			type: 'VariableDeclaration',
			kind: 'const',
			declarations: [
				{
					type: 'VariableDeclarator',
					id: {
						type: 'ObjectPattern',
						properties: ['a', 'b', 'c'].map((name) => {
							const id = { type: 'Identifier', name };
							return {
								type: 'Property',
								kind: 'init',
								method: false,
								computed: false,
								shorthand: true,
								key: id,
								value: id
							};
						})
					},
					init: {
						type: 'Identifier',
						name: 'obj'
					}
				}
			]
		});
	});

	test('removes falsy properties from an object', () => {
		const obj = x`{
			a: 1,
			b: ${false}
		}`;

		assert.deepEqual(obj.properties.length, 1);
		assert.deepEqual(obj.properties[0].key.name, 'a');
	});

	test('preserves locations of original nodes in a sourcemap', () => {
		const answer = {
			type: 'Literal',
			value: 42,
			raw: '42',
			loc: {
				start: { line: 10, column: 5 },
				end: { line: 10, column: 7 }
			}
		};

		const expression = x`console.log(${answer})`;

		const { code, map } = print(expression, {
			sourceMapSource: 'input.js'
		});

		assert.deepEqual(code, `console.log(42)`);

		assert.deepEqual(map, {
			version: 3,
			sources: ['input.js'],
			sourcesContent: [null],
			names: [],
			mappings: 'YASK,EAAE'
		});
	});

	test('errors on invalid expressions', () => {
		assert.throws(() => {
			x`this is broken`;
		}, /Unexpected token 'is'/);
	});
});

suite('p', (test) => {
	test('creates a regular object property', () => {
		const obj = x`{}`;
		obj.properties.push(p`foo: 'bar'`);

		assert.deepEqual(obj, {
			type: 'ObjectExpression',
			properties: [
				{
					type: 'Property',
					kind: 'init',
					method: false,
					shorthand: false,
					computed: false,
					key: { type: 'Identifier', name: 'foo' },
					value: { type: 'Literal', value: 'bar', raw: "'bar'" }
				}
			]
		});
	});
});

suite('print', (test) => {
	const read = (file) =>
		fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : null;

	fs.readdirSync('test/samples').forEach((dir) => {
		test(dir, async () => {
			if (dir[0] === '.') return;

			const url = new URL(`./samples/${dir}/input.js`, import.meta.url);
			const mod = await import(fileURLToPath(url.href));
			const input = mod.default({ b, x, p, parse });

			const expected = {
				code: read(`test/samples/${dir}/expected.js`),
				map: JSON.parse(read(`test/samples/${dir}/expected.js.map`) || '{}')
			};

			const actual = print(input, {
				sourceMapSource: 'input.js',
				getName: (name) => name.toUpperCase()
			});

			fs.writeFileSync(`test/samples/${dir}/_actual.js`, actual.code);
			fs.writeFileSync(
				`test/samples/${dir}/_actual.js.map`,
				actual.map.toString()
			);

			assert.deepEqual(
				actual.code.replace(/\t+$/gm, ''),
				expected.code.replace(/\t+$/gm, '')
			);
			assert.deepEqual(actual.map, expected.map);
		});
	});

	test('throws on unhandled sigils', () => {
		assert.throws(() => print(b`let foo = @bar;`), {
			message: 'Unhandled sigil @bar'
		});
	});

	test('can return sourcemap with decoded mappings', async () => {
		const url = new URL(`./samples/sourcemap/input.js`, import.meta.url);
		const mod = await import(fileURLToPath(url.href));
		const input = mod.default({ b, x, p });

		const expected = {
			code: read(`test/samples/sourcemap/expected.js`),
			map: JSON.parse(read(`test/samples/sourcemap/expected.js.map`) || '{}')
		};
		if (expected.map && expected.map.mappings) {
			expected.map.mappings = decode(expected.map.mappings);
		}

		const actual = print(input, {
			sourceMapSource: 'input.js',
			getName: (name) => name.toUpperCase(),
			sourceMapEncodeMappings: false
		});

		assert.deepEqual(actual.map, expected.map);
	});

	test.skip('passes fuzz testing', () => {
		for (let i = 0; i < 100; i += 1) {
			const js = generateRandomJS({
				sourceType: 'module',
				maxDepth: 7,
				comments: false
			});

			let ast1;
			try {
				ast1 = acorn.parse(js, {
					sourceType: 'module',
					ecmaVersion: 2020
				});
			} catch {
				continue;
			}

			let printed;

			try {
				printed = print(ast1);
			} catch (err) {
				fs.writeFileSync(`test/fuzz/report.js`, js);
				throw err;
			}

			const ast2 = acorn.parse(printed.code, {
				sourceType: 'module',
				ecmaVersion: 2019
			});

			[ast1, ast2].forEach((ast) => {
				walk(ast, {
					enter(node) {
						delete node.start;
						delete node.end;
					}
				});
			});

			try {
				assert.deepEqual(ast1, ast2);
			} catch (err) {
				fs.writeFileSync(
					`test/fuzz/report.js`,
					`// input\n${js}\n\n// output\n${printed.code}`
				);
				throw err;
			}
		}
	});
});


================================================
FILE: tsconfig.json
================================================
{
    "compilerOptions": {
        "allowJs": true,
        "checkJs": true,
        "declaration": true,
        "emitDeclarationOnly": true,
        "declarationDir": "types",
        "noImplicitAny": true,
        "diagnostics": true,
        "noImplicitThis": true,
        "noEmitOnError": true,
        "lib": ["es5", "es6", "dom"]
    },
    "target": "ES5",
    "module": "ES6",
    "include": [
        "src"
    ],
    "exclude": [
        "node_modules"
    ]
}
Download .txt
gitextract_nlxlmv96/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .prettierrc.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── src/
│   ├── index.js
│   ├── print/
│   │   ├── handlers.js
│   │   └── index.js
│   └── utils/
│       ├── comments.js
│       ├── id.js
│       └── push_array.js
├── test/
│   ├── samples/
│   │   ├── array-expressions/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── arrow-function-as-statement/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── arrow-function-assignment-object-pattern/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── arrow-function-parenthesized/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── at-prefix/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── await-precedence/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── basic/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── bigint/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── break-continue/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── call-expressions/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── chain-expressions/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── class-private/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── class-property/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── class-static-block/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-block/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-block-with-sigil/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-inline/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-inline-inserted/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-interpolated/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-mixed-trailing/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-within-call-expression/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── comment-within-parentheses/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── deconflict-let/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── deconflict-method/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── destructured-declaration/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── empty-body/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── export/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── function-declaration/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── hash-prefix/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── hash-prefix-arrow/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── hash-prefix-for-loop-head/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── hash-prefix-reused/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── import/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── import-as/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── import-default-and-named/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── import-many/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── inserted-parameter/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── inserted-parameters/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── logical-expression/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── meta-property/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── method/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── nested-blocks/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── nested-blocks-b/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── object-expressions/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── parenthesized-expression/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── regex/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── removes-parens/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── sourcemap/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── string-literal/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── switch/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── tagged-template/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── try-catch/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── var-declaration/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   ├── with/
│   │   │   ├── expected.js
│   │   │   └── input.js
│   │   └── yield/
│   │       ├── expected.js
│   │       └── input.js
│   └── test.js
└── tsconfig.json
Download .txt
SYMBOL INDEX (103 symbols across 23 files)

FILE: src/index.js
  constant EMPTY (line 119) | const EMPTY = { type: 'Empty' };
  method leave (line 158) | leave(node) {
  function b (line 267) | function b(strings, ...values) {
  function x (line 290) | function x(strings, ...values) {
  function p (line 322) | function p(strings, ...values) {
  function handle_error (line 345) | function handle_error(str, err) {

FILE: src/print/handlers.js
  function handle (line 62) | function handle(node, state) {
  function c (line 105) | function c(content, node) {
  constant OPERATOR_PRECEDENCE (line 113) | const OPERATOR_PRECEDENCE = {
  constant EXPRESSIONS_PRECEDENCE (line 142) | const EXPRESSIONS_PRECEDENCE = {
  function needs_parens (line 176) | function needs_parens(node, parent, is_right) {
  function has_call_expression (line 230) | function has_call_expression(node) {
  method Program (line 399) | Program(node, state) {
  method EmptyStatement (line 411) | EmptyStatement(node, state) {
  method ParenthesizedExpression (line 415) | ParenthesizedExpression(node, state) {
  method ExpressionStatement (line 419) | ExpressionStatement(node, state) {
  method IfStatement (line 431) | IfStatement(node, state) {
  method LabeledStatement (line 447) | LabeledStatement(node, state) {
  method BreakStatement (line 451) | BreakStatement(node, state) {
  method ContinueStatement (line 457) | ContinueStatement(node, state) {
  method WithStatement (line 463) | WithStatement(node, state) {
  method SwitchStatement (line 472) | SwitchStatement(/** @type {SwitchStatement} */ node, state) {
  method ReturnStatement (line 505) | ReturnStatement(node, state) {
  method ThrowStatement (line 524) | ThrowStatement(node, state) {
  method TryStatement (line 528) | TryStatement(node, state) {
  method WhileStatement (line 551) | WhileStatement(node, state) {
  method DoWhileStatement (line 560) | DoWhileStatement(node, state) {
  method DebuggerStatement (line 609) | DebuggerStatement(node, state) {
  method VariableDeclaration (line 652) | VariableDeclaration(node, state) {
  method VariableDeclarator (line 656) | VariableDeclarator(node, state) {
  method ClassDeclaration (line 664) | ClassDeclaration(node, state) {
  method ImportDeclaration (line 683) | ImportDeclaration(/** @type {ImportDeclaration} */ node, state) {
  method ImportExpression (line 752) | ImportExpression(node, state) {
  method ExportDefaultDeclaration (line 756) | ExportDefaultDeclaration(node, state) {
  method ExportNamedDeclaration (line 766) | ExportNamedDeclaration(node, state) {
  method ExportAllDeclaration (line 809) | ExportAllDeclaration(node, state) {
  method MethodDefinition (line 813) | MethodDefinition(node, state) {
  method ThisExpression (line 894) | ThisExpression(node, state) {
  method Super (line 898) | Super(node, state) {
  method RestElement (line 902) | RestElement(node, state) {
  method YieldExpression (line 906) | YieldExpression(node, state) {
  method AwaitExpression (line 917) | AwaitExpression(node, state) {
  method TemplateLiteral (line 931) | TemplateLiteral(node, state) {
  method TaggedTemplateExpression (line 947) | TaggedTemplateExpression(node, state) {
  method ArrayExpression (line 951) | ArrayExpression(node, state) {
  method ObjectExpression (line 998) | ObjectExpression(/** @type {ObjectExpression} */ node, state) {
  method Property (line 1059) | Property(node, state) {
  method ObjectPattern (line 1126) | ObjectPattern(node, state) {
  method SequenceExpression (line 1139) | SequenceExpression(/** @type {SequenceExpression} */ node, state) {
  method UnaryExpression (line 1145) | UnaryExpression(node, state) {
  method UpdateExpression (line 1166) | UpdateExpression(node, state) {
  method AssignmentExpression (line 1172) | AssignmentExpression(node, state) {
  method BinaryExpression (line 1180) | BinaryExpression(node, state) {
  method ConditionalExpression (line 1214) | ConditionalExpression(node, state) {
  method NewExpression (line 1256) | NewExpression(/** @type {NewExpression} */ node, state) {
  method ChainExpression (line 1290) | ChainExpression(node, state) {
  method CallExpression (line 1294) | CallExpression(/** @type {CallExpression} */ node, state) {
  method MemberExpression (line 1372) | MemberExpression(node, state) {
  method MetaProperty (line 1404) | MetaProperty(node, state) {
  method Identifier (line 1412) | Identifier(node, state) {
  method Literal (line 1443) | Literal(/** @type {Literal} */ node, state) {
  method PropertyDefinition (line 1465) | PropertyDefinition(/** @type {PropertyDefinition} */ node, state) {
  method StaticBlock (line 1489) | StaticBlock(/** @type {StaticBlock} */ node, state) {
  method PrivateIdentifier (line 1497) | PrivateIdentifier(/** @type {PrivateIdenifier} */ node, state) {

FILE: src/print/index.js
  function print (line 35) | function print(node, opts = {}) {

FILE: src/utils/comments.js
  method enter (line 51) | enter(node) {
  method leave (line 77) | leave(node) {

FILE: src/utils/push_array.js
  function push_array (line 8) | function push_array(array, items) {

FILE: test/samples/class-private/expected.js
  class A (line 1) | class A {
    method is (line 6) | static is(obj) {
    method #p3 (line 10) | get #p3() {
    method #p3 (line 14) | set #p3(v) {
    method #m1 (line 18) | #m1() {
    method #m2 (line 22) | static #m2() {
    method #m3 (line 26) | *#m3() {
    method #m4 (line 30) | async #m4() {

FILE: test/samples/class-property/expected.js
  class Foo (line 1) | class Foo {

FILE: test/samples/class-static-block/expected.js
  class Foo (line 1) | class Foo {

FILE: test/samples/comment-inline/expected.js
  function bar (line 12) | function bar() {

FILE: test/samples/comment-mixed-trailing/expected.js
  function foo (line 1) | function foo() {

FILE: test/samples/comment-within-parentheses/expected.js
  function foo (line 1) | function foo() {
  function bar (line 6) | function bar() {

FILE: test/samples/deconflict-let/expected.js
  function foo (line 1) | function foo() {

FILE: test/samples/deconflict-method/expected.js
  method a (line 2) | a(foo) {

FILE: test/samples/function-declaration/expected.js
  function foo (line 1) | function foo() {
  function foo$1 (line 5) | function foo$1() {

FILE: test/samples/hash-prefix-reused/expected.js
  function foo (line 1) | function foo(bar$1) {

FILE: test/samples/hash-prefix/expected.js
  function foo (line 1) | function foo(bar$1) {

FILE: test/samples/inserted-parameter/expected.js
  function foo (line 1) | function foo(bar) {

FILE: test/samples/inserted-parameters/expected.js
  function foo (line 1) | function foo(bar, baz) {

FILE: test/samples/meta-property/expected.js
  function foo (line 1) | function foo() {

FILE: test/samples/method/expected.js
  method foo (line 2) | foo() {
  method bar (line 5) | async bar() {
  method baz (line 8) | *baz() {

FILE: test/samples/object-expressions/expected.js
  method method (line 13) | method() {
  method foo (line 22) | get foo() {
  method foo (line 25) | set foo(value) {
  method [foo] (line 28) | get [foo]() {
  method [foo] (line 31) | set [foo](value) {

FILE: test/samples/sourcemap/expected.js
  function foo (line 3) | function foo(value) {

FILE: test/test.js
  method enter (line 22) | enter(node) {
  function suite (line 47) | function suite(name, fn) {
  method enter (line 573) | enter(node) {
Condensed preview — 125 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (104K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 758,
    "preview": "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 "
  },
  {
    "path": ".gitignore",
    "chars": 62,
    "preview": ".DS_Store\nnode_modules\n/types\ndist\ntest/**/_actual.*\ntest/fuzz"
  },
  {
    "path": ".prettierrc.json",
    "chars": 69,
    "preview": "{\n\t\"useTabs\": true,\n\t\"singleQuote\": true,\n\t\"trailingComma\": \"none\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 5516,
    "preview": "# 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"
  },
  {
    "path": "LICENSE",
    "chars": 1055,
    "preview": "Copyright (c) 2019 Rich Harris\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this sof"
  },
  {
    "path": "README.md",
    "chars": 2477,
    "preview": "# code-red\n\nExperimental toolkit for writing x-to-JavaScript compilers. It is used in [Svelte](https://svelte.dev).\n\n\n##"
  },
  {
    "path": "package.json",
    "chars": 865,
    "preview": "{\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"
  },
  {
    "path": "src/index.js",
    "chars": 9108,
    "preview": "import * as acorn from 'acorn';\nimport { walk } from 'estree-walker';\nimport { id, re } from './utils/id.js';\nimport { g"
  },
  {
    "path": "src/print/handlers.js",
    "chars": 34315,
    "preview": "// heavily based on https://github.com/davidbonnet/astring\n// released under MIT license https://github.com/davidbonnet/"
  },
  {
    "path": "src/print/index.js",
    "chars": 2956,
    "preview": "import * as perisopic from 'periscopic';\nimport { handle } from './handlers.js';\nimport { encode } from '@jridgewell/sou"
  },
  {
    "path": "src/utils/comments.js",
    "chars": 1901,
    "preview": "import { re } from './id.js';\n\n/** @typedef {import('estree').Comment} Comment */\n/** @typedef {import('estree').Node} N"
  },
  {
    "path": "src/utils/id.js",
    "chars": 204,
    "preview": "// generate an ID that is, to all intents and purposes, unique\nexport const id = Math.round(Math.random() * 1e20).toStri"
  },
  {
    "path": "src/utils/push_array.js",
    "chars": 331,
    "preview": "/**\n * Does `array.push` for all `items`. Needed because `array.push(...items)` throws\n * \"Maximum call stack size excee"
  },
  {
    "path": "test/samples/array-expressions/expected.js",
    "chars": 188,
    "preview": "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'"
  },
  {
    "path": "test/samples/array-expressions/input.js",
    "chars": 214,
    "preview": "export default ({ b }) => b`\na = [1, 2, 3];\n\nb = ['the quick brown fox jumps over the lazy dog', 'the quick brown fox ju"
  },
  {
    "path": "test/samples/arrow-function-as-statement/expected.js",
    "chars": 11,
    "preview": "() => {\n\n};"
  },
  {
    "path": "test/samples/arrow-function-as-statement/input.js",
    "chars": 39,
    "preview": "export default ({ b }) => b`() => {};`;"
  },
  {
    "path": "test/samples/arrow-function-assignment-object-pattern/expected.js",
    "chars": 34,
    "preview": "let a = () => ({ x } = { x: 42 });"
  },
  {
    "path": "test/samples/arrow-function-assignment-object-pattern/input.js",
    "chars": 65,
    "preview": "export default ({ b }) => b`let a = () => ({ x } = { x: 42 });`;\n"
  },
  {
    "path": "test/samples/arrow-function-parenthesized/expected.js",
    "chars": 20,
    "preview": "foo || (bar => bar);"
  },
  {
    "path": "test/samples/arrow-function-parenthesized/input.js",
    "chars": 56,
    "preview": "export default ({ b, x }) => b`foo || ${x`bar => bar`}`;"
  },
  {
    "path": "test/samples/at-prefix/expected.js",
    "chars": 8,
    "preview": "FOO(bar)"
  },
  {
    "path": "test/samples/at-prefix/input.js",
    "chars": 39,
    "preview": "export default ({ x }) => x`@foo(bar)`;"
  },
  {
    "path": "test/samples/await-precedence/expected.js",
    "chars": 24,
    "preview": "await (a || b);\nawait c;"
  },
  {
    "path": "test/samples/await-precedence/input.js",
    "chars": 56,
    "preview": "export default ({ b }) => b`\nawait (a || b);\nawait c;\n`;"
  },
  {
    "path": "test/samples/basic/expected.js",
    "chars": 30,
    "preview": "foo({ a: 1 });\n\"#foo\";\n\"@foo\";"
  },
  {
    "path": "test/samples/basic/input.js",
    "chars": 62,
    "preview": "export default ({ b }) => b`\nfoo({ a: 1 });\n\"#foo\";\n\"@foo\";\n`;"
  },
  {
    "path": "test/samples/bigint/expected.js",
    "chars": 6,
    "preview": "12345n"
  },
  {
    "path": "test/samples/bigint/input.js",
    "chars": 36,
    "preview": "export default ({ x }) => x`12345n`;"
  },
  {
    "path": "test/samples/break-continue/expected.js",
    "chars": 211,
    "preview": "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\ti"
  },
  {
    "path": "test/samples/break-continue/input.js",
    "chars": 243,
    "preview": "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_wi"
  },
  {
    "path": "test/samples/call-expressions/expected.js",
    "chars": 127,
    "preview": "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')"
  },
  {
    "path": "test/samples/call-expressions/input.js",
    "chars": 150,
    "preview": "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}, () "
  },
  {
    "path": "test/samples/chain-expressions/expected.js",
    "chars": 36,
    "preview": "foo?.bar.baz;\nx?.(a, b, c);\nx()?.();"
  },
  {
    "path": "test/samples/chain-expressions/input.js",
    "chars": 68,
    "preview": "export default ({ b }) => b`\nfoo?.bar.baz;\nx?.(a, b, c);\nx()?.()\n`;\n"
  },
  {
    "path": "test/samples/class-private/expected.js",
    "chars": 220,
    "preview": "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"
  },
  {
    "path": "test/samples/class-private/input.js",
    "chars": 225,
    "preview": "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\tg"
  },
  {
    "path": "test/samples/class-property/expected.js",
    "chars": 47,
    "preview": "class Foo {\n\thi;\n\tstatic foo = 1;\n\t[KEY] = 2;\n}"
  },
  {
    "path": "test/samples/class-property/input.js",
    "chars": 78,
    "preview": "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",
    "chars": 42,
    "preview": "class Foo {\n\tstatic {\n\t\tthis.abc = 1;\n\t}\n}"
  },
  {
    "path": "test/samples/class-static-block/input.js",
    "chars": 86,
    "preview": "export default ({ b }) => b`\nclass Foo {\n    static {\n        this.abc = 1;\n    }\n}\n`;"
  },
  {
    "path": "test/samples/comment-block/expected.js",
    "chars": 179,
    "preview": "/* comment before a node\n * second line */\nconsole.log(1);\n\nconsole.log(2); /* comment on same line as node */\n\nconst ob"
  },
  {
    "path": "test/samples/comment-block/input.js",
    "chars": 219,
    "preview": "export default ({ b }) => b`\n\t/* comment before a node\n\t * second line */\n\tconsole.log(1);\n\n\tconsole.log(2); /* comment "
  },
  {
    "path": "test/samples/comment-block-with-sigil/expected.js",
    "chars": 15,
    "preview": "a = /* #b */ c;"
  },
  {
    "path": "test/samples/comment-block-with-sigil/input.js",
    "chars": 47,
    "preview": "export default ({ b }) => b`\n\ta = /* #b */ c\n`;"
  },
  {
    "path": "test/samples/comment-inline/expected.js",
    "chars": 214,
    "preview": "// comment before a node\n// second line\nconsole.log(1);\n\nconsole.log(2); // comment on same line as node\n\nconst obj = {\n"
  },
  {
    "path": "test/samples/comment-inline/input.js",
    "chars": 258,
    "preview": "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 "
  },
  {
    "path": "test/samples/comment-inline-inserted/expected.js",
    "chars": 117,
    "preview": "// 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",
    "chars": 427,
    "preview": "export default ({ b, x }) => {\n\tconst insert = b`\"use strict\";`;\n\n\tconst node = {\n\t\ttype: 'ImportDeclaration',\n\t\tspecifi"
  },
  {
    "path": "test/samples/comment-interpolated/expected.js",
    "chars": 23,
    "preview": "a = /* the answer */ 42"
  },
  {
    "path": "test/samples/comment-interpolated/input.js",
    "chars": 60,
    "preview": "export default ({ x }) => x`a = /* ${\"the answer\"} */ ${42}`"
  },
  {
    "path": "test/samples/comment-mixed-trailing/expected.js",
    "chars": 38,
    "preview": "function foo() {\n\n} // hey1\n/*\nhey2\n*/"
  },
  {
    "path": "test/samples/comment-mixed-trailing/input.js",
    "chars": 79,
    "preview": "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",
    "chars": 242,
    "preview": "console.log(null, /* xxx */ new Date(), /** zzz */ a.b.c()); // www\n\nconsole.log(null, // foo\nnew Date());\n\nconsole.log("
  },
  {
    "path": "test/samples/comment-within-call-expression/input.js",
    "chars": 283,
    "preview": "export default ({ b }) => b`\n\tconsole.log(null, /* xxx */ new Date(), /** zzz */ a.b.c()); // www\n\tconsole.log(null,\n\t\t/"
  },
  {
    "path": "test/samples/comment-within-parentheses/expected.js",
    "chars": 87,
    "preview": "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",
    "chars": 140,
    "preview": "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"
  },
  {
    "path": "test/samples/deconflict-let/expected.js",
    "chars": 99,
    "preview": "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",
    "chars": 125,
    "preview": "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"
  },
  {
    "path": "test/samples/deconflict-method/expected.js",
    "chars": 26,
    "preview": "obj = {\n\ta(foo) {\n\t\t\n\t}\n};"
  },
  {
    "path": "test/samples/deconflict-method/input.js",
    "chars": 56,
    "preview": "export default ({ b, x }) => b`\nobj = {\n\ta(#foo) {}\n};`;"
  },
  {
    "path": "test/samples/destructured-declaration/expected.js",
    "chars": 57,
    "preview": "const { answer = 42 } = life_the_universe_and_everything;"
  },
  {
    "path": "test/samples/destructured-declaration/input.js",
    "chars": 89,
    "preview": "export default ({ b }) => b`\nconst { answer = 42 } = life_the_universe_and_everything;\n`;"
  },
  {
    "path": "test/samples/empty-body/expected.js",
    "chars": 95,
    "preview": "while (a) ;\ndo ; while (a);\nfor (; ; ) ;\nif (a) ;\nif (a) ; else ;\nif (a) ; else if (b) ; else ;"
  },
  {
    "path": "test/samples/empty-body/input.js",
    "chars": 126,
    "preview": "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) ; els"
  },
  {
    "path": "test/samples/export/expected.js",
    "chars": 31,
    "preview": "const foo = 42;\nexport { foo };"
  },
  {
    "path": "test/samples/export/input.js",
    "chars": 70,
    "preview": "export default ({ b }) => b`\nconst #foo = 42;\nexport { #foo as foo };`"
  },
  {
    "path": "test/samples/function-declaration/expected.js",
    "chars": 50,
    "preview": "function foo() {\n\tbar\n}\n\nfunction foo$1() {\n\tbar\n}"
  },
  {
    "path": "test/samples/function-declaration/input.js",
    "chars": 80,
    "preview": "export default ({ b }) => b`\nfunction foo() {\n\tbar;\n}\nfunction #foo() {\n\tbar;\n}`"
  },
  {
    "path": "test/samples/hash-prefix/expected.js",
    "chars": 44,
    "preview": "function foo(bar$1) {\n\treturn bar$1 * bar;\n}"
  },
  {
    "path": "test/samples/hash-prefix/input.js",
    "chars": 73,
    "preview": "export default ({ x }) => x`\nfunction foo(#bar) {\n\treturn #bar * bar;\n}`;"
  },
  {
    "path": "test/samples/hash-prefix-arrow/expected.js",
    "chars": 27,
    "preview": "const foo = bar => bar * 2;"
  },
  {
    "path": "test/samples/hash-prefix-arrow/input.js",
    "chars": 58,
    "preview": "export default ({ b }) => b`const foo = #bar => #bar * 2`;"
  },
  {
    "path": "test/samples/hash-prefix-for-loop-head/expected.js",
    "chars": 64,
    "preview": "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",
    "chars": 134,
    "preview": "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} *"
  },
  {
    "path": "test/samples/hash-prefix-reused/expected.js",
    "chars": 96,
    "preview": "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",
    "chars": 186,
    "preview": "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"
  },
  {
    "path": "test/samples/import/expected.js",
    "chars": 75,
    "preview": "import { foo } from 'x';\nimport { bar } from 'y';\nimport('baz').then(blah);"
  },
  {
    "path": "test/samples/import/input.js",
    "chars": 158,
    "preview": "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"
  },
  {
    "path": "test/samples/import-as/expected.js",
    "chars": 31,
    "preview": "import { foo as bar } from 'x';"
  },
  {
    "path": "test/samples/import-as/input.js",
    "chars": 61,
    "preview": "export default ({ b }) => b`\nimport { foo as bar } from 'x';`"
  },
  {
    "path": "test/samples/import-default-and-named/expected.js",
    "chars": 33,
    "preview": "import a, { b, c as d } from 'x';"
  },
  {
    "path": "test/samples/import-default-and-named/input.js",
    "chars": 63,
    "preview": "export default ({ b }) => b`import a, { b, c as d } from 'x';`;"
  },
  {
    "path": "test/samples/import-many/expected.js",
    "chars": 109,
    "preview": "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",
    "chars": 124,
    "preview": "export default ({ b }) => b`\nimport { abc, bcd, cde, def, efg, fgh, ghi, hij, ijk, jkl, klm, lmn, mno, nop, opq } from '"
  },
  {
    "path": "test/samples/inserted-parameter/expected.js",
    "chars": 38,
    "preview": "function foo(bar) {\n\treturn bar * 2;\n}"
  },
  {
    "path": "test/samples/inserted-parameter/input.js",
    "chars": 117,
    "preview": "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",
    "chars": 45,
    "preview": "function foo(bar, baz) {\n\treturn bar * baz;\n}"
  },
  {
    "path": "test/samples/inserted-parameters/input.js",
    "chars": 169,
    "preview": "export default ({ x }) => {\n\tconst bar = x`bar`;\n\tconst baz = x`baz`;\n\n\tconst params = [bar, baz];\n\n\treturn x`function f"
  },
  {
    "path": "test/samples/logical-expression/expected.js",
    "chars": 29,
    "preview": "a ?? (b || c);\n(a ?? b) || c;"
  },
  {
    "path": "test/samples/logical-expression/input.js",
    "chars": 84,
    "preview": "export default ({ b }) => {\n  return b`\n    a ?? (b || c);\n    (a ?? b) || c;\n  `;\n}"
  },
  {
    "path": "test/samples/meta-property/expected.js",
    "chars": 44,
    "preview": "function foo() {\n\tconsole.log(new.target);\n}"
  },
  {
    "path": "test/samples/meta-property/input.js",
    "chars": 76,
    "preview": "export default ({ b }) => b`\nfunction foo() {\n\tconsole.log(new.target);\n}\n`;"
  },
  {
    "path": "test/samples/method/expected.js",
    "chars": 121,
    "preview": "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",
    "chars": 161,
    "preview": "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: fu"
  },
  {
    "path": "test/samples/nested-blocks/expected.js",
    "chars": 74,
    "preview": "console.log(one);\nconsole.log(two);\nconsole.log(three);\nconsole.log(four);"
  },
  {
    "path": "test/samples/nested-blocks/input.js",
    "chars": 217,
    "preview": "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"
  },
  {
    "path": "test/samples/nested-blocks-b/expected.js",
    "chars": 41,
    "preview": "let foo = bar;\nreturn { hello: 'world' };"
  },
  {
    "path": "test/samples/nested-blocks-b/input.js",
    "chars": 309,
    "preview": "export default ({ b, x }) => {\n\tconst vars = [{ id: x`foo`, init: x`bar` }];\n\n\tconst return_value = x`{\n\t\thello: 'world'"
  },
  {
    "path": "test/samples/object-expressions/expected.js",
    "chars": 428,
    "preview": "obj = { foo, bar, baz: qux };\nobj = { \"1\": \"1\" };\nobj = { true: true };\nobj = { foo };\nobj = { [foo]: foo };\nobj = { [fo"
  },
  {
    "path": "test/samples/object-expressions/input.js",
    "chars": 667,
    "preview": "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 = {"
  },
  {
    "path": "test/samples/parenthesized-expression/expected.js",
    "chars": 5,
    "preview": "a + b"
  },
  {
    "path": "test/samples/parenthesized-expression/input.js",
    "chars": 88,
    "preview": "export default ({ x }) => ({\n\ttype: 'ParenthesizedExpression',\n\texpression: x`a + b`\n});"
  },
  {
    "path": "test/samples/regex/expected.js",
    "chars": 20,
    "preview": "/(?:^\\xb1\\X\\u765F)?/"
  },
  {
    "path": "test/samples/regex/input.js",
    "chars": 53,
    "preview": "export default ({ x }) => x`/(?:^\\\\xb1\\\\X\\\\u765F)?/`;"
  },
  {
    "path": "test/samples/removes-parens/expected.js",
    "chars": 9,
    "preview": "a = b + c"
  },
  {
    "path": "test/samples/removes-parens/input.js",
    "chars": 45,
    "preview": "export default ({ x }) => x`\na  =  (b + c)\n`;"
  },
  {
    "path": "test/samples/sourcemap/expected.js",
    "chars": 68,
    "preview": "const a = 42;\n\nfunction foo(value) {\n\tconsole.log(value);\n}\n\nfoo(a);"
  },
  {
    "path": "test/samples/sourcemap/input.js",
    "chars": 250,
    "preview": "import * as acorn from 'acorn';\n\nexport default ({ b, x }) => {\n\tconst decl = acorn.parse(`function foo(value) {\n\t\tconso"
  },
  {
    "path": "test/samples/string-literal/expected.js",
    "chars": 145,
    "preview": "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\n"
  },
  {
    "path": "test/samples/string-literal/input.js",
    "chars": 255,
    "preview": "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}'"
  },
  {
    "path": "test/samples/switch/expected.js",
    "chars": 83,
    "preview": "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",
    "chars": 117,
    "preview": "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",
    "chars": 9,
    "preview": "foo`bar`;"
  },
  {
    "path": "test/samples/tagged-template/input.js",
    "chars": 43,
    "preview": "export default ({ b }) => b`\nfoo\\`bar\\`;\n`;"
  },
  {
    "path": "test/samples/try-catch/expected.js",
    "chars": 92,
    "preview": "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",
    "chars": 124,
    "preview": "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"
  },
  {
    "path": "test/samples/var-declaration/expected.js",
    "chars": 284,
    "preview": "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"
  },
  {
    "path": "test/samples/var-declaration/input.js",
    "chars": 235,
    "preview": "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 "
  },
  {
    "path": "test/samples/with/expected.js",
    "chars": 17,
    "preview": "with (foo) bar();"
  },
  {
    "path": "test/samples/with/input.js",
    "chars": 388,
    "preview": "export default () => ({\n\ttype: 'WithStatement',\n\tstart: 0,\n\tend: 17,\n\tobject: {\n\t\ttype: 'Identifier',\n\t\tstart: 6,\n\t\tend:"
  },
  {
    "path": "test/samples/yield/expected.js",
    "chars": 59,
    "preview": "function* foo() {\n\tyield;\n}\n\nfunction* bar() {\n\tyield* 1;\n}"
  },
  {
    "path": "test/samples/yield/input.js",
    "chars": 91,
    "preview": "export default ({ b }) => b`\nfunction* foo() {\n\tyield;\n}\n\nfunction* bar() {\n\tyield* 1;\n}\n`;"
  },
  {
    "path": "test/test.js",
    "chars": 12617,
    "preview": "// @ts-check\nimport { decode } from '@jridgewell/sourcemap-codec';\nimport * as fs from 'fs';\nimport * as acorn from 'aco"
  },
  {
    "path": "tsconfig.json",
    "chars": 472,
    "preview": "{\n    \"compilerOptions\": {\n        \"allowJs\": true,\n        \"checkJs\": true,\n        \"declaration\": true,\n        \"emitD"
  }
]

About this extraction

This page contains the full source code of the Rich-Harris/code-red GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 125 files (82.9 KB), approximately 29.5k tokens, and a symbol index with 103 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!