package bundler_tests

// This file contains tests for "lowering" syntax, which means converting it to
// older JavaScript. For example, "a ** b" becomes a call to "Math.pow(a, b)"
// when lowered. Which syntax is lowered is determined by the language target.

import (
	"testing"

	"github.com/evanw/esbuild/internal/compat"
	"github.com/evanw/esbuild/internal/config"
)

var lower_suite = suite{
	name: "lower",
}

func TestLowerOptionalCatchNameCollisionNoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				try {}
				catch { var e, e2 }
				var e3
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2018),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerObjectSpreadNoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.jsx": `
				let tests = [
					{...a, ...b},
					{a, b, ...c},
					{...a, b, c},
					{a, ...b, c},
					{a, b, ...c, ...d, e, f, ...g, ...h, i, j},
				]
				let jsx = [
					<div {...a} {...b}/>,
					<div a b {...c}/>,
					<div {...a} b c/>,
					<div a {...b} c/>,
					<div a b {...c} {...d} e f {...g} {...h} i j/>,
				]
			`,
		},
		entryPaths: []string{"/entry.jsx"},
		options: config.Options{
			UnsupportedJSFeatures: es(2017),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerExponentiationOperatorNoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				let tests = {
					// Exponentiation operator
					0: a ** b ** c,
					1: (a ** b) ** c,

					// Exponentiation assignment operator
					2: a **= b,
					3: a.b **= c,
					4: a[b] **= c,
					5: a().b **= c,
					6: a()[b] **= c,
					7: a[b()] **= c,
					8: a()[b()] **= c,

					// These all should not need capturing (no object identity)
					9: a[0] **= b,
					10: a[false] **= b,
					11: a[null] **= b,
					12: a[void 0] **= b,
					13: a[123n] **= b,
					14: a[this] **= b,

					// These should need capturing (have object identity)
					15: a[/x/] **= b,
					16: a[{}] **= b,
					17: a[[]] **= b,
					18: a[() => {}] **= b,
					19: a[function() {}] **= b,
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2015),
			AbsOutputFile:         "/out.js",
		},
		expectedScanLog: `entry.js: WARNING: Big integer literals are not available in the configured target environment and may crash at run-time
`,
	})
}

func TestLowerPrivateFieldAssignments2015NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#x
					unary() {
						this.#x++
						this.#x--
						++this.#x
						--this.#x
					}
					binary() {
						this.#x = 1
						this.#x += 1
						this.#x -= 1
						this.#x *= 1
						this.#x /= 1
						this.#x %= 1
						this.#x **= 1
						this.#x <<= 1
						this.#x >>= 1
						this.#x >>>= 1
						this.#x &= 1
						this.#x |= 1
						this.#x ^= 1
						this.#x &&= 1
						this.#x ||= 1
						this.#x ??= 1
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2015),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateFieldAssignments2019NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#x
					unary() {
						this.#x++
						this.#x--
						++this.#x
						--this.#x
					}
					binary() {
						this.#x = 1
						this.#x += 1
						this.#x -= 1
						this.#x *= 1
						this.#x /= 1
						this.#x %= 1
						this.#x **= 1
						this.#x <<= 1
						this.#x >>= 1
						this.#x >>>= 1
						this.#x &= 1
						this.#x |= 1
						this.#x ^= 1
						this.#x &&= 1
						this.#x ||= 1
						this.#x ??= 1
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2019),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateFieldAssignments2020NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#x
					unary() {
						this.#x++
						this.#x--
						++this.#x
						--this.#x
					}
					binary() {
						this.#x = 1
						this.#x += 1
						this.#x -= 1
						this.#x *= 1
						this.#x /= 1
						this.#x %= 1
						this.#x **= 1
						this.#x <<= 1
						this.#x >>= 1
						this.#x >>>= 1
						this.#x &= 1
						this.#x |= 1
						this.#x ^= 1
						this.#x &&= 1
						this.#x ||= 1
						this.#x ??= 1
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateFieldAssignmentsNextNoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#x
					unary() {
						this.#x++
						this.#x--
						++this.#x
						--this.#x
					}
					binary() {
						this.#x = 1
						this.#x += 1
						this.#x -= 1
						this.#x *= 1
						this.#x /= 1
						this.#x %= 1
						this.#x **= 1
						this.#x <<= 1
						this.#x >>= 1
						this.#x >>>= 1
						this.#x &= 1
						this.#x |= 1
						this.#x ^= 1
						this.#x &&= 1
						this.#x ||= 1
						this.#x ??= 1
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			AbsOutputFile: "/out.js",
		},
	})
}

func TestLowerPrivateFieldOptionalChain2019NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#x
					foo() {
						this?.#x.y
						this?.y.#x
						this.#x?.y
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2019),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateFieldOptionalChain2020NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#x
					foo() {
						this?.#x.y
						this?.y.#x
						this.#x?.y
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateFieldOptionalChainNextNoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#x
					foo() {
						this?.#x.y
						this?.y.#x
						this.#x?.y
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			AbsOutputFile: "/out.js",
		},
	})
}

func TestTSLowerPrivateFieldOptionalChain2015NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				class Foo {
					#x
					foo() {
						this?.#x.y
						this?.y.#x
						this.#x?.y
					}
				}
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			UnsupportedJSFeatures: es(2015),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestTSLowerPrivateStaticMembers2015NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				class Foo {
					static #x
					static get #y() {}
					static set #y(x) {}
					static #z() {}
					foo() {
						Foo.#x += 1
						Foo.#y += 1
						Foo.#z()
					}
				}
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			UnsupportedJSFeatures: es(2015),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestTSLowerPrivateFieldAndMethodAvoidNameCollision2015(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				export class WeakMap {
					#x
				}
				export class WeakSet {
					#y() {}
				}
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2015),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateGetterSetter2015(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class Foo {
					get #foo() { return this.foo }
					set #bar(val) { this.bar = val }
					get #prop() { return this.prop }
					set #prop(val) { this.prop = val }
					foo(fn) {
						fn().#foo
						fn().#bar = 1
						fn().#prop
						fn().#prop = 2
					}
					unary(fn) {
						fn().#prop++;
						fn().#prop--;
						++fn().#prop;
						--fn().#prop;
					}
					binary(fn) {
						fn().#prop = 1;
						fn().#prop += 1;
						fn().#prop -= 1;
						fn().#prop *= 1;
						fn().#prop /= 1;
						fn().#prop %= 1;
						fn().#prop **= 1;
						fn().#prop <<= 1;
						fn().#prop >>= 1;
						fn().#prop >>>= 1;
						fn().#prop &= 1;
						fn().#prop |= 1;
						fn().#prop ^= 1;
						fn().#prop &&= 1;
						fn().#prop ||= 1;
						fn().#prop ??= 1;
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2015),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateGetterSetter2019(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class Foo {
					get #foo() { return this.foo }
					set #bar(val) { this.bar = val }
					get #prop() { return this.prop }
					set #prop(val) { this.prop = val }
					foo(fn) {
						fn().#foo
						fn().#bar = 1
						fn().#prop
						fn().#prop = 2
					}
					unary(fn) {
						fn().#prop++;
						fn().#prop--;
						++fn().#prop;
						--fn().#prop;
					}
					binary(fn) {
						fn().#prop = 1;
						fn().#prop += 1;
						fn().#prop -= 1;
						fn().#prop *= 1;
						fn().#prop /= 1;
						fn().#prop %= 1;
						fn().#prop **= 1;
						fn().#prop <<= 1;
						fn().#prop >>= 1;
						fn().#prop >>>= 1;
						fn().#prop &= 1;
						fn().#prop |= 1;
						fn().#prop ^= 1;
						fn().#prop &&= 1;
						fn().#prop ||= 1;
						fn().#prop ??= 1;
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2019),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateGetterSetter2020(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class Foo {
					get #foo() { return this.foo }
					set #bar(val) { this.bar = val }
					get #prop() { return this.prop }
					set #prop(val) { this.prop = val }
					foo(fn) {
						fn().#foo
						fn().#bar = 1
						fn().#prop
						fn().#prop = 2
					}
					unary(fn) {
						fn().#prop++;
						fn().#prop--;
						++fn().#prop;
						--fn().#prop;
					}
					binary(fn) {
						fn().#prop = 1;
						fn().#prop += 1;
						fn().#prop -= 1;
						fn().#prop *= 1;
						fn().#prop /= 1;
						fn().#prop %= 1;
						fn().#prop **= 1;
						fn().#prop <<= 1;
						fn().#prop >>= 1;
						fn().#prop >>>= 1;
						fn().#prop &= 1;
						fn().#prop |= 1;
						fn().#prop ^= 1;
						fn().#prop &&= 1;
						fn().#prop ||= 1;
						fn().#prop ??= 1;
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateGetterSetterNext(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class Foo {
					get #foo() { return this.foo }
					set #bar(val) { this.bar = val }
					get #prop() { return this.prop }
					set #prop(val) { this.prop = val }
					foo(fn) {
						fn().#foo
						fn().#bar = 1
						fn().#prop
						fn().#prop = 2
					}
					unary(fn) {
						fn().#prop++;
						fn().#prop--;
						++fn().#prop;
						--fn().#prop;
					}
					binary(fn) {
						fn().#prop = 1;
						fn().#prop += 1;
						fn().#prop -= 1;
						fn().#prop *= 1;
						fn().#prop /= 1;
						fn().#prop %= 1;
						fn().#prop **= 1;
						fn().#prop <<= 1;
						fn().#prop >>= 1;
						fn().#prop >>>= 1;
						fn().#prop &= 1;
						fn().#prop |= 1;
						fn().#prop ^= 1;
						fn().#prop &&= 1;
						fn().#prop ||= 1;
						fn().#prop ??= 1;
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:          config.ModeBundle,
			AbsOutputFile: "/out.js",
		},
	})
}

func TestLowerPrivateMethod2019(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class Foo {
					#field
					#method() {}
					baseline() {
						a().foo
						b().foo(x)
						c()?.foo(x)
						d().foo?.(x)
						e()?.foo?.(x)
					}
					privateField() {
						a().#field
						b().#field(x)
						c()?.#field(x)
						d().#field?.(x)
						e()?.#field?.(x)
						f()?.foo.#field(x).bar()
					}
					privateMethod() {
						a().#method
						b().#method(x)
						c()?.#method(x)
						d().#method?.(x)
						e()?.#method?.(x)
						f()?.foo.#method(x).bar()
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2019),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateMethod2020(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class Foo {
					#field
					#method() {}
					baseline() {
						a().foo
						b().foo(x)
						c()?.foo(x)
						d().foo?.(x)
						e()?.foo?.(x)
					}
					privateField() {
						a().#field
						b().#field(x)
						c()?.#field(x)
						d().#field?.(x)
						e()?.#field?.(x)
						f()?.foo.#field(x).bar()
					}
					privateMethod() {
						a().#method
						b().#method(x)
						c()?.#method(x)
						d().#method?.(x)
						e()?.#method?.(x)
						f()?.foo.#method(x).bar()
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateMethodNext(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class Foo {
					#field
					#method() {}
					baseline() {
						a().foo
						b().foo(x)
						c()?.foo(x)
						d().foo?.(x)
						e()?.foo?.(x)
					}
					privateField() {
						a().#field
						b().#field(x)
						c()?.#field(x)
						d().#field?.(x)
						e()?.#field?.(x)
						f()?.foo.#field(x).bar()
					}
					privateMethod() {
						a().#method
						b().#method(x)
						c()?.#method(x)
						d().#method?.(x)
						e()?.#method?.(x)
						f()?.foo.#method(x).bar()
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:          config.ModeBundle,
			AbsOutputFile: "/out.js",
		},
	})
}

func TestLowerPrivateClassExpr2020NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export let Foo = class {
					#field
					#method() {}
					static #staticField
					static #staticMethod() {}
					foo() {
						this.#field = this.#method()
						Foo.#staticField = Foo.#staticMethod()
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateMethodWithModifiers2020(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class Foo {
					*#g() {}
					async #a() {}
					async *#ag() {}

					static *#sg() {}
					static async #sa() {}
					static async *#sag() {}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerAsync2016NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				async function foo(bar) {
					await bar
					return [this, arguments]
				}
				class Foo {async foo() {}}
				new (class Bar extends class { } {
					constructor() {
						let x = 1;
						(async () => {
							console.log("before super", x);  // (1) Sync phase
							await 1;
							console.log("after super", x);   // (2) Async phase
						})();
						super();
						x = 2;
					}
				})();
				export default [
					foo,
					Foo,
					async function() {},
					async () => {},
					{async foo() {}},
					class {async foo() {}},
					function() {
						return async (bar) => {
							await bar
							return [this, arguments]
						}
					},
				]
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerAsync2017NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				async function foo(bar) {
					await bar
					return arguments
				}
				class Foo {async foo() {}}
				export default [
					foo,
					Foo,
					async function() {},
					async () => {},
					{async foo() {}},
					class {async foo() {}},
					function() {
						return async (bar) => {
							await bar
							return [this, arguments]
						}
					},
				]
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2017),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerAsyncThis2016CommonJS(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				exports.foo = async () => this
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerAsyncThis2016ES6(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export {bar} from "./other"
				export let foo = async () => this
			`,
			"/other.js": `
				export let bar = async () => {}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
		debugLogs: true,
		expectedScanLog: `entry.js: DEBUG: Top-level "this" will be replaced with undefined since this file is an ECMAScript module
entry.js: NOTE: This file is considered to be an ECMAScript module because of the "export" keyword here:
`,
	})
}

func TestLowerAsyncES5(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				import './fn-stmt'
				import './fn-expr'
				import './arrow-1'
				import './arrow-2'
				import './export-def-1'
				import './export-def-2'
				import './obj-method'
			`,
			"/fn-stmt.js":      `async function foo() {}`,
			"/fn-expr.js":      `(async function() {})`,
			"/arrow-1.js":      `(async () => {})`,
			"/arrow-2.js":      `(async x => {})`,
			"/export-def-1.js": `export default async function foo() {}`,
			"/export-def-2.js": `export default async function() {}`,
			"/obj-method.js":   `({async foo() {}})`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(5),
			AbsOutputFile:         "/out.js",
		},
		expectedScanLog: `arrow-1.js: ERROR: Transforming async functions to the configured target environment is not supported yet
arrow-2.js: ERROR: Transforming async functions to the configured target environment is not supported yet
export-def-1.js: ERROR: Transforming async functions to the configured target environment is not supported yet
export-def-2.js: ERROR: Transforming async functions to the configured target environment is not supported yet
fn-expr.js: ERROR: Transforming async functions to the configured target environment is not supported yet
fn-stmt.js: ERROR: Transforming async functions to the configured target environment is not supported yet
obj-method.js: ERROR: Transforming async functions to the configured target environment is not supported yet
`,
	})
}

func TestLowerAsyncSuperES2017NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Derived extends Base {
					async test(key) {
						return [
							await super.foo,
							await super[key],
							await ([super.foo] = [0]),
							await ([super[key]] = [0]),

							await (super.foo = 1),
							await (super[key] = 1),
							await (super.foo += 2),
							await (super[key] += 2),

							await ++super.foo,
							await ++super[key],
							await super.foo++,
							await super[key]++,

							await super.foo.name,
							await super[key].name,
							await super.foo?.name,
							await super[key]?.name,

							await super.foo(1, 2),
							await super[key](1, 2),
							await super.foo?.(1, 2),
							await super[key]?.(1, 2),

							await (() => super.foo)(),
							await (() => super[key])(),
							await (() => super.foo())(),
							await (() => super[key]())(),

							await super.foo` + "``" + `,
							await super[key]` + "``" + `,
						]
					}
				}

				// This covers a bug that caused a compiler crash
				let fn = async () => class extends Base {
					a = super.a
					b = () => super.b
					c() { return super.c }
					d() { return () => super.d }
				}

				// This covers a bug that generated bad code
				class Derived2 extends Base {
					async a() { return class { [super.foo] = 123 } }
					b = async () => class { [super.foo] = 123 }
				}

				// This covers putting the generated temporary variable inside the loop
				for (let i = 0; i < 3; i++) {
					objs.push({
						__proto__: {
							foo() { return i },
						},
						async bar() { return super.foo() },
					})
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2017),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerAsyncSuperES2016NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Derived extends Base {
					async test(key) {
						return [
							await super.foo,
							await super[key],
							await ([super.foo] = [0]),
							await ([super[key]] = [0]),

							await (super.foo = 1),
							await (super[key] = 1),
							await (super.foo += 2),
							await (super[key] += 2),

							await ++super.foo,
							await ++super[key],
							await super.foo++,
							await super[key]++,

							await super.foo.name,
							await super[key].name,
							await super.foo?.name,
							await super[key]?.name,

							await super.foo(1, 2),
							await super[key](1, 2),
							await super.foo?.(1, 2),
							await super[key]?.(1, 2),

							await (() => super.foo)(),
							await (() => super[key])(),
							await (() => super.foo())(),
							await (() => super[key]())(),

							await super.foo` + "``" + `,
							await super[key]` + "``" + `,
						]
					}
				}

				// This covers a bug that caused a compiler crash
				let fn = async () => class extends Base {
					a = super.a
					b = () => super.b
					c() { return super.c }
					d() { return () => super.d }
				}

				// This covers a bug that generated bad code
				class Derived2 extends Base {
					async a() { return class { [super.foo] = 123 } }
					b = async () => class { [super.foo] = 123 }
				}

				// This covers putting the generated temporary variable inside the loop
				for (let i = 0; i < 3; i++) {
					objs.push({
						__proto__: {
							foo() { return i },
						},
						async bar() { return super.foo() },
					})
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerStaticAsyncSuperES2021NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Derived extends Base {
					static test = async (key) => {
						return [
							await super.foo,
							await super[key],
							await ([super.foo] = [0]),
							await ([super[key]] = [0]),

							await (super.foo = 1),
							await (super[key] = 1),
							await (super.foo += 2),
							await (super[key] += 2),

							await ++super.foo,
							await ++super[key],
							await super.foo++,
							await super[key]++,

							await super.foo.name,
							await super[key].name,
							await super.foo?.name,
							await super[key]?.name,

							await super.foo(1, 2),
							await super[key](1, 2),
							await super.foo?.(1, 2),
							await super[key]?.(1, 2),

							await (() => super.foo)(),
							await (() => super[key])(),
							await (() => super.foo())(),
							await (() => super[key]())(),

							await super.foo` + "``" + `,
							await super[key]` + "``" + `,
						]
					}
				}

				// This covers a bug that caused a compiler crash
				let fn = async () => class extends Base {
					static a = super.a
					static b = () => super.b
					static c() { return super.c }
					static d() { return () => super.d }
				}

				// This covers a bug that generated bad code
				class Derived2 extends Base {
					static async a() { return class { [super.foo] = 123 } }
					static b = async () => class { [super.foo] = 123 }
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2021),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerStaticAsyncSuperES2016NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Derived extends Base {
					static test = async (key) => {
						return [
							await super.foo,
							await super[key],
							await ([super.foo] = [0]),
							await ([super[key]] = [0]),

							await (super.foo = 1),
							await (super[key] = 1),
							await (super.foo += 2),
							await (super[key] += 2),

							await ++super.foo,
							await ++super[key],
							await super.foo++,
							await super[key]++,

							await super.foo.name,
							await super[key].name,
							await super.foo?.name,
							await super[key]?.name,

							await super.foo(1, 2),
							await super[key](1, 2),
							await super.foo?.(1, 2),
							await super[key]?.(1, 2),

							await (() => super.foo)(),
							await (() => super[key])(),
							await (() => super.foo())(),
							await (() => super[key]())(),

							await super.foo` + "``" + `,
							await super[key]` + "``" + `,
						]
					}
				}

				// This covers a bug that caused a compiler crash
				let fn = async () => class extends Base {
					static a = super.a
					static b = () => super.b
					static c() { return super.c }
					static d() { return () => super.d }
				}

				// This covers a bug that generated bad code
				class Derived2 extends Base {
					static async a() { return class { [super.foo] = 123 } }
					static b = async () => class { [super.foo] = 123 }
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerStaticSuperES2021NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Derived extends Base {
					static test = key => {
						return [
							super.foo,
							super[key],
							([super.foo] = [0]),
							([super[key]] = [0]),

							(super.foo = 1),
							(super[key] = 1),
							(super.foo += 2),
							(super[key] += 2),

							++super.foo,
							++super[key],
							super.foo++,
							super[key]++,

							super.foo.name,
							super[key].name,
							super.foo?.name,
							super[key]?.name,

							super.foo(1, 2),
							super[key](1, 2),
							super.foo?.(1, 2),
							super[key]?.(1, 2),

							(() => super.foo)(),
							(() => super[key])(),
							(() => super.foo())(),
							(() => super[key]())(),

							super.foo` + "``" + `,
							super[key]` + "``" + `,
						]
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2021),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerStaticSuperES2016NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Derived extends Base {
					static test = key => {
						return [
							super.foo,
							super[key],
							([super.foo] = [0]),
							([super[key]] = [0]),

							(super.foo = 1),
							(super[key] = 1),
							(super.foo += 2),
							(super[key] += 2),

							++super.foo,
							++super[key],
							super.foo++,
							super[key]++,

							super.foo.name,
							super[key].name,
							super.foo?.name,
							super[key]?.name,

							super.foo(1, 2),
							super[key](1, 2),
							super.foo?.(1, 2),
							super[key]?.(1, 2),

							(() => super.foo)(),
							(() => super[key])(),
							(() => super.foo())(),
							(() => super[key]())(),

							super.foo` + "``" + `,
							super[key]` + "``" + `,
						]
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerAsyncArrowSuperES2016(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export { default as foo1 } from "./foo1"
				export { default as foo2 } from "./foo2"
				export { default as foo3 } from "./foo3"
				export { default as foo4 } from "./foo4"
				export { default as bar1 } from "./bar1"
				export { default as bar2 } from "./bar2"
				export { default as bar3 } from "./bar3"
				export { default as bar4 } from "./bar4"
				export { default as baz1 } from "./baz1"
				export { default as baz2 } from "./baz2"
				import "./outer"
			`,
			"/foo1.js": `export default class extends x { foo1() { return async () => super.foo('foo1') } }`,
			"/foo2.js": `export default class extends x { foo2() { return async () => () => super.foo('foo2') } }`,
			"/foo3.js": `export default class extends x { foo3() { return () => async () => super.foo('foo3') } }`,
			"/foo4.js": `export default class extends x { foo4() { return async () => async () => super.foo('foo4') } }`,
			"/bar1.js": `export default class extends x { bar1 = async () => super.foo('bar1') }`,
			"/bar2.js": `export default class extends x { bar2 = async () => () => super.foo('bar2') }`,
			"/bar3.js": `export default class extends x { bar3 = () => async () => super.foo('bar3') }`,
			"/bar4.js": `export default class extends x { bar4 = async () => async () => super.foo('bar4') }`,
			"/baz1.js": `export default class extends x { async baz1() { return () => super.foo('baz1') } }`,
			"/baz2.js": `export default class extends x { async baz2() { return () => () => super.foo('baz2') } }`,
			"/outer.js": `
				// Helper functions for "super" shouldn't be inserted into this outer function
				export default (async function () {
					class y extends z {
						foo = async () => super.foo()
					}
					await new y().foo()()
				})()
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerAsyncArrowSuperSetterES2016(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export { default as foo1 } from "./foo1"
				export { default as foo2 } from "./foo2"
				export { default as foo3 } from "./foo3"
				export { default as foo4 } from "./foo4"
				export { default as bar1 } from "./bar1"
				export { default as bar2 } from "./bar2"
				export { default as bar3 } from "./bar3"
				export { default as bar4 } from "./bar4"
				export { default as baz1 } from "./baz1"
				export { default as baz2 } from "./baz2"
				import "./outer"
			`,
			"/foo1.js": `export default class extends x { foo1() { return async () => super.foo = 'foo1' } }`,
			"/foo2.js": `export default class extends x { foo2() { return async () => () => super.foo = 'foo2' } }`,
			"/foo3.js": `export default class extends x { foo3() { return () => async () => super.foo = 'foo3' } }`,
			"/foo4.js": `export default class extends x { foo4() { return async () => async () => super.foo = 'foo4' } }`,
			"/bar1.js": `export default class extends x { bar1 = async () => super.foo = 'bar1' }`,
			"/bar2.js": `export default class extends x { bar2 = async () => () => super.foo = 'bar2' }`,
			"/bar3.js": `export default class extends x { bar3 = () => async () => super.foo = 'bar3' }`,
			"/bar4.js": `export default class extends x { bar4 = async () => async () => super.foo = 'bar4' }`,
			"/baz1.js": `export default class extends x { async baz1() { return () => super.foo = 'baz1' } }`,
			"/baz2.js": `export default class extends x { async baz2() { return () => () => super.foo = 'baz2' } }`,
			"/outer.js": `
				// Helper functions for "super" shouldn't be inserted into this outer function
				export default (async function () {
					class y extends z {
						foo = async () => super.foo = 'foo'
					}
					await new y().foo()()
				})()
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerStaticAsyncArrowSuperES2016(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export { default as foo1 } from "./foo1"
				export { default as foo2 } from "./foo2"
				export { default as foo3 } from "./foo3"
				export { default as foo4 } from "./foo4"
				export { default as bar1 } from "./bar1"
				export { default as bar2 } from "./bar2"
				export { default as bar3 } from "./bar3"
				export { default as bar4 } from "./bar4"
				export { default as baz1 } from "./baz1"
				export { default as baz2 } from "./baz2"
				import "./outer"
			`,
			"/foo1.js": `export default class extends x { static foo1() { return async () => super.foo('foo1') } }`,
			"/foo2.js": `export default class extends x { static foo2() { return async () => () => super.foo('foo2') } }`,
			"/foo3.js": `export default class extends x { static foo3() { return () => async () => super.foo('foo3') } }`,
			"/foo4.js": `export default class extends x { static foo4() { return async () => async () => super.foo('foo4') } }`,
			"/bar1.js": `export default class extends x { static bar1 = async () => super.foo('bar1') }`,
			"/bar2.js": `export default class extends x { static bar2 = async () => () => super.foo('bar2') }`,
			"/bar3.js": `export default class extends x { static bar3 = () => async () => super.foo('bar3') }`,
			"/bar4.js": `export default class extends x { static bar4 = async () => async () => super.foo('bar4') }`,
			"/baz1.js": `export default class extends x { static async baz1() { return () => super.foo('baz1') } }`,
			"/baz2.js": `export default class extends x { static async baz2() { return () => () => super.foo('baz2') } }`,
			"/outer.js": `
				// Helper functions for "super" shouldn't be inserted into this outer function
				export default (async function () {
					class y extends z {
						static foo = async () => super.foo()
					}
					await y.foo()()
				})()
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerStaticAsyncArrowSuperSetterES2016(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export { default as foo1 } from "./foo1"
				export { default as foo2 } from "./foo2"
				export { default as foo3 } from "./foo3"
				export { default as foo4 } from "./foo4"
				export { default as bar1 } from "./bar1"
				export { default as bar2 } from "./bar2"
				export { default as bar3 } from "./bar3"
				export { default as bar4 } from "./bar4"
				export { default as baz1 } from "./baz1"
				export { default as baz2 } from "./baz2"
				import "./outer"
			`,
			"/foo1.js": `export default class extends x { static foo1() { return async () => super.foo = 'foo1' } }`,
			"/foo2.js": `export default class extends x { static foo2() { return async () => () => super.foo = 'foo2' } }`,
			"/foo3.js": `export default class extends x { static foo3() { return () => async () => super.foo = 'foo3' } }`,
			"/foo4.js": `export default class extends x { static foo4() { return async () => async () => super.foo = 'foo4' } }`,
			"/bar1.js": `export default class extends x { static bar1 = async () => super.foo = 'bar1' }`,
			"/bar2.js": `export default class extends x { static bar2 = async () => () => super.foo = 'bar2' }`,
			"/bar3.js": `export default class extends x { static bar3 = () => async () => super.foo = 'bar3' }`,
			"/bar4.js": `export default class extends x { static bar4 = async () => async () => super.foo = 'bar4' }`,
			"/baz1.js": `export default class extends x { static async baz1() { return () => super.foo = 'baz1' } }`,
			"/baz2.js": `export default class extends x { static async baz2() { return () => () => super.foo = 'baz2' } }`,
			"/outer.js": `
				// Helper functions for "super" shouldn't be inserted into this outer function
				export default (async function () {
					class y extends z {
						static foo = async () => super.foo = 'foo'
					}
					await y.foo()()
				})()
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2016),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateSuperES2022(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export { default as foo1 } from "./foo1"
				export { default as foo2 } from "./foo2"
				export { default as foo3 } from "./foo3"
				export { default as foo4 } from "./foo4"
				export { default as foo5 } from "./foo5"
				export { default as foo6 } from "./foo6"
				export { default as foo7 } from "./foo7"
				export { default as foo8 } from "./foo8"
			`,
			"/foo1.js": `export default class extends x { #foo() { super.foo() } }`,
			"/foo2.js": `export default class extends x { #foo() { super.foo++ } }`,
			"/foo3.js": `export default class extends x { static #foo() { super.foo() } }`,
			"/foo4.js": `export default class extends x { static #foo() { super.foo++ } }`,
			"/foo5.js": `export default class extends x { #foo = () => { super.foo() } }`,
			"/foo6.js": `export default class extends x { #foo = () => { super.foo++ } }`,
			"/foo7.js": `export default class extends x { static #foo = () => { super.foo() } }`,
			"/foo8.js": `export default class extends x { static #foo = () => { super.foo++ } }`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2022),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerPrivateSuperES2021(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export { default as foo1 } from "./foo1"
				export { default as foo2 } from "./foo2"
				export { default as foo3 } from "./foo3"
				export { default as foo4 } from "./foo4"
				export { default as foo5 } from "./foo5"
				export { default as foo6 } from "./foo6"
				export { default as foo7 } from "./foo7"
				export { default as foo8 } from "./foo8"
			`,
			"/foo1.js": `export default class extends x { #foo() { super.foo() } }`,
			"/foo2.js": `export default class extends x { #foo() { super.foo++ } }`,
			"/foo3.js": `export default class extends x { static #foo() { super.foo() } }`,
			"/foo4.js": `export default class extends x { static #foo() { super.foo++ } }`,
			"/foo5.js": `export default class extends x { #foo = () => { super.foo() } }`,
			"/foo6.js": `export default class extends x { #foo = () => { super.foo++ } }`,
			"/foo7.js": `export default class extends x { static #foo = () => { super.foo() } }`,
			"/foo8.js": `export default class extends x { static #foo = () => { super.foo++ } }`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2021),
			AbsOutputFile:         "/out.js",
		},
	})
}

// https://github.com/evanw/esbuild/issues/2158
func TestLowerPrivateSuperStaticBundleIssue2158(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class Foo extends Object {
					static FOO;
					constructor() {
						super();
					}
					#foo;
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:          config.ModeBundle,
			AbsOutputFile: "/out.js",
		},
	})
}

func TestLowerClassField2020NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#foo = 123
					#bar
					foo = 123
					bar
					static #s_foo = 123
					static #s_bar
					static s_foo = 123
					static s_bar
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerClassFieldNextNoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#foo = 123
					#bar
					foo = 123
					bar
					static #s_foo = 123
					static #s_bar
					static s_foo = 123
					static s_bar
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			AbsOutputFile: "/out.js",
		},
	})
}

func TestTSLowerClassField2020NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				class Foo {
					#foo = 123
					#bar
					foo = 123
					bar
					static #s_foo = 123
					static #s_bar
					static s_foo = 123
					static s_bar
				}
			`,
			"/tsconfig.json": `{
				"compilerOptions": {
					"useDefineForClassFields": false
				}
			}`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestTSLowerClassPrivateFieldNextNoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				class Foo {
					#foo = 123
					#bar
					foo = 123
					bar
					static #s_foo = 123
					static #s_bar
					static s_foo = 123
					static s_bar
				}
			`,
			"/tsconfig.json": `{
				"compilerOptions": {
					"useDefineForClassFields": false
				}
			}`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			AbsOutputFile: "/out.js",
		},
	})
}

func TestLowerClassFieldStrictTsconfigJson2020(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				import loose from './loose'
				import strict from './strict'
				console.log(loose, strict)
			`,
			"/loose/index.js": `
				export default class {
					foo
				}
			`,
			"/loose/tsconfig.json": `
				{
					"compilerOptions": {
						"useDefineForClassFields": false
					}
				}
			`,
			"/strict/index.js": `
				export default class {
					foo
				}
			`,
			"/strict/tsconfig.json": `
				{
					"compilerOptions": {
						"useDefineForClassFields": true
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestTSLowerClassFieldStrictTsconfigJson2020(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				import loose from './loose'
				import strict from './strict'
				console.log(loose, strict)
			`,
			"/loose/index.ts": `
				export default class {
					foo
				}
			`,
			"/loose/tsconfig.json": `
				{
					"compilerOptions": {
						"useDefineForClassFields": false
					}
				}
			`,
			"/strict/index.ts": `
				export default class {
					foo
				}
			`,
			"/strict/tsconfig.json": `
				{
					"compilerOptions": {
						"useDefineForClassFields": true
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2020),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestTSLowerObjectRest2017NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				const { ...local_const } = {};
				let { ...local_let } = {};
				var { ...local_var } = {};
				let arrow_fn = ({ ...x }) => { };
				let fn_expr = function ({ ...x } = default_value) {};
				let class_expr = class { method(x, ...[y, { ...z }]) {} };

				function fn_stmt({ a = b(), ...x }, { c = d(), ...y }) {}
				class class_stmt { method({ ...x }) {} }
				namespace ns { export let { ...x } = {} }
				try { } catch ({ ...catch_clause }) {}

				for (const { ...for_in_const } in { abc }) {}
				for (let { ...for_in_let } in { abc }) {}
				for (var { ...for_in_var } in { abc }) ;
				for (const { ...for_of_const } of [{}]) ;
				for (let { ...for_of_let } of [{}]) x()
				for (var { ...for_of_var } of [{}]) x()
				for (const { ...for_const } = {}; x; x = null) {}
				for (let { ...for_let } = {}; x; x = null) {}
				for (var { ...for_var } = {}; x; x = null) {}
				for ({ ...x } in { abc }) {}
				for ({ ...x } of [{}]) {}
				for ({ ...x } = {}; x; x = null) {}

				({ ...assign } = {});
				({ obj_method({ ...x }) {} });

				// Check for used return values
				({ ...x } = x);
				for ({ ...x } = x; 0; ) ;
				console.log({ ...x } = x);
				console.log({ x, ...xx } = { x });
				console.log({ x: { ...xx } } = { x });
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			UnsupportedJSFeatures: es(2017),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestTSLowerObjectRest2018NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				const { ...local_const } = {};
				let { ...local_let } = {};
				var { ...local_var } = {};
				let arrow_fn = ({ ...x }) => { };
				let fn_expr = function ({ ...x } = default_value) {};
				let class_expr = class { method(x, ...[y, { ...z }]) {} };

				function fn_stmt({ a = b(), ...x }, { c = d(), ...y }) {}
				class class_stmt { method({ ...x }) {} }
				namespace ns { export let { ...x } = {} }
				try { } catch ({ ...catch_clause }) {}

				for (const { ...for_in_const } in { abc }) {}
				for (let { ...for_in_let } in { abc }) {}
				for (var { ...for_in_var } in { abc }) ;
				for (const { ...for_of_const } of [{}]) ;
				for (let { ...for_of_let } of [{}]) x()
				for (var { ...for_of_var } of [{}]) x()
				for (const { ...for_const } = {}; x; x = null) {}
				for (let { ...for_let } = {}; x; x = null) {}
				for (var { ...for_var } = {}; x; x = null) {}
				for ({ ...x } in { abc }) {}
				for ({ ...x } of [{}]) {}
				for ({ ...x } = {}; x; x = null) {}

				({ ...assign } = {});
				({ obj_method({ ...x }) {} });

				// Check for used return values
				({ ...x } = x);
				for ({ ...x } = x; 0; ) ;
				console.log({ ...x } = x);
				console.log({ x, ...xx } = { x });
				console.log({ x: { ...xx } } = { x });
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			UnsupportedJSFeatures: es(2018),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestClassSuperThisIssue242NoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				export class A {}

				export class B extends A {
					#e: string
					constructor(c: { d: any }) {
						super()
						this.#e = c.d ?? 'test'
					}
					f() {
						return this.#e
					}
				}
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			UnsupportedJSFeatures: es(2019),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerExportStarAsNameCollisionNoBundle(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export * as ns from 'path'
				let ns = 123
				export {ns as sn}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			UnsupportedJSFeatures: es(2019),
			AbsOutputFile:         "/out.js",
		},
	})
}

func TestLowerExportStarAsNameCollision(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				import * as test from './nested'
				console.log(test.foo, test.oof)
				export * as ns from 'path1'
				let ns = 123
				export {ns as sn}
			`,
			"/nested.js": `
				export * as foo from 'path2'
				let foo = 123
				export {foo as oof}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			UnsupportedJSFeatures: es(2019),
			AbsOutputFile:         "/out.js",
			ExternalSettings: config.ExternalSettings{
				PreResolve: config.ExternalMatchers{Exact: map[string]bool{
					"path1": true,
					"path2": true,
				}},
			},
		},
	})
}

func TestLowerStrictModeSyntax(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				import './for-in'
			`,
			"/for-in.js": `
				if (test)
					for (var a = b in {}) ;
				for (var x = y in {}) ;
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:          config.ModeBundle,
			OutputFormat:  config.FormatESModule,
			AbsOutputFile: "/out.js",
		},
	})
}

func TestLowerForbidStrictModeSyntax(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				import './with'
				import './delete-1'
				import './delete-2'
				import './delete-3'
			`,
			"/with.js": `
				with (x) y
			`,
			"/delete-1.js": `
				delete x
			`,
			"/delete-2.js": `
				delete (y)
			`,
			"/delete-3.js": `
				delete (1 ? z : z)
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:          config.ModeBundle,
			OutputFormat:  config.FormatESModule,
			AbsOutputFile: "/out.js",
		},
		expectedScanLog: `delete-1.js: ERROR: Delete of a bare identifier cannot be used with the "esm" output format due to strict mode
delete-2.js: ERROR: Delete of a bare identifier cannot be used with the "esm" output format due to strict mode
with.js: ERROR: With statements cannot be used with the "esm" output format due to strict mode
`,
	})
}

func TestLowerPrivateClassFieldOrder(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#foo = 123 // This must be set before "bar" is initialized
					bar = this.#foo
				}
				console.log(new Foo().bar === 123)
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.ClassPrivateField,
		},
	})
}

func TestLowerPrivateClassMethodOrder(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					bar = this.#foo()
					#foo() { return 123 } // This must be set before "bar" is initialized
				}
				console.log(new Foo().bar === 123)
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.ClassPrivateMethod,
		},
	})
}

func TestLowerPrivateClassAccessorOrder(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					bar = this.#foo
					get #foo() { return 123 } // This must be set before "bar" is initialized
				}
				console.log(new Foo().bar === 123)
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.ClassPrivateAccessor,
		},
	})
}

func TestLowerPrivateClassStaticFieldOrder(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					static #foo = 123 // This must be set before "bar" is initialized
					static bar = Foo.#foo
				}
				console.log(Foo.bar === 123)

				class FooThis {
					static #foo = 123 // This must be set before "bar" is initialized
					static bar = this.#foo
				}
				console.log(FooThis.bar === 123)
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.ClassPrivateStaticField,
		},
	})
}

func TestLowerPrivateClassStaticMethodOrder(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					static bar = Foo.#foo()
					static #foo() { return 123 } // This must be set before "bar" is initialized
				}
				console.log(Foo.bar === 123)

				class FooThis {
					static bar = this.#foo()
					static #foo() { return 123 } // This must be set before "bar" is initialized
				}
				console.log(FooThis.bar === 123)
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.ClassPrivateStaticMethod,
		},
	})
}

func TestLowerPrivateClassStaticAccessorOrder(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					static bar = Foo.#foo
					static get #foo() { return 123 } // This must be set before "bar" is initialized
				}
				console.log(Foo.bar === 123)

				class FooThis {
					static bar = this.#foo
					static get #foo() { return 123 } // This must be set before "bar" is initialized
				}
				console.log(FooThis.bar === 123)
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.ClassPrivateStaticAccessor,
		},
	})
}

func TestLowerPrivateClassBrandCheckUnsupported(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#foo
					#bar
					baz() {
						return [
							this.#foo,
							this.#bar,
							#foo in this,
						]
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.ClassPrivateBrandCheck,
		},
	})
}

func TestLowerPrivateClassBrandCheckSupported(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class Foo {
					#foo
					#bar
					baz() {
						return [
							this.#foo,
							this.#bar,
							#foo in this,
						]
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:          config.ModePassThrough,
			AbsOutputFile: "/out.js",
		},
	})
}

func TestLowerTemplateObject(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				x = () => [
					tag` + "`x`" + `,
					tag` + "`\\xFF`" + `,
					tag` + "`\\x`" + `,
					tag` + "`\\u`" + `,
				]
				y = () => [
					tag` + "`x${y}z`" + `,
					tag` + "`\\xFF${y}z`" + `,
					tag` + "`x${y}\\z`" + `,
					tag` + "`x${y}\\u`" + `,
				]
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.TemplateLiteral,
		},
	})
}

// See https://github.com/evanw/esbuild/issues/1424 for more information
func TestLowerPrivateClassFieldStaticIssue1424(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class T {
					#a() { return 'a'; }
					#b() { return 'b'; }
					static c;
					d() { console.log(this.#a()); }
				}
				new T().d();
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.ClassPrivateMethod,
		},
	})
}

// See https://github.com/evanw/esbuild/issues/1493 for more information
func TestLowerNullishCoalescingAssignmentIssue1493(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export class A {
					#a;
					f() {
						this.#a ??= 1;
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.LogicalAssignment,
		},
	})
}

func TestStaticClassBlockESNext(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class A {
					static {}
					static {
						this.thisField++
						A.classField++
						super.superField = super.superField + 1
						super.superField++
					}
				}
				let B = class {
					static {}
					static {
						this.thisField++
						super.superField = super.superField + 1
						super.superField++
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:          config.ModeBundle,
			AbsOutputFile: "/out.js",
		},
	})
}

func TestStaticClassBlockES2021(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				class A {
					static {}
					static {
						this.thisField++
						A.classField++
						super.superField = super.superField + 1
						super.superField++
					}
				}
				let B = class {
					static {}
					static {
						this.thisField++
						super.superField = super.superField + 1
						super.superField++
					}
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: es(2021),
		},
	})
}

func TestLowerRegExpNameCollision(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export function foo(RegExp) {
					return new RegExp(/./d, 'd')
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: es(2021),
		},
	})
}

func TestLowerForAwait2017(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export default [
					async () => { for await (x of y) z(x) },
					async () => { for await (x.y of y) z(x) },
					async () => { for await (let x of y) z(x) },
					async () => { for await (const x of y) z(x) },
					async () => { label: for await (const x of y) break label },
					async () => { label: for await (const x of y) continue label },
				]
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: es(2017),
		},
	})
}

func TestLowerForAwait2015(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				export default [
					async () => { for await (x of y) z(x) },
					async () => { for await (x.y of y) z(x) },
					async () => { for await (let x of y) z(x) },
					async () => { for await (const x of y) z(x) },
					async () => { label: for await (const x of y) break label },
					async () => { label: for await (const x of y) continue label },
				]
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: es(2015),
		},
	})
}

func TestLowerNestedFunctionDirectEval(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/1.js": "if (foo) { function x() {} }",
			"/2.js": "if (foo) { function x() {} eval('') }",
			"/3.js": "if (foo) { function x() {} if (bar) { eval('') } }",
			"/4.js": "if (foo) { eval(''); function x() {} }",
			"/5.js": "'use strict'; if (foo) { function x() {} }",
			"/6.js": "'use strict'; if (foo) { function x() {} eval('') }",
			"/7.js": "'use strict'; if (foo) { function x() {} if (bar) { eval('') } }",
			"/8.js": "'use strict'; if (foo) { eval(''); function x() {} }",
		},
		entryPaths: []string{
			"/1.js",
			"/2.js",
			"/3.js",
			"/4.js",
			"/5.js",
			"/6.js",
			"/7.js",
			"/8.js",
		},
		options: config.Options{
			Mode:         config.ModePassThrough,
			AbsOutputDir: "/out",
		},
	})
}

func TestJavaScriptDecoratorsESNext(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				@x.y()
				@(new y.x)
				export default class Foo {
					@x @y mUndef
					@x @y mDef = 1
					@x @y method() { return new Foo }
					@x @y static sUndef
					@x @y static sDef = new Foo
					@x @y static sMethod() { return new Foo }
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:          config.ModePassThrough,
			AbsOutputFile: "/out.js",
		},
	})
}

func TestJavaScriptAutoAccessorESNext(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/js-define.js": `
				class Foo {
					accessor one = 1
					accessor #two = 2
					accessor [three()] = 3

					static accessor four = 4
					static accessor #five = 5
					static accessor [six()] = 6
				}
			`,
			"/ts-define/ts-define.ts": `
				class Foo {
					accessor one = 1
					accessor #two = 2
					accessor [three()] = 3

					static accessor four = 4
					static accessor #five = 5
					static accessor [six()] = 6
				}
				class Normal { accessor a = b; c = d }
				class Private { accessor #a = b; c = d }
				class StaticNormal { static accessor a = b; static c = d }
				class StaticPrivate { static accessor #a = b; static c = d }
			`,
			"/ts-define/tsconfig.json": `{
				"compilerOptions": {
					"useDefineForClassFields": true,
				},
			}`,
			"/ts-assign/ts-assign.ts": `
				class Foo {
					accessor one = 1
					accessor #two = 2
					accessor [three()] = 3

					static accessor four = 4
					static accessor #five = 5
					static accessor [six()] = 6
				}
				class Normal { accessor a = b; c = d }
				class Private { accessor #a = b; c = d }
				class StaticNormal { static accessor a = b; static c = d }
				class StaticPrivate { static accessor #a = b; static c = d }
			`,
			"/ts-assign/tsconfig.json": `{
				"compilerOptions": {
					"useDefineForClassFields": false,
				},
			}`,
		},
		entryPaths: []string{
			"/js-define.js",
			"/ts-define/ts-define.ts",
			"/ts-assign/ts-assign.ts",
		},
		options: config.Options{
			Mode:         config.ModePassThrough,
			AbsOutputDir: "/out",
		},
	})
}

func TestJavaScriptAutoAccessorES2022(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/js-define.js": `
				class Foo {
					accessor one = 1
					accessor #two = 2
					accessor [three()] = 3

					static accessor four = 4
					static accessor #five = 5
					static accessor [six()] = 6
				}
			`,
			"/ts-define/ts-define.ts": `
				class Foo {
					accessor one = 1
					accessor #two = 2
					accessor [three()] = 3

					static accessor four = 4
					static accessor #five = 5
					static accessor [six()] = 6
				}
				class Normal { accessor a = b; c = d }
				class Private { accessor #a = b; c = d }
				class StaticNormal { static accessor a = b; static c = d }
				class StaticPrivate { static accessor #a = b; static c = d }
			`,
			"/ts-define/tsconfig.json": `{
				"compilerOptions": {
					"useDefineForClassFields": true,
				},
			}`,
			"/ts-assign/ts-assign.ts": `
				class Foo {
					accessor one = 1
					accessor #two = 2
					accessor [three()] = 3

					static accessor four = 4
					static accessor #five = 5
					static accessor [six()] = 6
				}
				class Normal { accessor a = b; c = d }
				class Private { accessor #a = b; c = d }
				class StaticNormal { static accessor a = b; static c = d }
				class StaticPrivate { static accessor #a = b; static c = d }
			`,
			"/ts-assign/tsconfig.json": `{
				"compilerOptions": {
					"useDefineForClassFields": false,
				},
			}`,
		},
		entryPaths: []string{
			"/js-define.js",
			"/ts-define/ts-define.ts",
			"/ts-assign/ts-assign.ts",
		},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: es(2022),
		},
	})
}

func TestJavaScriptAutoAccessorES2021(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/js-define.js": `
				class Foo {
					accessor one = 1
					accessor #two = 2
					accessor [three()] = 3

					static accessor four = 4
					static accessor #five = 5
					static accessor [six()] = 6
				}
			`,
			"/ts-define/ts-define.ts": `
				class Foo {
					accessor one = 1
					accessor #two = 2
					accessor [three()] = 3

					static accessor four = 4
					static accessor #five = 5
					static accessor [six()] = 6
				}
				class Normal { accessor a = b; c = d }
				class Private { accessor #a = b; c = d }
				class StaticNormal { static accessor a = b; static c = d }
				class StaticPrivate { static accessor #a = b; static c = d }
			`,
			"/ts-define/tsconfig.json": `{
				"compilerOptions": {
					"useDefineForClassFields": true,
				},
			}`,
			"/ts-assign/ts-assign.ts": `
				class Foo {
					accessor one = 1
					accessor #two = 2
					accessor [three()] = 3

					static accessor four = 4
					static accessor #five = 5
					static accessor [six()] = 6
				}
				class Normal { accessor a = b; c = d }
				class Private { accessor #a = b; c = d }
				class StaticNormal { static accessor a = b; static c = d }
				class StaticPrivate { static accessor #a = b; static c = d }
			`,
			"/ts-assign/tsconfig.json": `{
				"compilerOptions": {
					"useDefineForClassFields": false,
				},
			}`,
		},
		entryPaths: []string{
			"/js-define.js",
			"/ts-define/ts-define.ts",
			"/ts-assign/ts-assign.ts",
		},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: es(2021),
		},
	})
}

func TestLowerUsing(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				using a = b
				await using c = d
				if (nested) {
					using x = 1
					await using y = 2
				}

				function foo() {
					using a = b
					if (nested) {
						using x = 1
					}
				}

				async function bar() {
					using a = b
					await using c = d
					if (nested) {
						using x = 1
						await using y = 2
					}
				}
			`,
			"/loops.js": `
				for (using a of b) c(() => a)
				for (await using d of e) f(() => d)
				for await (using g of h) i(() => g)
				for await (await using j of k) l(() => j)

				if (nested) {
					for (using a of b) c(() => a)
					for (await using d of e) f(() => d)
					for await (using g of h) i(() => g)
					for await (await using j of k) l(() => j)
				}

				function foo() {
					for (using a of b) c(() => a)
				}

				async function bar() {
					for (using a of b) c(() => a)
					for (await using d of e) f(() => d)
					for await (using g of h) i(() => g)
					for await (await using j of k) l(() => j)
				}
			`,
		},
		entryPaths: []string{
			"/entry.js",
			"/loops.js",
		},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: compat.Using,
		},
	})
}

func TestLowerUsingUnsupportedAsync(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				function foo() {
					using a = b
					if (nested) {
						using x = 1
					}
				}

				async function bar() {
					using a = b
					await using c = d
					if (nested) {
						using x = 1
						await using y = 2
					}
				}
			`,
			"/loops.js": `
				for (using a of b) c(() => a)

				if (nested) {
					for (using a of b) c(() => a)
				}

				function foo() {
					for (using a of b) c(() => a)
				}

				async function bar() {
					for (using a of b) c(() => a)
					for (await using d of e) f(() => d)
				}
			`,
		},
		entryPaths: []string{
			"/entry.js",
			"/loops.js",
		},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: compat.AsyncAwait | compat.TopLevelAwait,
		},
	})
}

func TestLowerUsingUnsupportedUsingAndAsync(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				function foo() {
					using a = b
					if (nested) {
						using x = 1
					}
				}

				async function bar() {
					using a = b
					await using c = d
					if (nested) {
						using x = 1
						await using y = 2
					}
				}
			`,
			"/loops.js": `
				for (using a of b) c(() => a)

				if (nested) {
					for (using a of b) c(() => a)
				}

				function foo() {
					for (using a of b) c(() => a)
				}

				async function bar() {
					for (using a of b) c(() => a)
					for (await using d of e) f(() => d)
				}
			`,
		},
		entryPaths: []string{
			"/entry.js",
			"/loops.js",
		},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: compat.Using | compat.AsyncAwait | compat.TopLevelAwait,
		},
	})
}

func TestLowerUsingHoisting(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/hoist-use-strict.js": `
				"use strict"
				using a = b
				function foo() {
					"use strict"
					using a = b
				}
			`,
			"/hoist-directive.js": `
				"use wtf"
				using a = b
				function foo() {
					"use wtf"
					using a = b
				}
			`,
			"/hoist-import.js": `
				using a = b
				import "./foo"
				using c = d
			`,
			"/hoist-export-star.js": `
				using a = b
				export * from './foo'
				using c = d
			`,
			"/hoist-export-from.js": `
				using a = b
				export {x, y} from './foo'
				using c = d
			`,
			"/hoist-export-clause.js": `
				using a = b
				export {a, c as 'c!'}
				using c = d
			`,
			"/hoist-export-local-direct.js": `
				using a = b
				export var ac1 = [a, c], { x: [x1] } = foo
				export let a1 = a, { y: [y1] } = foo
				export const c1 = c, { z: [z1] } = foo
				var ac2 = [a, c], { x: [x2] } = foo
				let a2 = a, { y: [y2] } = foo
				const c2 = c, { z: [z2] } = foo
				using c = d
			`,
			"/hoist-export-local-indirect.js": `
				using a = b
				var ac1 = [a, c], { x: [x1] } = foo
				let a1 = a, { y: [y1] } = foo
				const c1 = c, { z: [z1] } = foo
				var ac2 = [a, c], { x: [x2] } = foo
				let a2 = a, { y: [y2] } = foo
				const c2 = c, { z: [z2] } = foo
				using c = d
				export {x1, y1, z1}
			`,
			"/hoist-export-class-direct.js": `
				using a = b
				export class Foo1 { ac = [a, c] }
				export class Bar1 { ac = [a, c, Bar1] }
				class Foo2 { ac = [a, c] }
				class Bar2 { ac = [a, c, Bar2] }
				using c = d
			`,
			"/hoist-export-class-indirect.js": `
				using a = b
				class Foo1 { ac = [a, c] }
				class Bar1 { ac = [a, c, Bar1] }
				class Foo2 { ac = [a, c] }
				class Bar2 { ac = [a, c, Bar2] }
				using c = d
				export {Foo1, Bar1}
			`,
			"/hoist-export-function-direct.js": `
				using a = b
				export function foo1() { return [a, c] }
				export function bar1() { return [a, c, bar1] }
				function foo2() { return [a, c] }
				function bar2() { return [a, c, bar2] }
				using c = d
			`,
			"/hoist-export-function-indirect.js": `
				using a = b
				function foo1() { return [a, c] }
				function bar1() { return [a, c, bar1] }
				function foo2() { return [a, c] }
				function bar2() { return [a, c, bar2] }
				using c = d
				export {foo1, bar1}
			`,
			"/hoist-export-default-class-name-unused.js": `
				using a = b
				export default class Foo {
					ac = [a, c]
				}
				using c = d
			`,
			"/hoist-export-default-class-name-used.js": `
				using a = b
				export default class Foo {
					ac = [a, c, Foo]
				}
				using c = d
			`,
			"/hoist-export-default-class-anonymous.js": `
				using a = b
				export default class {
					ac = [a, c]
				}
				using c = d
			`,
			"/hoist-export-default-function-name-unused.js": `
				using a = b
				export default function foo() {
					return [a, c]
				}
				using c = d
			`,
			"/hoist-export-default-function-name-used.js": `
				using a = b
				export default function foo() {
					return [a, c, foo]
				}
				using c = d
			`,
			"/hoist-export-default-function-anonymous.js": `
				using a = b
				export default function() {
					return [a, c]
				}
				using c = d
			`,
			"/hoist-export-default-expr.js": `
				using a = b
				export default [a, c]
				using c = d
			`,
		},
		entryPaths: []string{
			"/hoist-use-strict.js",
			"/hoist-directive.js",
			"/hoist-import.js",
			"/hoist-export-star.js",
			"/hoist-export-from.js",
			"/hoist-export-clause.js",
			"/hoist-export-local-direct.js",
			"/hoist-export-local-indirect.js",
			"/hoist-export-class-direct.js",
			"/hoist-export-class-indirect.js",
			"/hoist-export-function-direct.js",
			"/hoist-export-function-indirect.js",
			"/hoist-export-default-class-name-unused.js",
			"/hoist-export-default-class-name-used.js",
			"/hoist-export-default-class-anonymous.js",
			"/hoist-export-default-function-name-unused.js",
			"/hoist-export-default-function-name-used.js",
			"/hoist-export-default-function-anonymous.js",
			"/hoist-export-default-expr.js",
		},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: compat.Using,
		},
	})
}

func TestLowerUsingInsideTSNamespace(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				namespace ns {
					export let a = b
					using c = d
					export let e = f
				}
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: compat.Using,
		},
	})
}

func TestLowerAsyncGenerator(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				async function* foo() {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				}
				foo = async function* () {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				}
				foo = { async *bar () {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				} }
				class Foo { async *bar () {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				} }
				Foo = class { async *bar () {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				} }
				async function bar() {
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				}
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: compat.AsyncGenerator,
		},
	})
}

func TestLowerAsyncGeneratorNoAwait(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				async function* foo() {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				}
				foo = async function* () {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				}
				foo = { async *bar () {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				} }
				class Foo { async *bar () {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				} }
				Foo = class { async *bar () {
					yield
					yield x
					yield *x
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				} }
				async function bar() {
					await using x = await y
					for await (let x of y) {}
					for await (await using x of y) {}
				}
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: compat.AsyncGenerator | compat.AsyncAwait,
		},
	})
}

// https://github.com/evanw/esbuild/issues/3768
func TestJavaScriptDecoratorsBundleIssue3768(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/base-instance-method.js":   `class Foo { @dec foo() { return Foo } }`,
			"/base-instance-field.js":    `class Foo { @dec foo = Foo }`,
			"/base-instance-accessor.js": `class Foo { @dec accessor foo = Foo }`,

			"/base-static-method.js":   `class Foo { @dec static foo() { return Foo } }`,
			"/base-static-field.js":    `class Foo { @dec static foo = Foo }`,
			"/base-static-accessor.js": `class Foo { @dec static accessor foo = Foo }`,

			"/derived-instance-method.js":   `class Foo extends Bar { @dec foo() { return Foo } }`,
			"/derived-instance-field.js":    `class Foo extends Bar { @dec foo = Foo }`,
			"/derived-instance-accessor.js": `class Foo extends Bar { @dec accessor foo = Foo }`,

			"/derived-static-method.js":   `class Foo extends Bar { @dec static foo() { return Foo } }`,
			"/derived-static-field.js":    `class Foo extends Bar { @dec static foo = Foo }`,
			"/derived-static-accessor.js": `class Foo extends Bar { @dec static accessor foo = Foo }`,
		},
		entryPaths: []string{"/*"},
		options: config.Options{
			Mode:                  config.ModeBundle,
			AbsOutputDir:          "/out",
			UnsupportedJSFeatures: compat.Decorators,
		},
	})
}

// https://github.com/evanw/esbuild/issues/4378
func TestForAwaitWithOptionalCatchIssue4378(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.js": `
				async function test(b) {
					for await (const a of b) a()
				}
			`,
		},
		entryPaths: []string{"/entry.js"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			AbsOutputFile:         "/out.js",
			UnsupportedJSFeatures: compat.ForAwait,
			MinifySyntax:          true,
		},
	})
}

// https://github.com/evanw/esbuild/issues/4448
func TestLowerConstIssue4448(t *testing.T) {
	lower_suite.expectBundled(t, bundled{
		files: map[string]string{
			"/entry.ts": `
				import x = require('fs')
				console.log(import.meta.foo(x))
			`,
		},
		entryPaths: []string{"/entry.ts"},
		options: config.Options{
			Mode:                  config.ModePassThrough,
			UnsupportedJSFeatures: compat.ConstAndLet | compat.ImportMeta,
			AbsOutputFile:         "/out.js",
		},
		expectedScanLog: `entry.ts: WARNING: "import.meta" is not available in the configured target environment and will be empty
`,
	})
}
